fix(pl): 独立组合统计编号筛选与关键词搜索以避免条件被忽略。
This commit is contained in:
1 parent
da56a1e319
commit
726575aaf2
7 files changed
+62
-12
No files matched your search
@@ -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')
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
})
|
||||
@@ -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 } : {})
|
||||
}
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user