快递应付:报价维护只留报价页签 + 附加费结算定时同步(每日7天/每周90天) (#314)

This commit is contained in:
zhoushengchen 2026-10-12 17:49:10 +08:00
parent fba094ce26
commit c55c06026f
7 changed files with 53 additions and 80 deletions

View File

@ -381,6 +381,14 @@ class Pages extends Controller
abort_if(empty($sectionConfig), 403, '当前账号没有该页面权限。');
// 附加费结算已独立到快递应付页面,旧页签地址跳到新页面。
if ($section === 'courier-payables' && $page === 'courier-surcharge' && $tab === 'settlement') {
return redirect()->route('business.pages.show', array_merge(request()->query(), [
'section' => $section,
'page' => 'courier-surcharge-settlement',
]));
}
$pages = collect($sectionConfig['pages'] ?? []);
if ($pages->isEmpty()) {
@ -3782,7 +3790,7 @@ class Pages extends Controller
protected function contentForCourierSurcharge(?string $tab = null): array
{
$clean = static fn (mixed $value, int $length = 60): string => mb_substr(trim((string) $value), 0, $length);
$quoteInput = $tab === 'settlement' ? [] : request()->query();
$quoteInput = request()->query();
$filters = [
'keyword' => $clean($quoteInput['keyword'] ?? '', 40),
'courier' => $clean($quoteInput['courier'] ?? '', 40),
@ -3810,10 +3818,6 @@ class Pages extends Controller
'courierSurchargeAuditLogs' => $this->courierSurchargeAudits->recent(),
];
if ($tab === 'settlement') {
$content += $this->contentForCourierSurchargeSettlement();
}
return $content;
}

View File

@ -713,7 +713,6 @@ $navigation = [
'title' => '附加费报价维护',
'tabs' => [
['slug' => 'quotes', 'title' => '附加费报价'],
['slug' => 'settlement', 'title' => '附加费结算'],
],
],
],

View File

@ -204,6 +204,10 @@ CRON_TZ=Asia/Shanghai
# 头条放心购(店铺收款账户账单 › 抖音账户)资金流水:每天 09:30 定点把该渠道所用
# 原始账单镜像追平一次,保证上班前当日/前一日流水已进系统,而不是等每分钟轮转慢慢走。
30 9 * * * root /usr/bin/flock -w 1800 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-tables operations_replica.rpt.jst_originalBillInfo --max-batches=20 --quiet >/dev/null 2>&1
# 附加费结算:每天滚动重算最近 7 天;每周一再把最近 90 天全量重算一次,
# 补齐晚到的退货进仓与审批变化(超长费按出库日期、退回费/拦截费按查件工单创建时间)。
30 6 * * * root /usr/bin/flock -w 1800 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-overlength --days=7 --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-return --days=7 --quiet' >/dev/null 2>&1
40 6 * * 1 root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-overlength --days=90 --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-return --days=90 --quiet' >/dev/null 2>&1
EOF
chmod 644 /etc/cron.d/caiwu-jiansun-snapshots

View File

@ -208,6 +208,10 @@ CRON_TZ=Asia/Shanghai
# 头条放心购(店铺收款账户账单 › 抖音账户)资金流水:每天 09:30 定点把该渠道所用
# 原始账单镜像追平一次,保证上班前当日/前一日流水已进系统,而不是等每分钟轮转慢慢走。
30 9 * * * root /usr/bin/flock -w 1800 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-tables operations_replica.rpt.jst_originalBillInfo --max-batches=20 --quiet >/dev/null 2>&1
# 附加费结算:每天滚动重算最近 7 天;每周一再把最近 90 天全量重算一次,
# 补齐晚到的退货进仓与审批变化(超长费按出库日期、退回费/拦截费按查件工单创建时间)。
30 6 * * * root /usr/bin/flock -w 1800 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-overlength --days=7 --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-return --days=7 --quiet' >/dev/null 2>&1
40 6 * * 1 root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-overlength --days=90 --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-return --days=90 --quiet' >/dev/null 2>&1
EOF
chmod 644 /etc/cron.d/caiwu-jiansun-snapshots

View File

@ -1,63 +1,5 @@
@php
$surchargeTabUrls = collect(['quotes', 'settlement'])->mapWithKeys(fn ($tab) => [
$tab => route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-surcharge', 'tab' => $tab]),
])->all();
@endphp
<div data-surcharge-workspace>
<div data-surcharge-panel="quotes" @if ($courierSurchargeActiveTab !== 'quotes') hidden @endif>
<div data-surcharge-panel="quotes">
@include('business.pages.courier-payables.courier-surcharge')
</div>
<div data-surcharge-panel="settlement" @if ($courierSurchargeActiveTab !== 'settlement') hidden @endif>
@include('business.pages.courier-payables.courier-surcharge-settlement')
</div>
</div>
<script>
(() => {
const root = document.querySelector('[data-surcharge-workspace]');
const content = root.closest('.business-content-section');
const navigation = content.parentElement.querySelector('.business-tab-bar');
const links = [...navigation.querySelectorAll('a')];
const panels = [...root.querySelectorAll('[data-surcharge-panel]')];
const title = document.querySelector('[data-page-title-first] h1');
const documentTitle = document.title;
const initialTitle = @json($courierSurchargeActiveTab === 'settlement' ? '附加费结算' : '附加费报价');
const urls = @json($surchargeTabUrls);
const keyFor = (link) => Object.keys(urls).find(key => new URL(urls[key]).pathname === new URL(link.href).pathname);
const savedUrls = { ...urls };
let active = @json($courierSurchargeActiveTab);
savedUrls[active] = location.href;
function select(key, push = true) {
if (!Object.hasOwn(urls, key)) return;
if (push) savedUrls[active] = location.href;
active = key;
panels.forEach(panel => { panel.hidden = panel.dataset.surchargePanel !== key; });
links.forEach(link => {
const selected = keyFor(link) === key;
link.classList.toggle('is-active', selected);
if (selected) link.setAttribute('aria-current', 'page');
else link.removeAttribute('aria-current');
});
const label = key === 'settlement' ? '附加费结算' : '附加费报价';
if (title) title.textContent = label;
content.setAttribute('aria-label', label);
document.title = documentTitle.replace(initialTitle, label);
if (push) history.pushState({}, '', savedUrls[key]);
window.dispatchEvent(new Event('resize'));
}
links.forEach(link => link.addEventListener('click', event => {
if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
const key = keyFor(link);
if (!key) return;
event.preventDefault();
if (key !== active) select(key);
}));
window.addEventListener('popstate', () => {
const key = Object.keys(urls).find(key => new URL(urls[key]).pathname === location.pathname) || 'quotes';
select(key, false);
savedUrls[key] = location.href;
});
})();
</script>

View File

@ -20,10 +20,8 @@ class CourierSurchargeSettlementPageTest extends FeatureTestCase
$this->loginAs()->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'courier-surcharge',
'tab' => 'settlement',
'page' => 'courier-surcharge-settlement',
]))->assertOk()
->assertSee('data-surcharge-panel="quotes" hidden', false)
->assertSeeText('附加费结算')
->assertSeeText('共享业务镜像尚未建立,附加费结算暂时无法读取。')
->assertSee('data-finance-table-name="courier-surcharge-settlement"', false)
@ -45,8 +43,7 @@ class CourierSurchargeSettlementPageTest extends FeatureTestCase
$this->loginAs()->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'courier-surcharge',
'tab' => 'settlement',
'page' => 'courier-surcharge-settlement',
'courier_name' => '测试快递',
'waybill' => '001234',
'shop_name' => '测试店铺',
@ -61,7 +58,7 @@ class CourierSurchargeSettlementPageTest extends FeatureTestCase
->assertViewHas('content', fn (array $content) => $content['courierSurchargeSettlementFilters']['status'] === '已计算');
}
public function test_quote_page_exposes_quote_and_settlement_tabs(): void
public function test_quote_page_only_exposes_quote_tab(): void
{
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
@ -70,18 +67,29 @@ class CourierSurchargeSettlementPageTest extends FeatureTestCase
'page' => 'courier-surcharge',
]))->assertOk()
->assertSeeText('附加费报价')
->assertSeeText('附加费结算')
->assertSee('data-surcharge-panel="settlement" hidden', false)
->assertSee(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'courier-surcharge',
'tab' => 'quotes',
]), false)
->assertSee(route('business.pages.show', [
->assertDontSee(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'courier-surcharge',
'tab' => 'settlement',
]), false);
]), false)
->assertDontSee('data-surcharge-panel="settlement"', false)
->assertSee('data-surcharge-panel="quotes"', false);
}
public function test_old_settlement_tab_url_redirects_to_standalone_page(): void
{
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
$this->loginAs()->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'courier-surcharge',
'tab' => 'settlement',
'waybill' => '0000123456789',
]))->assertRedirect(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'courier-surcharge-settlement',
'waybill' => '0000123456789',
]));
}
public function test_settlement_page_renders_as_courier_payables_page(): void

View File

@ -74,6 +74,18 @@ class BusinessMirrorScheduleTest extends TestCase
$contents,
$script,
);
$this->assertStringContainsString(
"30 6 * * * root /usr/bin/flock -w 1800 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-overlength --days=7 --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-return --days=7 --quiet'",
$contents,
$script,
);
$this->assertStringContainsString(
"40 6 * * 1 root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-overlength --days=90 --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-return --days=90 --quiet'",
$contents,
$script,
);
$this->assertSame(2, substr_count($contents, 'courier-surcharge:pull-overlength'), $script);
$this->assertSame(2, substr_count($contents, 'courier-surcharge:pull-return'), $script);
$this->assertStringContainsString(
'business-mirror:sync-third-party-warehouse-storage --max-batches=0 --quiet',
$contents,