141 lines
7.9 KiB
Python
141 lines
7.9 KiB
Python
"""Quick tracking dialogs and responsive cards, using isolated sample orders."""
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
from datetime import date, datetime
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
RUN = Path(tempfile.mkdtemp(prefix='tracking-ui-', dir=ROOT/'tools/out'))
|
|
os.environ['PO_DATA_DIR'] = str(RUN)
|
|
sys.path.insert(0, str(ROOT/'po_app'))
|
|
from app import app, Session
|
|
from models import ApprovalLog, Order, User
|
|
from playwright.sync_api import sync_playwright, expect
|
|
from waitress.server import create_server
|
|
from werkzeug.security import generate_password_hash
|
|
|
|
PASSWORD = 'Isolated-Tracking-2026'
|
|
with Session() as db:
|
|
users = {}
|
|
for name, role in [('采购员', 'maker'), ('审批员', 'approver'), ('管理员', 'admin')]:
|
|
user = User(username=role, name=name, role=role, active=True,
|
|
must_change_password=False, session_version=1,
|
|
password_hash=generate_password_hash(PASSWORD))
|
|
db.add(user)
|
|
db.flush()
|
|
users[role] = user.id
|
|
samples = [
|
|
('博远精密(测试)', 'unpaid', 'pending', 'pending', True),
|
|
('华星电子(测试)', 'partial', 'delivered', 'received', False),
|
|
('恒达机电(测试)', 'paid', 'partial', 'received', False),
|
|
('新力材料(测试)', 'paid', 'delivered', 'received', False),
|
|
('中科配件(测试)', 'unknown', 'unknown', 'unknown', False),
|
|
('宏光模具(测试)', 'paid', 'delivered', 'pending', False),
|
|
]
|
|
for i, (supplier, payment, delivery, invoice, prepay) in enumerate(samples, 1):
|
|
order = Order(order_no='PO20260917-%03d' % i, supplier_name=supplier,
|
|
created_by=users['maker'], created_by_name='采购员',
|
|
status='approved', order_date='2026-09-17', total_amount=1200*i,
|
|
revision=1, approved_by='审批员', approved_at=datetime.now(),
|
|
payment_status=payment, delivery_status=delivery, invoice_status=invoice,
|
|
needs_prepayment=prepay, fulfillment_revision=1, payment_revision=1)
|
|
if i == 4:
|
|
order.fulfillment_revision = 0
|
|
db.add(order)
|
|
db.commit()
|
|
|
|
server = create_server(app, host='127.0.0.1', port=0)
|
|
threading.Thread(target=server.run, daemon=True).start()
|
|
base = 'http://127.0.0.1:%s' % server.effective_port
|
|
errors = []
|
|
try:
|
|
with sync_playwright() as p:
|
|
browser = p.chromium.launch(channel='msedge', headless=True)
|
|
def login(role):
|
|
page = browser.new_page(viewport={'width': 1440, 'height': 1100})
|
|
page.on('pageerror', lambda error: errors.append(str(error)))
|
|
page.goto(base+'/login')
|
|
page.locator('[name=username]').fill(role)
|
|
page.locator('[name=password]').fill(PASSWORD)
|
|
page.get_by_role('button', name='登 录').click()
|
|
page.wait_for_url(base+'/')
|
|
return page
|
|
maker, approver, admin = [login(role) for role in ('maker', 'approver', 'admin')]
|
|
admin.screenshot(path=str(RUN/'desktop.png'), full_page=True)
|
|
assert maker.locator('[data-quick-tracking][href$="/payment"]').count() == 0
|
|
assert approver.locator('[data-quick-tracking][href$="/follow-up"]').count() == 0
|
|
assert admin.locator('.tracking-table tbody tr').first.get_attribute('data-order-id') == '4'
|
|
for width in (320, 390, 768, 1024):
|
|
approver.set_viewport_size({'width': width, 'height': 844})
|
|
for path in ('/', '/orders', '/orders/2/view'):
|
|
approver.goto(base+path)
|
|
assert approver.evaluate('document.documentElement.scrollWidth <= innerWidth'), (width, path)
|
|
if width <= 768 and path != '/orders/2/view':
|
|
assert approver.locator('table.order-cards').evaluate('(el) => el.scrollWidth <= el.clientWidth + 1'), (width, path)
|
|
approver.set_viewport_size({'width': 390, 'height': 844})
|
|
approver.goto(base+'/')
|
|
approver.screenshot(path=str(RUN/'mobile.png'), full_page=True)
|
|
# Inline validation retains user input; a stale form cannot overwrite a newer record.
|
|
approver.locator('tr[data-order-id="2"] [data-quick-tracking]').click()
|
|
dialog = approver.locator('#tracking-dialog')
|
|
expect(dialog.locator('[name=payment_status]')).to_have_value('partial')
|
|
dialog.locator('[name=payment_status]').select_option('unpaid')
|
|
dialog.get_by_role('button', name='保存付款状态').click()
|
|
expect(dialog.locator('#tracking-dialog-error')).to_contain_text('更正原因')
|
|
expect(dialog.locator('[name=payment_status]')).to_have_value('unpaid')
|
|
dialog.locator('[name=payment_status]').select_option('paid')
|
|
dialog.locator('[name=payment_date]').fill(date.today().isoformat())
|
|
dialog.locator('[name=payment_note]').fill('款项已付清')
|
|
# Another authorized user changes the same order while this dialog is open.
|
|
admin.locator('tr[data-order-id="2"] a[href$="/payment"]').click()
|
|
ad = admin.locator('#tracking-dialog')
|
|
ad.locator('[name=payment_date]').fill(date.today().isoformat())
|
|
ad.locator('[name=payment_note]').fill('另一位经办人已核对首款')
|
|
ad.get_by_role('button', name='保存付款状态').click()
|
|
expect(ad).not_to_be_visible()
|
|
dialog.get_by_role('button', name='保存付款状态').click()
|
|
expect(dialog.locator('#tracking-dialog-error')).to_contain_text('已经更新')
|
|
expect(dialog.get_by_role('button', name='保存付款状态')).to_be_disabled()
|
|
dialog.locator('#tracking-reload').click()
|
|
expect(dialog.locator('[name=payment_note]')).to_have_value('另一位经办人已核对首款')
|
|
dialog.locator('[name=payment_status]').select_option('paid')
|
|
dialog.locator('[name=payment_note]').fill('款项已付清')
|
|
approver.screenshot(path=str(RUN/'payment-dialog-mobile.png'))
|
|
dialog.get_by_role('button', name='保存付款状态').click()
|
|
expect(dialog).not_to_be_visible()
|
|
expect(approver.locator('.tracking-table tr[data-order-id="2"]')).to_have_count(0)
|
|
assert approver.url == base+'/'
|
|
# Maker updates delivery and invoice directly from the order list.
|
|
maker.goto(base+'/orders?kw=001')
|
|
maker.locator('a[data-quick-tracking]').click()
|
|
md = maker.locator('#tracking-dialog')
|
|
md.locator('[name=invoice_status]').select_option('received')
|
|
md.locator('[name=delivery_status]').select_option('partial')
|
|
md.locator('[name=fulfillment_note]').fill('首批货已到,剩余预计周五交齐。')
|
|
md.get_by_role('button', name='保存采购跟进').click()
|
|
expect(md).not_to_be_visible()
|
|
assert maker.url == base+'/orders?kw=001'
|
|
expect(maker.locator('[data-progress=delivery] [aria-current=step]')).to_have_text('部分交货')
|
|
maker.goto(base+'/orders/1/view')
|
|
expect(maker.locator('#order-tracking')).to_contain_text('最近跟进:')
|
|
expect(maker.locator('#order-tracking')).to_contain_text('首批货已到,剩余预计周五交齐。')
|
|
maker.locator('#order-tracking').screenshot(path=str(RUN/'detail.png'))
|
|
# A standard page remains usable when JavaScript is unavailable.
|
|
fallback = browser.new_context(java_script_enabled=False)
|
|
fallback.add_cookies(maker.context.cookies())
|
|
tab = fallback.new_page()
|
|
tab.goto(base+'/orders/1/tracking/follow-up')
|
|
assert tab.locator('#follow-up-form').count() == 1
|
|
assert not errors, errors
|
|
browser.close()
|
|
with Session() as db:
|
|
assert db.get(Order, 2).payment_status == 'paid'
|
|
assert db.get(Order, 1).delivery_status == 'partial'
|
|
assert db.query(ApprovalLog).filter_by(user_name='采购员', action='更新采购跟进').count() == 1
|
|
print(json.dumps({'result': 'PASS', 'output': str(RUN), 'js_errors': errors}, ensure_ascii=False))
|
|
finally:
|
|
server.close()
|