Files

223 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import io
import re
import secrets
from datetime import date, timedelta
import openpyxl
import pytest
from conftest import post
from test_workflow import action, approve, new, order, pending, save
def approved(app, clients):
oid = pending(app, clients)
approve(app, clients, oid)
return oid
def follow(app, clients, oid, actor='maker', **values):
fields = {'needs_prepayment': '1', 'invoice_status': 'received', 'delivery_status': 'partial',
'fulfillment_note': '已收第一批货'}
fields.update(values)
return action(app, clients[actor], oid, 'follow-up', **fields)
def pay(app, clients, oid, actor='approver', **values):
fields = {'payment_status': 'paid', 'payment_date': date.today().isoformat(), 'payment_note': '银行转账'}
fields.update(values)
return action(app, clients[actor], oid, 'payment', **fields)
def contains(client, key, number):
response = client.get('/orders?tracking=' + key)
assert response.status_code == 200
return number in response.get_data(as_text=True)
def test_follow_payment_dashboard_and_archived_pdf(app, clients):
oid = approved(app, clients)
pdf = clients['maker'].get('/orders/%s/pdf' % oid).data
assert follow(app, clients, oid).status_code == 302
with app.Session() as db:
number = db.get(app.Order, oid).order_no
for key in ('prepayment', 'invoice_due', 'partial_delivery', 'unpaid'):
assert contains(clients['maker'], key, number)
assert not contains(clients['other'], 'invoice_due', number)
assert pay(app, clients, oid, payment_status='partial').status_code == 302
assert contains(clients['admin'], 'partial_payment', number)
assert contains(clients['admin'], 'invoice_due', number)
assert pay(app, clients, oid, actor='admin').status_code == 302
assert not contains(clients['maker'], 'invoice_due', number)
assert not contains(clients['maker'], 'prepayment', number)
assert contains(clients['maker'], 'paid_undelivered', number)
assert follow(app, clients, oid, delivery_status='delivered').status_code == 302
assert contains(clients['approver'], 'completed', number)
assert not contains(clients['approver'], 'paid_undelivered', number)
assert clients['maker'].get('/orders/%s/pdf' % oid).data == pdf
dashboard = clients['admin'].get('/').get_data(as_text=True)
assert re.search(r'data-tracking="closed"[^>]*>查看已全部完成(1)', dashboard)
other_dashboard = clients['other'].get('/').get_data(as_text=True)
assert number not in other_dashboard
with app.Session() as db:
logs = db.get(app.Order, oid).logs
assert any(lg.user_name == 'maker' and '部分交货' in (lg.comment or '') for lg in logs)
assert any(lg.user_name == 'admin' and '部分付款 → 已付款' in (lg.comment or '') for lg in logs)
workbook = openpyxl.load_workbook(io.BytesIO(clients['admin'].get('/export/ledger').data))
headers, values = list(workbook.active.values)
row = dict(zip(headers, values))
assert row['付款状态'] == '已付款' and row['交货状态'] == '已交货' and row['收票状态'] == '已收票'
workbook.close()
@pytest.mark.parametrize('actor,endpoint,status', [
('maker', 'payment', 403), ('other', 'payment', 404),
('other', 'follow-up', 404), ('approver', 'follow-up', 403),
])
def test_tracking_role_and_order_scope(app, clients, actor, endpoint, status):
oid = approved(app, clients)
function = pay if endpoint == 'payment' else follow
assert function(app, clients, oid, actor=actor).status_code == status
with app.Session() as db:
row = db.get(app.Order, oid)
assert row.payment_status == 'unpaid' and row.invoice_status == 'pending'
def test_tracking_rejects_forged_fields_csrf_and_stale_page(app, clients):
oid = approved(app, clients)
version = order(app, oid)['version']
assert follow(app, clients, oid, payment_status='paid').status_code == 403
assert pay(app, clients, oid, delivery_status='delivered').status_code == 403
assert clients['approver'].post('/orders/%s/payment' % oid, data={'payment_status': 'paid'}).status_code == 400
assert follow(app, clients, oid).status_code == 302
assert post(clients['approver'], '/orders/%s/payment' % oid,
{'row_version': str(version), 'payment_status': 'paid', 'payment_date': date.today().isoformat()}).status_code == 409
with app.Session() as db:
assert db.get(app.Order, oid).payment_status == 'unpaid'
def test_tracking_requires_approval_and_valid_input(app, clients):
oid = new(app, clients['maker'])
assert follow(app, clients, oid).status_code == 403
assert pay(app, clients, oid).status_code == 403
assert save(app, clients['maker'], oid, intent='submit').status_code == 302
assert pay(app, clients, oid).status_code == 403
approve(app, clients, oid)
assert pay(app, clients, oid, payment_date='').status_code == 400
assert pay(app, clients, oid, payment_date=(date.today()+timedelta(days=1)).isoformat()).status_code == 400
assert pay(app, clients, oid, payment_status='invalid').status_code == 400
assert follow(app, clients, oid, delivery_status='invalid').status_code == 400
assert follow(app, clients, oid, fulfillment_note='x'*501).status_code == 400
assert action(app, clients['admin'], oid, 'void', comment='取消采购').status_code == 302
assert pay(app, clients, oid).status_code == 403
assert follow(app, clients, oid).status_code == 403
def test_corrections_require_reason_and_keep_audit(app, clients):
oid = approved(app, clients)
assert pay(app, clients, oid).status_code == 302
assert pay(app, clients, oid, payment_status='unpaid').status_code == 400
assert pay(app, clients, oid, payment_status='unpaid', reason='误登记,实际尚未转账').status_code == 302
assert follow(app, clients, oid, delivery_status='delivered').status_code == 302
assert follow(app, clients, oid, invoice_status='pending').status_code == 400
assert follow(app, clients, oid, invoice_status='pending', delivery_status='pending', reason='收票和收货录入有误').status_code == 302
with app.Session() as db:
row = db.get(app.Order, oid)
assert row.payment_status == 'unpaid' and row.payment_date == ''
assert row.delivery_status == row.invoice_status == 'pending'
assert any('误登记,实际尚未转账' in (log.comment or '') for log in row.logs)
def test_revision_requires_each_role_to_recheck_and_copy_starts_fresh(app, clients):
oid = approved(app, clients)
follow(app, clients, oid, delivery_status='delivered')
pay(app, clients, oid)
with app.Session() as db:
number = db.get(app.Order, oid).order_no
assert action(app, clients['maker'], oid, 'revise', comment='调整数量').status_code == 302
assert save(app, clients['maker'], oid, qty='5', intent='submit').status_code == 302
approve(app, clients, oid)
assert contains(clients['admin'], 'recheck', number)
assert not contains(clients['admin'], 'completed', number)
assert '订单内容已变更' in clients['maker'].get('/orders/%s/view' % oid).get_data(as_text=True)
assert follow(app, clients, oid, delivery_status='delivered').status_code == 302
assert contains(clients['admin'], 'recheck', number)
assert pay(app, clients, oid).status_code == 302
assert not contains(clients['admin'], 'recheck', number)
assert contains(clients['admin'], 'completed', number)
copied = action(app, clients['maker'], oid, 'copy', request_key=secrets.token_hex(16))
assert copied.status_code == 302
copy_id = int(re.search(r'/orders/(\d+)', copied.location)[1])
with app.Session() as db:
clone = db.get(app.Order, copy_id)
assert not clone.needs_prepayment
assert clone.invoice_status == clone.delivery_status == 'pending'
assert clone.payment_status == 'unpaid' and clone.payment_date == ''
def test_admin_can_follow_and_pay_and_invalid_filter_is_rejected(app, clients):
oid = approved(app, clients)
assert follow(app, clients, oid, actor='admin').status_code == 302
assert pay(app, clients, oid, actor='admin').status_code == 302
assert clients['admin'].get('/orders?tracking=invalid').status_code == 400
def test_action_queue_keeps_old_open_orders_and_unreceived_invoices(app, clients):
with app.Session() as db:
maker = db.query(app.User).filter_by(username='maker').one()
for i in range(16):
db.add(app.Order(order_no='QUEUE-%02d' % i, created_by=maker.id,
supplier_name='待办测试', status='approved', revision=1,
payment_status='paid', delivery_status='delivered',
invoice_status='received', fulfillment_revision=1, payment_revision=1))
db.flush()
rows = db.query(app.Order).order_by(app.Order.id).all()
rows[0].payment_status = 'unpaid'
rows[0].needs_prepayment = True
rows[1].invoice_status = 'pending'
rows[2].payment_revision = 0
rows[3].payment_status = 'unknown'
db.commit()
html = clients['maker'].get('/').get_data(as_text=True)
board = html.split('<table class="grid tracking-table order-cards">')[1].split('</table>')[0]
assert all('QUEUE-%02d' % i in board for i in range(4))
assert 'QUEUE-15' not in board
assert board.index('QUEUE-02') < board.index('QUEUE-00')
assert '跟进收票' in board and '变更前记录 · 待核对' in board
assert 'QUEUE-15' not in clients['maker'].get('/orders').get_data(as_text=True)
assert 'QUEUE-15' in clients['maker'].get('/orders?show_completed=1').get_data(as_text=True)
assert 'QUEUE-15' in clients['maker'].get('/orders?tracking=closed').get_data(as_text=True)
assert 'QUEUE-00' not in clients['other'].get('/').get_data(as_text=True)
assert clients['admin'].get('/?tracking=invalid').status_code == 400
@pytest.mark.parametrize('actor,kind,status', [
('maker', 'payment', 403), ('approver', 'follow-up', 403),
('other', 'follow-up', 404), ('maker', 'follow-up', 200),
('approver', 'payment', 200), ('admin', 'follow-up', 200),
])
def test_quick_editor_checks_permissions(app, clients, actor, kind, status):
oid = approved(app, clients)
for suffix in ('', '?fragment=1'):
assert clients[actor].get('/orders/%s/tracking/%s%s' % (oid, kind, suffix)).status_code == status
def test_quick_payment_preserves_concurrency_archive_and_latest_actor(app, clients):
oid = approved(app, clients)
pdf = clients['maker'].get('/orders/%s/pdf' % oid).data
version = order(app, oid)['version']
values = {'row_version': str(version), 'payment_status': 'partial',
'payment_date': date.today().isoformat(), 'payment_note': '已付首款,尾款待交齐'}
response = post(clients['approver'], '/orders/%s/payment' % oid, values,
headers={'X-Tracking-Modal': '1'})
assert response.status_code == 200 and response.json == {'ok': True}
stale = post(clients['approver'], '/orders/%s/payment' % oid, values,
headers={'X-Tracking-Modal': '1'})
assert stale.status_code == 409
assert follow(app, clients, oid).status_code == 302
html = clients['maker'].get('/orders/%s/view' % oid).get_data(as_text=True)
assert '最近登记:' in html and '最近跟进:' in html
assert '已付首款,尾款待交齐' in html and '已收第一批货' in html
assert html.index('付款与交货跟进') < html.index('单据信息')
assert clients['maker'].get('/orders/%s/pdf' % oid).data == pdf