348 lines
14 KiB
PHP
348 lines
14 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Business;
|
|
|
|
use App\Models\Auth\Permission;
|
|
use Illuminate\Pagination\LengthAwarePaginator;
|
|
use Modules\EcommerceFinance\Repositories\ProductPriceMirrorRepository;
|
|
use Modules\EcommerceFinance\Services\JianSunQueryCache;
|
|
use Modules\EcommerceFinance\Services\UpstreamQueryCache;
|
|
use Modules\EcommerceFinance\Services\UpstreamQueryCacheFactory;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
use Tests\Feature\FeatureTestCase;
|
|
|
|
class SupplierPayablePagesTest extends FeatureTestCase
|
|
{
|
|
protected function grantAdminPermission(): void
|
|
{
|
|
$this->user->attachPermission(
|
|
Permission::where('name', 'read-admin-panel')->firstOrFail()
|
|
);
|
|
}
|
|
|
|
public function testEverySupplierPayablePageExposesTheFiveRequestedActions(): void
|
|
{
|
|
$this->grantAdminPermission();
|
|
$pages = [
|
|
'payment-reconciliation', 'return-reconciliation', 'invoice-deductions',
|
|
'compensation-details', 'advance-details', 'sample-deductions',
|
|
'other-adjustments', 'delivery-unpriced', 'return-unpriced',
|
|
'dropshipping-express', 'board-deductions', 'supplier-payables-report',
|
|
];
|
|
|
|
foreach ($pages as $page) {
|
|
$response = $this->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'supplier-payables',
|
|
'page' => $page,
|
|
]));
|
|
|
|
$response->assertOk();
|
|
foreach (['导入', '导出', '编辑', '查询', '重置'] as $action) {
|
|
$response->assertSeeText($action);
|
|
}
|
|
$response->assertSeeText('下载导入模板');
|
|
$response->assertSee('/template', false);
|
|
preg_match_all('/class="[^"]*supplier-action-button/', $response->getContent(), $actionButtons);
|
|
$this->assertCount(5, $actionButtons[0]);
|
|
}
|
|
}
|
|
|
|
public function testSupplierPayableEditorOverridesTheGlobalMultipartRequestType(): void
|
|
{
|
|
$source = file_get_contents(resource_path('assets/js/views/ecommerce-finance/supplier-payable-maintenance.js'));
|
|
$dialog = file_get_contents(resource_path('views/business/pages/supplier-payables/maintenance-toolbar.blade.php'));
|
|
|
|
$this->assertIsString($source);
|
|
$this->assertStringContainsString("'Content-Type': 'application/json'", $source);
|
|
$this->assertStringContainsString('maintenanceEditSelected()', $source);
|
|
$this->assertStringContainsString('v-if="maintenance.row"', $dialog);
|
|
}
|
|
|
|
public function testVueTablesRequireASelectedRowBeforeToolbarEditing(): void
|
|
{
|
|
$this->grantAdminPermission();
|
|
$pages = [
|
|
'payment-reconciliation', 'return-reconciliation', 'invoice-deductions',
|
|
'compensation-details', 'advance-details', 'sample-deductions',
|
|
'other-adjustments', 'delivery-unpriced', 'return-unpriced',
|
|
'dropshipping-express',
|
|
];
|
|
|
|
foreach ($pages as $page) {
|
|
$response = $this->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'supplier-payables',
|
|
'page' => $page,
|
|
]));
|
|
$content = $response->getContent();
|
|
|
|
$response->assertOk();
|
|
$this->assertStringContainsString('@current-change="maintenanceSelect"', $content);
|
|
$this->assertStringContainsString('@click="maintenanceEditSelected"', $content);
|
|
$this->assertStringNotContainsString(':disabled="!maintenance.fields.length', $content);
|
|
$this->assertStringNotContainsString('maintenanceOpen(scope.row)', $content);
|
|
$this->assertLessThan(strpos($content, '@click="maintenanceEditSelected"'), strpos($content, '重置'));
|
|
}
|
|
}
|
|
|
|
public function testDeliveryUnpricedPageUsesItsOwnTwentyColumnTable()
|
|
{
|
|
$this->grantAdminPermission();
|
|
|
|
$response = $this->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'supplier-payables',
|
|
'page' => 'delivery-unpriced',
|
|
]));
|
|
|
|
$response
|
|
->assertOk()
|
|
->assertSee('id="delivery-unpriced-app"', false)
|
|
->assertSee('来源单号')
|
|
->assertSee('单据编号(采购单号)')
|
|
->assertSee('负责人')
|
|
->assertSee('public/js/ecommerce-finance/delivery-unpriced.min.js', false)
|
|
->assertDontSee('class="business-blank-page"', false);
|
|
|
|
$this->assertSame(20, substr_count($response->getContent(), '<el-table-column'));
|
|
}
|
|
|
|
public function testReturnUnpricedPageUsesItsOwnTwentyColumnTable()
|
|
{
|
|
$this->grantAdminPermission();
|
|
|
|
$response = $this->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'supplier-payables',
|
|
'page' => 'return-unpriced',
|
|
]));
|
|
|
|
$response
|
|
->assertOk()
|
|
->assertSee('id="return-unpriced-app"', false)
|
|
->assertSee('明细供应商')
|
|
->assertSee('供应商条码')
|
|
->assertSee('对账人')
|
|
->assertSee('public/js/ecommerce-finance/return-unpriced.min.js', false)
|
|
->assertDontSee('class="business-blank-page"', false);
|
|
|
|
$this->assertSame(20, substr_count($response->getContent(), '<el-table-column'));
|
|
}
|
|
|
|
public function testBoardDeductionsPageUsesItsOwnSeventeenColumnTable()
|
|
{
|
|
$this->grantAdminPermission();
|
|
|
|
$response = $this->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'supplier-payables',
|
|
'page' => 'board-deductions',
|
|
]));
|
|
|
|
$response
|
|
->assertOk()
|
|
->assertSee('class="supplier-board-table"', false)
|
|
->assertSee('入库日期')
|
|
->assertSee('出库备注')
|
|
->assertSee('送货单号')
|
|
->assertDontSee('class="business-blank-page"', false);
|
|
|
|
preg_match_all('/<th(?:\s|>)/', $response->getContent(), $headers);
|
|
|
|
$this->assertCount(17, $headers[0]);
|
|
}
|
|
|
|
public function testSupplierPayablesReportPageUsesItsOwnThirtyFourColumnGroupedTable()
|
|
{
|
|
$this->grantAdminPermission();
|
|
|
|
$response = $this->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'supplier-payables',
|
|
'page' => 'supplier-payables-report',
|
|
]));
|
|
|
|
$response
|
|
->assertOk()
|
|
->assertSee('class="supplier-payables-report-table"', false)
|
|
->assertSee('已销账供应商')
|
|
->assertSee('本月采购金额')
|
|
->assertSee('<th class="group-addition">加</th>', false)
|
|
->assertSee('<th colspan="6" class="group-payment">单位:元</th>', false)
|
|
->assertSee('<th class="group-payment">供应商余额</th>', false)
|
|
->assertSee('最晚出账单时间(新执行标准)')
|
|
->assertDontSee('class="business-blank-page"', false);
|
|
|
|
preg_match_all('/<col(?:\s|>)/', $response->getContent(), $columns);
|
|
|
|
$this->assertCount(34, $columns[0]);
|
|
}
|
|
|
|
public function testProductPricesPageUsesOperationsReplicaPriceLimitFields(): void
|
|
{
|
|
$this->grantAdminPermission();
|
|
|
|
$repository = \Mockery::mock(ProductPriceMirrorRepository::class);
|
|
$repository->shouldReceive('ready')->once()->andReturnTrue();
|
|
$repository->shouldReceive('initialized')->once()->andReturnTrue();
|
|
$repository->shouldReceive('paginate')
|
|
->once()
|
|
->withArgs(static function (array $filters, int $page, int $limit): bool {
|
|
return $filters === [
|
|
'barcode' => 'YMJ005203N1176V01',
|
|
'completed_from' => '2025-12-01',
|
|
'completed_to' => '2025-12-31',
|
|
] && $page === 1 && $limit > 0;
|
|
})
|
|
->andReturn(new LengthAwarePaginator([(object) [
|
|
'upstream_key' => hash('sha256', 'product-price-row'),
|
|
'barcode' => 'YMJ005203N1176V01',
|
|
'spu' => 'YMJ0052',
|
|
'product_name' => '款式二 36/36/176 胡桃木色 BY-008',
|
|
'category' => '住宅家具 - 衣帽架',
|
|
'production_status' => '销售',
|
|
'minimum_price' => 152.8,
|
|
'price_limit' => 152.6,
|
|
'purchase_price' => 91.11,
|
|
'shipping_fee' => 11.62,
|
|
'jingxi_limit_price' => 123.0,
|
|
'approval_status' => '已完成',
|
|
'approval_result' => '已同意',
|
|
'workflow_title' => '特殊限价需求申请',
|
|
'completed_at' => '2025-12-31 00:00:00',
|
|
] ], 1, 25, 1));
|
|
$this->app->instance(ProductPriceMirrorRepository::class, $repository);
|
|
|
|
$response = $this->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'supplier-payables',
|
|
'page' => 'product-prices',
|
|
'barcode' => 'YMJ005203N1176V01',
|
|
'completed_from' => '2025-12-01',
|
|
'completed_to' => '2025-12-31',
|
|
]));
|
|
|
|
$response
|
|
->assertOk()
|
|
->assertSee('商品编码')
|
|
->assertSee('最低售价')
|
|
->assertSee('计算限价')
|
|
->assertSee('采购价')
|
|
->assertSee('京喜限价')
|
|
->assertSee('YMJ005203N1176V01')
|
|
->assertSee('152.80')
|
|
->assertSee('已同意')
|
|
->assertSeeText('导入')
|
|
->assertSeeText('导出')
|
|
->assertSeeText('下载导入模板')
|
|
->assertSeeText('查询')
|
|
->assertSeeText('重置')
|
|
->assertSee('data-product-price-edit-open', false)
|
|
->assertSee('data-product-price-row', false)
|
|
->assertDontSee('class="product-price-edit-button"', false)
|
|
->assertSeeText('编辑商品价格')
|
|
->assertDontSee('是否采购降本');
|
|
|
|
preg_match_all('/<th(?:\s|>)/', $response->getContent(), $headers);
|
|
preg_match_all('/class="[^"]*supplier-action-button/', $response->getContent(), $actionButtons);
|
|
|
|
$this->assertCount(15, $headers[0]);
|
|
$this->assertCount(5, $actionButtons[0]);
|
|
}
|
|
|
|
public function testProductPricesPageDoesNotTreatUninitializedMirrorAsEmptyData(): void
|
|
{
|
|
$this->grantAdminPermission();
|
|
|
|
$repository = \Mockery::mock(ProductPriceMirrorRepository::class);
|
|
$repository->shouldReceive('ready')->once()->andReturnTrue();
|
|
$repository->shouldReceive('initialized')->once()->andReturnFalse();
|
|
$repository->shouldNotReceive('paginate');
|
|
$this->app->instance(ProductPriceMirrorRepository::class, $repository);
|
|
|
|
$url = route('business.pages.show', [
|
|
'company_id' => $this->company->id,
|
|
'section' => 'supplier-payables',
|
|
'page' => 'product-prices',
|
|
]) . '?page=2';
|
|
$response = $this->loginAs()->get($url);
|
|
|
|
$response
|
|
->assertOk()
|
|
->assertSee('商品价格正在执行首次全量同步')
|
|
->assertDontSee('没有符合当前筛选条件的商品价格数据');
|
|
}
|
|
|
|
public function testCompensationPageUsesTheFiveColumnsFromTheReferenceSheet(): void
|
|
{
|
|
$this->grantAdminPermission();
|
|
|
|
$response = $this->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'supplier-payables',
|
|
'page' => 'compensation-details',
|
|
]));
|
|
|
|
$response
|
|
->assertOk()
|
|
->assertSee('id="compensation-detail-app"', false)
|
|
->assertSee('赔付原因')
|
|
->assertSee('备注')
|
|
->assertSee('public/js/ecommerce-finance/compensation-details.min.js', false);
|
|
|
|
$this->assertSame(5, substr_count($response->getContent(), '<el-table-column'));
|
|
}
|
|
|
|
public function testDataBackedSupplierPayableListRoutesAreAvailableToWorkspaceAjaxRequests()
|
|
{
|
|
$this->grantAdminPermission();
|
|
|
|
$cache = \Mockery::mock(JianSunQueryCache::class);
|
|
$cache->shouldReceive('remember')->times(10)->andReturnUsing(
|
|
static fn (string $scope): array => [
|
|
'data' => [['scope' => $scope]],
|
|
'total' => 1,
|
|
],
|
|
);
|
|
$cache->shouldReceive('responseHeaders')->times(10)->andReturn([]);
|
|
$this->app->instance(JianSunQueryCache::class, $cache);
|
|
|
|
$routes = [
|
|
'workspace-data.supplier-payables.payment-reconciliation.index',
|
|
'workspace-data.supplier-payables.return-reconciliation.index',
|
|
'workspace-data.supplier-payables.invoice-deductions.index',
|
|
'workspace-data.supplier-payables.compensation-details.index',
|
|
'workspace-data.supplier-payables.advance-details.index',
|
|
'workspace-data.supplier-payables.delivery-unpriced.index',
|
|
'workspace-data.supplier-payables.return-unpriced.index',
|
|
'workspace-data.supplier-payables.sample-deductions.index',
|
|
'workspace-data.supplier-payables.other-adjustments.index',
|
|
'workspace-data.supplier-payables.dropshipping-express.index',
|
|
];
|
|
|
|
foreach ($routes as $routeName) {
|
|
$this->loginAs()
|
|
->withHeader('X-Requested-With', 'XMLHttpRequest')
|
|
->get(route($routeName))
|
|
->assertOk()
|
|
->assertJsonPath('total', 1);
|
|
}
|
|
}
|
|
|
|
public function testSupplierPayableDataRouteRejectsUsersWithoutTheMatchingSidebarPage(): 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 => ['supplier-payables/invoice-deductions'],
|
|
],
|
|
], JSON_UNESCAPED_UNICODE));
|
|
|
|
try {
|
|
$this->loginAs()
|
|
->withHeader('X-Requested-With', 'XMLHttpRequest')
|
|
->get(route('workspace-data.supplier-payables.payment-reconciliation.index'));
|
|
$this->fail('缺少页面权限的用户不应读取货款对账数据。');
|
|
} catch (HttpException $exception) {
|
|
$this->assertSame(403, $exception->getStatusCode());
|
|
}
|
|
}
|
|
|
|
}
|