fix(pl): 独立组合统计编号筛选与关键词搜索以避免条件被忽略。

This commit is contained in:
wuzilong committed 2026-11-01 22:11:51 +08:00
1 parent da56a1e319
commit 726575aaf2
7 files changed
+62 -12

No files matched your search

+4 -2
View File
@@ -148,20 +148,22 @@ def save_bindings(contract_id: str, body: ContractCertBindingsBody,
@router.get('/statistics/contracts')
def contract_statistics_list(keyword: str = Query('', max_length=200),
contract_no: str = Query('', alias='contractNo', max_length=200),
page: int = Query(1, ge=1),
page_size: int = Query(20, alias='pageSize', ge=1, le=100),
db: Session = Depends(get_db), user: DbUserContext = Depends(current_user)):
return _wire(statistics.list_contracts(db, user, keyword=keyword, page=page, page_size=page_size))
return _wire(statistics.list_contracts(db, user, keyword=keyword, page=page, page_size=page_size, contract_no=contract_no))
@router.get('/statistics/certificates')
def certificate_statistics_list(keyword: str = Query('', max_length=200),
cert_no: str = Query('', alias='certNo', max_length=200),
cert_type: Optional[CertType] = Query(None, alias='certType'),
page: int = Query(1, ge=1),
page_size: int = Query(20, alias='pageSize', ge=1, le=100),
db: Session = Depends(get_db), user: DbUserContext = Depends(current_user)):
return _wire(statistics.list_certificates(db, user, keyword=keyword, cert_type=cert_type,
page=page, page_size=page_size))
page=page, page_size=page_size, cert_no=cert_no))
@router.get('/contracts/{contract_id}/statistics')
+8 -4
View File
@@ -22,7 +22,7 @@ def _contracts(db, user):
user.to_user_info(), db, user.tenant_no)
def _certificates(db, user, keyword=None, cert_type=None):
def _certificates(db, user, keyword=None, cert_type=None, cert_no=None):
queries = []
for kind, model in MODELS.items():
if cert_type and kind != cert_type:
@@ -31,6 +31,8 @@ def _certificates(db, user, keyword=None, cert_type=None):
if keyword:
query = query.filter(or_(model.cert_no.contains(keyword, autoescape=True),
model.product_name_on_cert.contains(keyword, autoescape=True)))
if cert_no:
query = query.filter(model.cert_no.contains(cert_no, autoescape=True))
queries.append(query.with_entities(literal(kind).label('cert_type'), model.id.label('cert_id'),
*[getattr(model, field).label(field) for field in ('cert_no', 'product_name_on_cert',
'quantity_unit', 'total_qty', 'reserved_qty', 'used_qty', 'status',
@@ -131,7 +133,7 @@ def _details(db, user, keys, contract_ids=None):
return groups, usage_items
def list_contracts(db, user, keyword=None, page=1, page_size=20):
def list_contracts(db, user, keyword=None, page=1, page_size=20, contract_no=None):
certs = _certificates(db, user)
linked = _active(db, user, Link).join(certs, and_(Link.cert_type == certs.c.cert_type,
Link.cert_id == certs.c.cert_id)).with_entities(Link.contract_id)
@@ -139,6 +141,8 @@ def list_contracts(db, user, keyword=None, page=1, page_size=20):
if keyword:
query = query.filter(or_(CrmContract.contract_no.contains(keyword, autoescape=True),
CrmContract.contract_name.contains(keyword, autoescape=True)))
if contract_no:
query = query.filter(CrmContract.contract_no.contains(contract_no, autoescape=True))
total = query.count()
rows = query.order_by(CrmContract.create_time.desc(), CrmContract.id).offset((page-1)*page_size).limit(page_size).all()
counts = dict(db.query(Link.contract_id, func.count(Link.id)).join(certs,
@@ -149,8 +153,8 @@ def list_contracts(db, user, keyword=None, page=1, page_size=20):
'total': total, 'page': page, 'pageSize': page_size}
def list_certificates(db, user, keyword=None, cert_type=None, page=1, page_size=20):
certs = _certificates(db, user, keyword, cert_type)
def list_certificates(db, user, keyword=None, cert_type=None, page=1, page_size=20, cert_no=None):
certs = _certificates(db, user, keyword, cert_type, cert_no)
query = db.query(certs)
total = query.count()
rows = query.order_by(certs.c.create_time.desc(), certs.c.cert_type, certs.c.cert_id).offset(
+18 -3
View File
@@ -165,9 +165,24 @@ def test_statistics_routes_are_static_paginated_and_strict(api, monkeypatch):
return {'list': [], 'total': 0, 'page': 2, 'pageSize': 3}
monkeypatch.setattr(stats, 'list_certificates', capture)
result = client.get('/api' + prefix + '/statistics/certificates', params={
'keyword': '硫酸', 'certType': 'purchase', 'page': 2, 'pageSize': 3})
'keyword': '硫酸', 'certNo': 'P', 'certType': 'purchase', 'page': 2, 'pageSize': 3})
assert result.status_code == 200 and result.json()['pageSize'] == 3
assert received == [{'keyword': '硫酸', 'cert_type': 'purchase', 'page': 2, 'page_size': 3}]
for params in ({'page': 0}, {'pageSize': 101}, {'certType': 'bad'}, {'keyword': 'x' * 201}):
assert received == [{'keyword': '硫酸', 'cert_type': 'purchase', 'page': 2, 'page_size': 3, 'cert_no': 'P'}]
for params in ({'page': 0}, {'pageSize': 101}, {'certType': 'bad'}, {'keyword': 'x' * 201}, {'certNo': 'x' * 201}):
assert client.get('/api' + prefix + '/statistics/certificates', params=params).status_code == 422
assert client.get('/api' + prefix + '/contracts/missing/statistics').status_code == 404
def test_contract_statistics_route_forwards_number_and_keyword_independently(api, monkeypatch):
from app.services import pl_quota_statistics as stats
client, app, auth, _ = api
app.dependency_overrides[auth] = lambda: {'role': 'super_admin', 'user_id': 'u', 'tenant_no': 't'}
received = []
def capture(*args, **kwargs):
received.append(kwargs)
return {'list': [], 'total': 0, 'page': 1, 'pageSize': 20}
monkeypatch.setattr(stats, 'list_contracts', capture)
result = client.get('/api/transport/quota-certs/statistics/contracts', params={'keyword': '目标', 'contractNo': 'HT'})
assert result.status_code == 200
assert received[0]['keyword'] == '目标' and received[0]['contract_no'] == 'HT'
assert client.get('/api/transport/quota-certs/statistics/contracts', params={'contractNo': 'x' * 201}).status_code == 422
+11
View File
@@ -235,3 +235,14 @@ def test_usage_party_is_taken_only_from_exact_source_stop_snapshot(case, source_
row = service().certificate_statistics(case.db, case.user, 'purchase', 'same')['usageItems'][0]
assert (row['partyName'], row['address']) == expected
assert item.source_snapshot['stops'][0]['address'] == '关联段地址'
def test_number_filters_are_independent_from_keyword_and_applied_before_pagination(case):
s = service()
case.db.query(CrmContract).filter_by(id='a').one().contract_name = '目标合同'
case.db.commit()
assert s.list_contracts(case.db, case.user, keyword='目标', contract_no='a', page_size=1)['total'] == 1
assert s.list_contracts(case.db, case.user, keyword='目标', contract_no='b')['total'] == 0
assert s.list_contracts(case.db, case.user, contract_no='目标')['total'] == 0
assert s.list_certificates(case.db, case.user, keyword='硫酸', cert_no='P', page_size=1)['total'] == 1
assert s.list_certificates(case.db, case.user, keyword='硫酸', cert_no='missing')['total'] == 0
assert s.list_certificates(case.db, case.user, cert_no='硫酸')['total'] == 0
+16 -1
View File
@@ -126,7 +126,7 @@ function harness(fixed?: string) {
const state = scope.run(() =>
new Function(
...Object.keys(env),
`${code}; return { rows, fixedDetail, details, load, loadDetail, allowed, canEdit }`
`${code}; return { rows, fixedDetail, details, load, loadDetail, allowed, canEdit, keyword, setFilterState }`
)(...Object.values(env))
)
return {
@@ -360,3 +360,18 @@ test('usage trace renders business columns and resolves contract/product by thei
const stopExpression = table.match(/<el-tag size="small">\s*\{\{([\s\S]*?)\}\}/)![1]
assert.equal(new Function('row', `return (${stopExpression})`)(rows[0]), '送货')
})
test('quick search and number header filter are sent as independent conditions', async () => {
const h = harness()
h.permissions[0].resolve({ all: true })
await flush()
h.calls[0].resolve({ list: [], total: 0 })
await flush()
h.state.keyword.value = '目标合同'
h.state.setFilterState('contractNo', { text: 'HT-2026' })
assert.equal(h.calls[1].args[0].keyword, '目标合同')
assert.equal(h.calls[1].args[0].contractNo, 'HT-2026')
h.calls[1].resolve({ list: [], total: 0 })
await flush()
h.stop()
})
+2
View File
@@ -358,6 +358,8 @@ export interface CertificateStatistics extends CertificateStatisticsSummary {
unattributedUsedQty: QuotaQuantity
}
export interface StatisticsQuery {
contractNo?: string
certNo?: string
keyword?: string
page?: number
pageSize?: number
@@ -210,7 +210,7 @@
['validFrom', '有效期起'],
['validTo', '有效期止']
]
).map(([prop, label]) => ({ prop, label, type: 'text', queryKey: 'keyword' }))
).map(([prop, label]) => ({ prop, label, type: 'text', queryKey: prop }))
const { visibleColumns, checkAll, isIndeterminate, resetToDefault } = usePersistedListColumns(
`pl-${props.mode}-statistics-v1`,
columns
@@ -252,7 +252,8 @@
query: {
page: page.value,
pageSize: 20,
keyword: toQueryParams().keyword || keyword.value,
keyword: keyword.value,
...toQueryParams(),
...(props.mode === 'certificate' ? { certType: certType.value } : {})
}
})