feat(self-operated): unify completed-day ranges and analytics sorting

This commit is contained in:
2026-10-14 12:39:16 +08:00
parent 6b30e35d07
commit e7411eb03f
17 changed files with 426 additions and 135 deletions
@@ -70,6 +70,23 @@ describe('warehouse operating dashboard', () => {
expect(wrapper.findAll('.dashboard-kpis section')).toHaveLength(6)
})
it('explains today and pending publication using the returned query context', async () => {
const data = structuredClone(dashboardFixture)
data.context.rebuildRequired = true
data.context.salesStatus = 'REBUILD_REQUIRED'
mocks.dashboard.mockResolvedValue(data)
const wrapper = page(); await choose(wrapper, { warehouseCode: 'WH-A' })
expect(wrapper.text()).toContain('所选时段包含今日(2026-10-13)')
expect(wrapper.text()).toContain('库存评估等待新版销量发布')
const refreshed = structuredClone(data)
refreshed.context.rangeEndDateExclusive = refreshed.context.anchorDate
refreshed.context.rebuildRequired = false
refreshed.context.salesStatus = 'VALID'
mocks.dashboard.mockResolvedValue(refreshed)
await findButton(wrapper, '整体刷新').trigger('click'); await flushPromises()
expect(wrapper.text()).not.toContain('所选时段包含今日(')
expect(wrapper.text()).not.toContain('库存评估等待新版销量发布')
})
it('offers coverage evidence for both inventory rankings even when they are empty', async () => {
const data = structuredClone(dashboardFixture)
data.longCoverageSkus = { ...data.longCoverageSkus, items: [], eligibleCount: '0', unassessableCount: '2711', dataStatus: 'PARTIAL' }
@@ -83,10 +100,27 @@ describe('warehouse operating dashboard', () => {
expect(wrapper.text()).toContain('待评估 2741 SKU')
expect(mocks.dashboard).toHaveBeenCalledOnce()
})
it('removes today, blocks custom today before querying and drills into the displayed dates', async () => {
const wrapper = page(); await choose(wrapper, { warehouseCode: 'WH-A' })
const toolbar = wrapper.findComponent(query)
const options = toolbar.props('fields').find((field: { key: string }) => field.key === 'rangePreset').options
expect(options.some((option: { value: string }) => option.value === 'TODAY')).toBe(false)
expect(options.find((option: { value: string }) => option.value === 'LAST_15_DAYS').label).toBe('最近15天')
const calls = mocks.dashboard.mock.calls.length
await choose(wrapper, { rangePreset: 'CUSTOM', dateRange: ['2026-10-01', '2026-10-13'] })
const disabled = toolbar.props('fields').find((field: { key: string }) => field.key === 'dateRange').props.disabledDate
expect(disabled(new Date(2026, 9, 13))).toBe(true)
expect(disabled(new Date(2026, 9, 12))).toBe(false)
await findButton(wrapper, '查询').trigger('click'); await flushPromises()
expect(mocks.dashboard).toHaveBeenCalledTimes(calls)
expect(wrapper.text()).toContain('结束日期最晚为昨日')
await findButton(wrapper, '查看销量').trigger('click')
expect(mocks.push.mock.calls.at(-1)?.[0].query).toMatchObject({ rangePreset: 'CUSTOM', startDate: dashboardFixture.context.rangeStartDate, endDate: '2026-10-13' })
})
it('uses one date range field, blocks incomplete ranges and clears obsolete validation errors', async () => {
const wrapper = page(); await choose(wrapper, { warehouseCode: 'WH-A', rangePreset: 'CUSTOM' })
const toolbar = wrapper.findComponent(query)
expect(toolbar.props('fields').filter((field: { type: string }) => field.type.includes('date'))).toEqual([{ key: 'dateRange', type: 'daterange', label: '日期范围', width: 300 }])
expect(toolbar.props('fields').filter((field: { type: string }) => field.type.includes('date'))).toMatchObject([{ key: 'dateRange', type: 'daterange', label: '日期范围', width: 300 }])
expect(mocks.dashboard).not.toHaveBeenCalled()
await findButton(wrapper, '查询').trigger('click'); await flushPromises()
expect(mocks.dashboard).not.toHaveBeenCalled()
@@ -103,7 +137,7 @@ describe('warehouse operating dashboard', () => {
await choose(wrapper, { dateRange: null })
await findButton(wrapper, '查询').trigger('click'); await flushPromises()
expect(mocks.dashboard).toHaveBeenCalledOnce()
await choose(wrapper, { rangePreset: 'TODAY' })
await choose(wrapper, { rangePreset: 'LAST_3_DAYS' })
expect(wrapper.findComponent(error).exists()).toBe(false)
})
it('restores a custom range and keeps the applied dates when an unsubmitted range is edited', async () => {
@@ -116,12 +150,12 @@ describe('warehouse operating dashboard', () => {
expect(JSON.parse(mocks.push.mock.calls.at(-1)?.[0].query.overviewReturn)).toMatchObject({ rangePreset: 'CUSTOM', startDate: '2026-10-10', endDate: '2026-10-10' })
wrapper.findComponent(query).vm.$emit('reset'); await flushPromises()
expect(wrapper.findComponent(query).props('modelValue').dateRange).toEqual([])
expect(mocks.dashboard.mock.calls.at(-1)?.[0]).not.toHaveProperty('startDate')
expect(mocks.dashboard.mock.calls.at(-1)?.[0]).not.toHaveProperty('endDate')
expect(mocks.dashboard.mock.calls.at(-1)?.[0]).toHaveProperty('startDate')
expect(mocks.dashboard.mock.calls.at(-1)?.[0]).toHaveProperty('endDate')
})
it('does not replace an invalid warehouse-less return with the default warehouse', async () => {
mocks.query = { context: JSON.stringify(dashboardFixture.context), overviewReturn: JSON.stringify({ rangePreset: 'THIS_MONTH', scroll: 760 }) }
mocks.query = { context: JSON.stringify(dashboardFixture.context), overviewReturn: JSON.stringify({ rangePreset: 'LAST_7_DAYS', scroll: 760 }) }
mocks.overview.mockResolvedValue({ warehouses: [{ warehouseCode: 'WH-A', warehouseName: 'A仓' }], observedAt: '2026-10-13T00:00:00Z' })
const wrapper = page(); await flushPromises()
expect(wrapper.text()).toContain('经营总览返回参数无效')
@@ -148,9 +182,9 @@ describe('warehouse operating dashboard', () => {
const wrapper = page(); await flushPromises()
expect(wrapper.findComponent(query).props('modelValue').warehouseCode).toBe('WH-A')
expect(mocks.dashboard).toHaveBeenCalledTimes(1)
expect(mocks.dashboard.mock.calls[0]?.[0]).toMatchObject({ warehouseCode: 'WH-A', rangePreset: 'LAST_15_DAYS' })
expect(mocks.dashboard.mock.calls[0]?.[0]).toMatchObject({ warehouseCode: 'WH-A', rangePreset: 'CUSTOM' })
expect(wrapper.find('.dashboard-data-notes').exists()).toBe(false)
await choose(wrapper, { rangePreset: 'TODAY' })
await choose(wrapper, { rangePreset: 'LAST_3_DAYS' })
wrapper.findComponent(query).vm.$emit('reset'); await flushPromises()
expect(wrapper.findComponent(query).props('modelValue')).toMatchObject({ warehouseCode: 'WH-A', rangePreset: 'LAST_15_DAYS' })
expect(mocks.dashboard.mock.calls.at(-1)?.[0]).not.toHaveProperty('queryAsOf')
@@ -165,14 +199,14 @@ describe('warehouse operating dashboard', () => {
})
it('restores the saved warehouse, range, fixed context and scroll when returning from a drilldown', async () => {
const scroll = vi.spyOn(window, 'scrollTo').mockImplementation(() => undefined)
mocks.query = { warehouseCode: 'WH-A', context: JSON.stringify(dashboardFixture.context), overviewReturn: JSON.stringify({ rangePreset: 'THIS_MONTH', scroll: 760 }) }
mocks.query = { warehouseCode: 'WH-A', context: JSON.stringify(dashboardFixture.context), overviewReturn: JSON.stringify({ rangePreset: 'LAST_7_DAYS', scroll: 760 }) }
const wrapper = page(); await flushPromises()
expect(mocks.dashboard.mock.calls[0]?.[0]).toMatchObject({ warehouseCode: 'WH-A', rangePreset: 'THIS_MONTH', salesRunId: '11', inventoryRunId: '12', queryAsOf: dashboardFixture.context.queryAsOf })
expect(wrapper.findComponent(query).props('modelValue')).toMatchObject({ warehouseCode: 'WH-A', rangePreset: 'THIS_MONTH' })
expect(mocks.dashboard.mock.calls[0]?.[0]).toMatchObject({ warehouseCode: 'WH-A', rangePreset: 'CUSTOM', startDate: '2026-10-06', endDate: '2026-10-12', salesRunId: '11', inventoryRunId: '12', queryAsOf: dashboardFixture.context.queryAsOf })
expect(wrapper.findComponent(query).props('modelValue')).toMatchObject({ warehouseCode: 'WH-A', rangePreset: 'LAST_7_DAYS' })
expect(scroll).toHaveBeenCalledWith({ top: 760 })
})
it('does not silently refresh an expired return context', async () => {
mocks.query = { warehouseCode: 'WH-A', context: JSON.stringify({ ...dashboardFixture.context, queryExpiresAt: '2026-10-13T00:01:00Z' }), overviewReturn: JSON.stringify({ rangePreset: 'TODAY', scroll: 0 }) }
mocks.query = { warehouseCode: 'WH-A', context: JSON.stringify({ ...dashboardFixture.context, queryExpiresAt: '2026-10-13T00:01:00Z' }), overviewReturn: JSON.stringify({ rangePreset: 'LAST_3_DAYS', scroll: 0 }) }
vi.useFakeTimers({ toFake: ['Date', 'setInterval', 'clearInterval'] }); vi.setSystemTime(new Date('2026-10-13T00:02:00Z'))
const wrapper = page(); await flushPromises()
expect(mocks.dashboard).not.toHaveBeenCalled()
@@ -201,7 +235,7 @@ describe('warehouse operating dashboard', () => {
expect(mocks.dashboard).not.toHaveBeenCalled()
await choose(wrapper, { warehouseCode: 'WH-A' })
expect(mocks.dashboard).toHaveBeenCalledTimes(1)
expect(mocks.dashboard.mock.calls[0]?.[0]).toMatchObject({ warehouseCode: 'WH-A', rangePreset: 'LAST_15_DAYS' })
expect(mocks.dashboard.mock.calls[0]?.[0]).toMatchObject({ warehouseCode: 'WH-A', rangePreset: 'CUSTOM' })
expect(wrapper.findAll('.dashboard-kpis section')).toHaveLength(6)
expect(wrapper.findAllComponents(DashboardBars)).toHaveLength(4)
expect(wrapper.findAllComponents(DashboardTrend)).toHaveLength(1)
@@ -215,8 +249,8 @@ describe('warehouse operating dashboard', () => {
})
it('changes the shared date range while pinning versions, then explicitly refreshes the whole snapshot', async () => {
const wrapper = page(); await choose(wrapper, { warehouseCode: 'WH-A' })
await choose(wrapper, { rangePreset: 'TODAY' }); await findButton(wrapper, '查询').trigger('click'); await flushPromises()
expect(mocks.dashboard.mock.calls.at(-1)?.[0]).toMatchObject({ rangePreset: 'TODAY', salesRunId: '11', inventoryRunId: '12', queryAsOf: dashboardFixture.context.queryAsOf })
await choose(wrapper, { rangePreset: 'LAST_3_DAYS' }); await findButton(wrapper, '查询').trigger('click'); await flushPromises()
expect(mocks.dashboard.mock.calls.at(-1)?.[0]).toMatchObject({ rangePreset: 'CUSTOM', startDate: '2026-10-10', endDate: '2026-10-12', salesRunId: '11', inventoryRunId: '12', queryAsOf: dashboardFixture.context.queryAsOf })
await findButton(wrapper, '整体刷新').trigger('click'); await flushPromises()
expect(mocks.dashboard.mock.calls.at(-1)?.[0]).not.toHaveProperty('salesRunId')
})
@@ -230,7 +264,7 @@ describe('warehouse operating dashboard', () => {
expect(wrapper.find('.dashboard-kpis').exists()).toBe(false)
expect(wrapper.find('.dashboard-placeholder').exists()).toBe(true)
expect(mocks.dashboard).toHaveBeenCalledTimes(2)
expect(mocks.dashboard.mock.calls[1]?.[0]).toMatchObject({ warehouseCode: 'WH-B', rangePreset: 'LAST_15_DAYS' })
expect(mocks.dashboard.mock.calls[1]?.[0]).toMatchObject({ warehouseCode: 'WH-B', rangePreset: 'CUSTOM' })
expect(mocks.dashboard.mock.calls[1]?.[0]).not.toHaveProperty('queryAsOf')
finishNew(dashboardFixture); await flushPromises()
expect(wrapper.find('.dashboard-kpis').exists()).toBe(true)
@@ -248,7 +282,7 @@ describe('warehouse operating dashboard', () => {
toolbar.vm.$emit('reset'); await flushPromises()
expect(toolbar.props('modelValue')).toMatchObject({ warehouseCode: 'WH-B', rangePreset: 'LAST_15_DAYS' })
expect(mocks.dashboard).toHaveBeenCalledTimes(3)
expect(mocks.dashboard.mock.calls.at(-1)?.[0]).toMatchObject({ warehouseCode: 'WH-B', rangePreset: 'LAST_15_DAYS' })
expect(mocks.dashboard.mock.calls.at(-1)?.[0]).toMatchObject({ warehouseCode: 'WH-B', rangePreset: 'CUSTOM' })
})
it('preserves same-SKU owner identity in the 1120 dialog and cancels closed requests', async () => {
let finish!: (value: unknown) => void