"""Internal order follow-up; approved documents stay frozen.""" from datetime import date from flask import abort, flash, redirect, request, url_for from sqlalchemy import and_, or_ from models import DELIVERY_STATUS, INVOICE_STATUS, PAYMENT_STATUS, Order from order_logic import add_log, get_order, normalize_date from security import current_user, db, login_required TRACKING_FILTERS = { 'prepayment': '需预付款', 'invoice_due': '已收票待付款', 'unpaid': '未付款', 'partial_payment': '部分付款', 'paid': '已付款', 'undelivered': '未交货', 'partial_delivery': '部分交货', 'delivered': '已交货', 'paid_undelivered': '已付款未交齐', 'completed': '已付款且已交货', 'unrecorded': '跟进待登记', 'recheck': '变更后待核对', } def tracking_conditions(): current_fulfillment = or_(Order.fulfillment_revision < 0, Order.fulfillment_revision == Order.revision) current_payment = or_(Order.payment_revision < 0, Order.payment_revision == Order.revision) payment_due = and_(current_payment, Order.payment_status.in_(('unpaid', 'partial'))) delivery_open = and_(current_fulfillment, Order.delivery_status.in_(('pending', 'partial'))) return { 'prepayment': and_(current_fulfillment, Order.needs_prepayment.is_(True), payment_due), 'invoice_due': and_(current_fulfillment, Order.invoice_status == 'received', payment_due), 'unpaid': and_(current_payment, Order.payment_status == 'unpaid'), 'partial_payment': and_(current_payment, Order.payment_status == 'partial'), 'paid': and_(current_payment, Order.payment_status == 'paid'), 'undelivered': and_(current_fulfillment, Order.delivery_status == 'pending'), 'partial_delivery': and_(current_fulfillment, Order.delivery_status == 'partial'), 'delivered': and_(current_fulfillment, Order.delivery_status == 'delivered'), 'paid_undelivered': and_(current_payment, Order.payment_status == 'paid', delivery_open), 'completed': and_(current_payment, current_fulfillment, Order.payment_status == 'paid', Order.delivery_status == 'delivered'), 'unrecorded': or_(Order.invoice_status == 'unknown', Order.delivery_status == 'unknown', Order.payment_status == 'unknown'), 'recheck': or_(~current_fulfillment, ~current_payment), } def filter_tracking(query, key): if not key: return query conditions = tracking_conditions() if key not in conditions: abort(400, description='无效的订单跟进筛选条件') return query.filter(Order.status == 'approved', conditions[key]) def can_follow(order, user): return (order.status == 'approved' and user.role in ('maker', 'admin') and (user.role == 'admin' or order.created_by == user.id)) def can_record_payment(order, user): return order.status == 'approved' and user.role in ('approver', 'admin') def enum_value(form, key, choices): value = form.get(key, '') if value not in choices: raise ValueError('请选择有效的跟进状态') return value def note_value(form, key): value = form.get(key, '').strip() if len(value) > 500: raise ValueError('备注最多填写 500 个字符') return value def register_tracking_routes(app): @app.context_processor def tracking_context(): return {'INVOICE_STATUS': INVOICE_STATUS, 'DELIVERY_STATUS': DELIVERY_STATUS, 'PAYMENT_STATUS': PAYMENT_STATUS, 'TRACKING_FILTERS': TRACKING_FILTERS} @app.post('/orders//follow-up') @login_required def order_follow_up(oid): order, user = get_order(oid, mutation=True), current_user() if not can_follow(order, user): abort(403) if set(request.form).intersection(('payment_status', 'payment_date', 'payment_note')): abort(403, description='付款状态须由审批员或管理员登记') invoice = enum_value(request.form, 'invoice_status', INVOICE_STATUS) delivery = enum_value(request.form, 'delivery_status', DELIVERY_STATUS) prepayment = request.form.get('needs_prepayment', '0') if prepayment not in ('0', '1'): raise ValueError('预付款标记无效') note = note_value(request.form, 'fulfillment_note') reason = note_value(request.form, 'reason') if ((order.invoice_status == 'received' and invoice != 'received') or (order.delivery_status in ('partial', 'delivered') and list(DELIVERY_STATUS).index(delivery) < list(DELIVERY_STATUS).index(order.delivery_status))): if not reason: raise ValueError('回退收票或交货状态时,请填写更正原因') changes = [] values = {'needs_prepayment': prepayment == '1', 'invoice_status': invoice, 'delivery_status': delivery, 'fulfillment_note': note} labels = {'needs_prepayment': ('预付款', {True: '需要', False: '不需要'}), 'invoice_status': ('收票', INVOICE_STATUS), 'delivery_status': ('交货', DELIVERY_STATUS)} for key, value in values.items(): old = getattr(order, key) if old != value: label, choices = labels.get(key, ('跟进备注', {})) changes.append('%s:%s → %s' % (label, choices.get(old, old or '空'), choices.get(value, value or '空'))) setattr(order, key, value) if order.fulfillment_stale: changes.append('已核对本次变更后的采购跟进状态') if changes or order.fulfillment_revision != order.revision: order.fulfillment_revision = order.revision add_log(order, '更新采购跟进', user, ';'.join(changes + (['更正原因:' + reason] if reason else []))) db.commit() flash('采购跟进已保存', 'ok') return redirect(url_for('order_view', oid=oid) + '#order-tracking') @app.post('/orders//payment') @login_required def order_payment(oid): order, user = get_order(oid, mutation=True), current_user() if not can_record_payment(order, user): abort(403) if set(request.form).intersection(('needs_prepayment', 'invoice_status', 'delivery_status', 'fulfillment_note')): abort(403, description='采购跟进须在对应表单中登记') status = enum_value(request.form, 'payment_status', PAYMENT_STATUS) paid_date = normalize_date(request.form.get('payment_date'), '付款日期', required=status in ('partial', 'paid')) if paid_date and paid_date > date.today().isoformat(): raise ValueError('实际付款日期不能晚于今天') if status in ('unpaid', 'unknown'): paid_date = '' note = note_value(request.form, 'payment_note') reason = note_value(request.form, 'reason') if (order.payment_status in ('partial', 'paid') and list(PAYMENT_STATUS).index(status) < list(PAYMENT_STATUS).index(order.payment_status) and not reason): raise ValueError('回退付款状态时,请填写更正原因') changes = [] for key, value, label in [('payment_status', status, '付款'), ('payment_date', paid_date, '付款日期'), ('payment_note', note, '付款备注')]: old = getattr(order, key) if old != value: choices = PAYMENT_STATUS if key == 'payment_status' else {} changes.append('%s:%s → %s' % (label, choices.get(old, old or '空'), choices.get(value, value or '空'))) setattr(order, key, value) if order.payment_stale: changes.append('已核对本次变更后的付款状态') if changes or order.payment_revision != order.revision: order.payment_revision = order.revision add_log(order, '登记付款状态', user, ';'.join(changes + (['更正原因:' + reason] if reason else []))) db.commit() flash('付款状态已保存', 'ok') return redirect(url_for('order_view', oid=oid) + '#order-tracking')