安装费明细增加平台筛选与平台汇总 #595
@ -128,7 +128,7 @@ class InstallationDetailMirrorRepository
|
||||
/**
|
||||
* 按客服审核时间汇总安装费;金额取安装明细子表的结算明细金额。
|
||||
*
|
||||
* @return array{ready:bool,total:float,rows:list<array{store_name:string,installation_company:string,payment_channel:string,amount:float}>}
|
||||
* @return array{ready:bool,total:float,rows:list<array{platform:string,store_name:string,installation_company:string,payment_channel:string,amount:float}>}
|
||||
*/
|
||||
public function feeDetails(array $filters = []): array
|
||||
{
|
||||
@ -160,27 +160,43 @@ class InstallationDetailMirrorRepository
|
||||
->where('i.kfshTime', '>=', $from)
|
||||
->where('i.kfshTime', '<', \Illuminate\Support\Carbon::parse($to)->addDay()->toDateString());
|
||||
|
||||
$platformSql = "CASE"
|
||||
. " WHEN COALESCE(TRIM(install_main.shop_name), '') LIKE '%阿里巴巴%'"
|
||||
. " OR COALESCE(TRIM(install_main.shop_name), '') LIKE '%天猫%'"
|
||||
. " OR COALESCE(TRIM(install_main.shop_name), '') LIKE '%新佰广%'"
|
||||
. " OR COALESCE(TRIM(install_main.shop_name), '') LIKE '%淘工厂%'"
|
||||
. " OR COALESCE(TRIM(install_main.shop_name), '') LIKE '%淘宝%' THEN '淘系'"
|
||||
. " WHEN COALESCE(TRIM(install_main.shop_name), '') LIKE '抖音-%' THEN '抖音'"
|
||||
. " WHEN COALESCE(TRIM(install_main.shop_name), '') LIKE '拼多多-%' THEN '拼多多'"
|
||||
. " WHEN COALESCE(TRIM(install_main.shop_name), '') LIKE '唯品会-%' THEN '唯品会'"
|
||||
. " WHEN COALESCE(TRIM(install_main.shop_name), '') LIKE '京东-%' THEN '京东'"
|
||||
. " WHEN COALESCE(TRIM(install_main.shop_name), '') LIKE '京喜-%' THEN '京喜'"
|
||||
. " ELSE '其他' END";
|
||||
|
||||
$rows = $query
|
||||
->selectRaw($platformSql . ' AS platform')
|
||||
->selectRaw("COALESCE(NULLIF(TRIM(install_main.shop_name), ''), '未填写店铺') AS store_name")
|
||||
->selectRaw("COALESCE(NULLIF(TRIM(install_main.installCompanyName), ''), '未填写安装公司') AS installation_company")
|
||||
->selectRaw("CASE WHEN install_main.costWay IS NULL OR TRIM(install_main.costWay) = '' THEN '包安装' ELSE '不包安装' END AS payment_channel")
|
||||
->selectRaw('ROUND(SUM(CAST(i.settleItemAmount AS DECIMAL(18,2))), 2) AS amount')
|
||||
->groupByRaw(
|
||||
"COALESCE(NULLIF(TRIM(install_main.shop_name), ''), '未填写店铺'),"
|
||||
$platformSql . ','
|
||||
. " COALESCE(NULLIF(TRIM(install_main.shop_name), ''), '未填写店铺'),"
|
||||
. " COALESCE(NULLIF(TRIM(install_main.installCompanyName), ''), '未填写安装公司'),"
|
||||
. " CASE WHEN install_main.costWay IS NULL OR TRIM(install_main.costWay) = '' THEN '包安装' ELSE '不包安装' END"
|
||||
)
|
||||
->havingRaw('ABS(SUM(CAST(i.settleItemAmount AS DECIMAL(18,2)))) > 0.004')
|
||||
->get()
|
||||
->map(static fn (object $row): array => [
|
||||
'platform' => trim((string) ($row->platform ?? '其他')),
|
||||
'store_name' => trim((string) ($row->store_name ?? '')),
|
||||
'installation_company' => trim((string) ($row->installation_company ?? '')),
|
||||
'payment_channel' => (string) ($row->payment_channel ?? '包安装'),
|
||||
'amount' => round((float) ($row->amount ?? 0), 2),
|
||||
])
|
||||
->sort(static fn (array $left, array $right): int =>
|
||||
[abs($right['amount']), $left['store_name'], $left['installation_company']]
|
||||
<=> [abs($left['amount']), $right['store_name'], $right['installation_company']]
|
||||
[abs($right['amount']), $left['platform'], $left['store_name'], $left['installation_company']]
|
||||
<=> [abs($left['amount']), $right['platform'], $right['store_name'], $right['installation_company']]
|
||||
)
|
||||
->values()
|
||||
->all();
|
||||
|
||||
@ -164,7 +164,7 @@
|
||||
|
||||
.installation-fee-filters {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(10rem, 1fr));
|
||||
grid-template-columns: repeat(4, minmax(8.5rem, 1fr));
|
||||
gap: 0.75rem;
|
||||
padding: 0.8rem 1.1rem;
|
||||
border-bottom: 1px solid #eee8dd;
|
||||
@ -228,6 +228,42 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.installation-fee-platform-summary {
|
||||
margin: 0.75rem 0 1rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid #d8d2c7;
|
||||
}
|
||||
|
||||
.installation-fee-platform-summary h3 {
|
||||
margin: 0 0 0.5rem;
|
||||
color: #171717;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.installation-fee-platform-summary table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.75rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.installation-fee-platform-summary th,
|
||||
.installation-fee-platform-summary td {
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid #eee8dd;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.installation-fee-platform-summary th {
|
||||
background: #f7f0de;
|
||||
color: #595959;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.installation-fee-platform-summary .is-number {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.installation-fee-dialog__foot {
|
||||
padding: 0.65rem 1.1rem;
|
||||
border-top: 1px solid #eee8dd;
|
||||
@ -235,6 +271,10 @@
|
||||
font-size: 0.6875rem;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.installation-fee-filters { grid-template-columns: repeat(2, minmax(10rem, 1fr)); }
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.installation-fee-filters { grid-template-columns: 1fr; }
|
||||
.installation-fee-dialog__head { flex-direction: column; }
|
||||
@ -278,6 +318,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="installation-fee-filters" aria-label="安装费明细筛选">
|
||||
<label class="installation-fee-filter">
|
||||
<span>平台</span>
|
||||
<select data-installation-fee-platform aria-label="筛选平台">
|
||||
<option value="">全部平台</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="installation-fee-filter">
|
||||
<span>店铺名称</span>
|
||||
<select data-installation-fee-store aria-label="筛选店铺名称">
|
||||
@ -310,6 +356,18 @@
|
||||
<tbody data-installation-fee-rows></tbody>
|
||||
</table>
|
||||
<div class="installation-fee-empty" data-installation-fee-empty>请选择客服审核日期后查看明细</div>
|
||||
<section class="installation-fee-platform-summary" aria-label="安装费平台汇总">
|
||||
<h3>平台汇总</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">平台</th>
|
||||
<th scope="col" class="is-number">结算明细金额</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-installation-fee-platform-rows></tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
<div class="installation-fee-dialog__foot">
|
||||
<span data-installation-fee-count>共 0 条明细汇总</span>
|
||||
@ -329,10 +387,12 @@
|
||||
var status = document.querySelector('[data-installation-fee-status]');
|
||||
var subtitle = dialog.querySelector('[data-installation-fee-subtitle]');
|
||||
var currentTotal = dialog.querySelector('[data-installation-fee-current-total]');
|
||||
var platformFilter = dialog.querySelector('[data-installation-fee-platform]');
|
||||
var storeFilter = dialog.querySelector('[data-installation-fee-store]');
|
||||
var companyFilter = dialog.querySelector('[data-installation-fee-company]');
|
||||
var channelFilter = dialog.querySelector('[data-installation-fee-channel]');
|
||||
var rowsEl = dialog.querySelector('[data-installation-fee-rows]');
|
||||
var platformRowsEl = dialog.querySelector('[data-installation-fee-platform-rows]');
|
||||
var emptyEl = dialog.querySelector('[data-installation-fee-empty]');
|
||||
var countEl = dialog.querySelector('[data-installation-fee-count]');
|
||||
var allRows = [];
|
||||
@ -389,11 +449,13 @@
|
||||
}
|
||||
|
||||
function renderRows() {
|
||||
var platform = platformFilter.value;
|
||||
var store = storeFilter.value;
|
||||
var company = companyFilter.value;
|
||||
var channel = channelFilter.value;
|
||||
var rows = allRows.filter(function (row) {
|
||||
return (!store || row.store_name === store)
|
||||
return (!platform || row.platform === platform)
|
||||
&& (!store || row.store_name === store)
|
||||
&& (!company || row.installation_company === company)
|
||||
&& (!channel || row.payment_channel === channel);
|
||||
});
|
||||
@ -416,6 +478,28 @@
|
||||
rowsEl.appendChild(tr);
|
||||
});
|
||||
|
||||
var byPlatform = {};
|
||||
rows.forEach(function (row) {
|
||||
var key = row.platform || '其他';
|
||||
byPlatform[key] ??= 0;
|
||||
byPlatform[key] += Number(row.amount || 0);
|
||||
});
|
||||
platformRowsEl.replaceChildren();
|
||||
Object.keys(byPlatform).sort(function (left, right) {
|
||||
return Math.abs(byPlatform[right]) - Math.abs(byPlatform[left])
|
||||
|| left.localeCompare(right, 'zh-CN');
|
||||
}).forEach(function (name) {
|
||||
var tr = document.createElement('tr');
|
||||
var platformCell = document.createElement('td');
|
||||
platformCell.textContent = name;
|
||||
tr.appendChild(platformCell);
|
||||
var amount = document.createElement('td');
|
||||
amount.className = 'is-number';
|
||||
amount.textContent = yuan(byPlatform[name]);
|
||||
tr.appendChild(amount);
|
||||
platformRowsEl.appendChild(tr);
|
||||
});
|
||||
|
||||
currentTotal.textContent = yuan(total);
|
||||
countEl.textContent = '共 ' + rows.length.toLocaleString('zh-CN') + ' 条明细汇总';
|
||||
emptyEl.hidden = rows.length > 0;
|
||||
@ -428,11 +512,13 @@
|
||||
allRows = [];
|
||||
loadedSignature = '';
|
||||
rowsEl.replaceChildren();
|
||||
platformRowsEl.replaceChildren();
|
||||
currentTotal.textContent = '¥0.00';
|
||||
countEl.textContent = '共 0 条明细汇总';
|
||||
emptyEl.hidden = false;
|
||||
emptyEl.textContent = message;
|
||||
fillFilter(storeFilter, '全部店铺', []);
|
||||
fillFilter(platformFilter, '全部平台', []);
|
||||
fillFilter(companyFilter, '全部安装公司', []);
|
||||
fillFilter(channelFilter, '全部渠道', []);
|
||||
}
|
||||
@ -498,6 +584,7 @@
|
||||
cardTotal.textContent = wan(data.total);
|
||||
status.textContent = value.from + ' 至 ' + value.to + ' · ' + allRows.length + ' 条汇总';
|
||||
subtitle.textContent = value.from + ' 至 ' + value.to + ' · 按客服审核时间汇总';
|
||||
fillFilter(platformFilter, '全部平台', distinct('platform'));
|
||||
fillFilter(storeFilter, '全部店铺', distinct('store_name'));
|
||||
fillFilter(companyFilter, '全部安装公司', distinct('installation_company'));
|
||||
fillFilter(channelFilter, '全部渠道', distinct('payment_channel'));
|
||||
@ -526,7 +613,7 @@
|
||||
[fromInput, toInput].forEach(function (input) {
|
||||
if (input) { input.addEventListener('change', scheduleRefresh); }
|
||||
});
|
||||
[storeFilter, companyFilter, channelFilter].forEach(function (select) {
|
||||
[platformFilter, storeFilter, companyFilter, channelFilter].forEach(function (select) {
|
||||
select.addEventListener('change', renderRows);
|
||||
});
|
||||
dialog.querySelector('[data-installation-fee-close]').addEventListener('click', closeDialog);
|
||||
|
||||
@ -268,10 +268,13 @@ class ManagementCrudTest extends FeatureTestCase
|
||||
->assertSee('id="installation-fee-dialog"', false)
|
||||
->assertSeeText('安装费金额')
|
||||
->assertSeeText('按客服审核时间汇总安装明细的结算明细金额')
|
||||
->assertSeeText('平台')
|
||||
->assertSeeText('店铺名称')
|
||||
->assertSeeText('安装公司')
|
||||
->assertSeeText('收款渠道')
|
||||
->assertSeeText('结算明细金额')
|
||||
->assertSee('data-installation-fee-platform', false)
|
||||
->assertSee('data-installation-fee-platform-rows', false)
|
||||
->assertSee('refresh(true)', false)
|
||||
->assertSee("dispatchEvent(new Event('change'", false)
|
||||
->assertSee('installation-details-table-wrap', false)
|
||||
@ -336,8 +339,8 @@ class ManagementCrudTest extends FeatureTestCase
|
||||
'ready' => true,
|
||||
'total' => 2000.50,
|
||||
'rows' => [
|
||||
['store_name' => '店铺A', 'installation_company' => '万师傅', 'payment_channel' => '包安装', 'amount' => 1250.50],
|
||||
['store_name' => '店铺B', 'installation_company' => '鲁班到家', 'payment_channel' => '不包安装', 'amount' => 750.00],
|
||||
['platform' => '淘系', 'store_name' => '店铺A', 'installation_company' => '万师傅', 'payment_channel' => '包安装', 'amount' => 1250.50],
|
||||
['platform' => '京东', 'store_name' => '店铺B', 'installation_company' => '鲁班到家', 'payment_channel' => '不包安装', 'amount' => 750.00],
|
||||
],
|
||||
]);
|
||||
$this->app->instance(InstallationDetailMirrorRepository::class, $mirror);
|
||||
@ -349,6 +352,7 @@ class ManagementCrudTest extends FeatureTestCase
|
||||
]))
|
||||
->assertOk()
|
||||
->assertJsonPath('total', 2000.5)
|
||||
->assertJsonPath('rows.0.platform', '淘系')
|
||||
->assertJsonPath('rows.0.store_name', '店铺A')
|
||||
->assertJsonPath('rows.0.payment_channel', '包安装')
|
||||
->assertJsonPath('rows.1.payment_channel', '不包安装');
|
||||
|
||||
@ -154,12 +154,65 @@ class InstallationDetailMirrorRepositoryTest extends TestCase
|
||||
|
||||
$this->assertTrue($result['ready']);
|
||||
$this->assertSame([
|
||||
['store_name'=>'测试店','installation_company'=>'万师傅','payment_channel'=>'包安装','amount'=>100.0],
|
||||
['store_name'=>'其他店','installation_company'=>'鲁班到家','payment_channel'=>'不包安装','amount'=>80.0],
|
||||
['platform'=>'其他','store_name'=>'测试店','installation_company'=>'万师傅','payment_channel'=>'包安装','amount'=>100.0],
|
||||
['platform'=>'其他','store_name'=>'其他店','installation_company'=>'鲁班到家','payment_channel'=>'不包安装','amount'=>80.0],
|
||||
],$result['rows']);
|
||||
$this->assertSame(180.0,$result['total']);
|
||||
}
|
||||
|
||||
public function test_fee_details_classifies_platform_by_store_name_rules(): void
|
||||
{
|
||||
$stores = [
|
||||
'阿里巴巴-测试店' => '淘系',
|
||||
'天猫-测试店' => '淘系',
|
||||
'新佰广1688' => '淘系',
|
||||
'淘工厂-测试店' => '淘系',
|
||||
'淘宝-测试店' => '淘系',
|
||||
'抖音-测试店' => '抖音',
|
||||
'拼多多-测试店' => '拼多多',
|
||||
'唯品会-测试店' => '唯品会',
|
||||
'京东-测试店' => '京东',
|
||||
'京喜-测试店' => '京喜',
|
||||
'其他平台店铺' => '其他',
|
||||
];
|
||||
$mainRows = [];
|
||||
$itemRows = [];
|
||||
$id = 100;
|
||||
foreach (array_keys($stores) as $store) {
|
||||
$guid = 'platform-' . $id;
|
||||
$mainRows[] = [
|
||||
'Id' => $id,
|
||||
'SourceGuid' => $guid,
|
||||
'OrderNum' => 'ORDER-' . $id,
|
||||
'shop_name' => $store,
|
||||
'SubmitTime' => '2026-09-01',
|
||||
'ShipTIme' => '2026-09-02',
|
||||
'installCompanyName' => '万师傅',
|
||||
'costWay' => null,
|
||||
];
|
||||
$itemRows[] = [
|
||||
'Id' => $id,
|
||||
'guid' => $guid,
|
||||
'shopproid' => 'PRODUCT-' . $id,
|
||||
'kfshTime' => '2026-09-15 10:00:00',
|
||||
'settleItemAmount' => 1,
|
||||
];
|
||||
$id++;
|
||||
}
|
||||
DB::connection('business_mirror')->table('fixture_install')->insert($mainRows);
|
||||
DB::connection('business_mirror')->table('fixture_items')->insert($itemRows);
|
||||
|
||||
$result = $this->repository->feeDetails([
|
||||
'customer_review_from' => '2026-09-01',
|
||||
'customer_review_to' => '2026-09-30',
|
||||
]);
|
||||
$actual = collect($result['rows'])->pluck('platform', 'store_name')->all();
|
||||
|
||||
foreach ($stores as $store => $platform) {
|
||||
$this->assertSame($platform, $actual[$store] ?? null, $store);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_end_date_alone_is_valid_and_invalid_status_is_rejected(): void
|
||||
{
|
||||
request()->merge(['created_to'=>'2026-08-20']);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user