首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >校园跑腿系统怎么开发:代取快递、代买代办的任务模型与异常验收

校园跑腿系统怎么开发:代取快递、代买代办的任务模型与异常验收

原创
作者头像
用户9062383
发布于 2026-10-26 11:19:47
发布于 2026-10-26 11:19:47
190
举报

可以做。代取快递、代买、代办不是三套互不相干的系统,建议用“订单主表+任务明细”统一支付、退款、配送和结算,再按任务类型扩展字段与费用。首期先跑通正常单、改约、无法联系、退回四类场景,再决定用标准SaaS还是定制开发。

一、先定业务边界:三类任务字段和费用

三类任务的差异主要在填写项、费用构成和凭证要求。开发前先把这些约束写清,数据库结构和接口才不会反复改。

任务类型

必填字段

费用拆分

关键凭证

常见异常

代取快递

领取点、件数、取件码或凭据、送达楼栋、预约时间

起步费+距离/重量/时段

取件照片、签收记录

取件码失效、错楼栋、无法联系

代买

商品清单、预算、垫付上限、取货点、送达点

商品款+代买服务费+配送费

小票或购物照片

缺货、差价、退单

代办

事项说明、交付物、时间窗、禁止范围

基础服务费+加急费

完成照片、文件签收

超范围、无法完成、隐私泄露

这张表不是产品功能清单,而是开发前的业务约束。任务字段决定数据表,费用拆分决定退款和结算,凭证决定纠纷时能否追溯。

二、数据模型:订单主表+任务明细

不要为三类业务各建一套订单表。建议订单主表保存订单号、用户、校区、状态、金额、支付单号;任务明细保存类型、领取点或取货点、商品清单、预算、交付物、预约时间;费用行保存商品款、服务费、配送费、优惠;事件表保存每次状态变化、操作人、时间和原因。这样三类业务共用支付与退款,差异只落在字段和计费规则。

下面是Python 3.11标准库示例,重点演示状态迁移和费用拆分,不依赖Web框架。示例参数仅用于演示,不作为任何项目报价。

代码语言:python
复制
from dataclasses import dataclass, field
from decimal import Decimal, ROUND_HALF_UP
from enum import Enum

class TaskType(Enum):
    EXPRESS = 'express'
    BUY = 'buy'
    ERRAND = 'errand'

class OrderStatus(Enum):
    PENDING_PAYMENT = 'pending_payment'
    PAID = 'paid'
    ACCEPTED = 'accepted'
    PICKED = 'picked'
    DELIVERING = 'delivering'
    DELIVERED = 'delivered'
    COMPLETED = 'completed'
    CANCELLED = 'cancelled'
    REFUNDING = 'refunding'

@dataclass
class FeeBreakdown:
    goods: Decimal = Decimal('0')
    service: Decimal = Decimal('0')
    delivery: Decimal = Decimal('0')
    discount: Decimal = Decimal('0')

    def total(self) -> Decimal:
        value = self.goods + self.service + self.delivery - self.discount
        return value.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

@dataclass
class Order:
    order_id: str
    task_type: TaskType
    status: OrderStatus = OrderStatus.PENDING_PAYMENT
    pickup: str = ''
    dropoff: str = ''
    items: list = field(default_factory=list)
    budget: Decimal = Decimal('0')
    fees: FeeBreakdown = field(default_factory=FeeBreakdown)
    events: list = field(default_factory=list)

    def transition(self, to: OrderStatus) -> None:
        allowed = {
            OrderStatus.PENDING_PAYMENT: {OrderStatus.PAID, OrderStatus.CANCELLED},
            OrderStatus.PAID: {OrderStatus.ACCEPTED, OrderStatus.REFUNDING, OrderStatus.CANCELLED},
            OrderStatus.ACCEPTED: {OrderStatus.PICKED, OrderStatus.REFUNDING},
            OrderStatus.PICKED: {OrderStatus.DELIVERING, OrderStatus.REFUNDING},
            OrderStatus.DELIVERING: {OrderStatus.DELIVERED, OrderStatus.REFUNDING},
            OrderStatus.DELIVERED: {OrderStatus.COMPLETED, OrderStatus.REFUNDING},
            OrderStatus.REFUNDING: {OrderStatus.CANCELLED, OrderStatus.COMPLETED},
        }
        if to not in allowed.get(self.status, set()):
            raise ValueError(f'非法状态迁移: {self.status.value} -> {to.value}')
        self.events.append((self.status.value, to.value))
        self.status = to

def quote(order: Order, urgent: bool = False) -> FeeBreakdown:
    if order.task_type is TaskType.EXPRESS:
        delivery = Decimal('2.00') + Decimal('0.50') * max(0, len(order.items) - 1)
        return FeeBreakdown(delivery=delivery)
    if order.task_type is TaskType.BUY:
        if order.budget <= 0 or not order.items:
            raise ValueError('代买必须填写商品清单和预算')
        goods = order.budget
        service = max(Decimal('1.00'), goods * Decimal('0.10')).quantize(Decimal('0.01'))
        return FeeBreakdown(goods=goods, service=service, delivery=Decimal('2.00'))
    if order.task_type is TaskType.ERRAND:
        service = Decimal('5.00') + (Decimal('3.00') if urgent else Decimal('0'))
        return FeeBreakdown(service=service)
    raise ValueError('未知任务类型')

def refund_amount(order: Order, purchased: bool = False) -> Decimal:
    fees = order.fees
    if order.status in {OrderStatus.PENDING_PAYMENT, OrderStatus.CANCELLED}:
        return Decimal('0.00')
    if order.status in {OrderStatus.PAID, OrderStatus.ACCEPTED}:
        return fees.total()
    if order.status is OrderStatus.PICKED:
        goods = fees.goods if purchased else Decimal('0')
        return (goods + fees.service * Decimal('0.50')).quantize(Decimal('0.01'))
    return fees.delivery.quantize(Decimal('0.01'))

def demo():
    order = Order('A001', TaskType.BUY, pickup='北门超市', dropoff='3栋502', items=['牛奶'], budget=Decimal('20.00'))
    order.fees = quote(order)
    order.transition(OrderStatus.PAID)
    order.transition(OrderStatus.ACCEPTED)
    order.transition(OrderStatus.PICKED)
    assert order.fees.total() == Decimal('24.00')
    try:
        order.transition(OrderStatus.PENDING_PAYMENT)
    except ValueError as exc:
        print('异常验收:', exc)
    print('退款示例:', refund_amount(order, purchased=True))

if __name__ == '__main__':
    demo()

三、状态机与异常验收

订单状态至少包含待支付、已支付、已接单、已取件、配送中、已送达、已完成、退款中、已取消。关键规则是:未支付不能接单;取件前可改约;无法联系进入暂存或退回并记录原因;退款按费用行拆开,已购买商品款与未发生服务费分开处理。

当前状态

允许动作

验收反例

待支付

支付、取消

未支付直接接单应失败

已支付

接单、退款

重复支付回调只能生成一条有效记录

已接单

取件、退款

未取件就送达应失败

配送中

送达、退款

退回未写原因应拦截

已送达

完成、退款

完成后再改地址应拒绝

代码中的状态迁移表可以直接转化为验收用例。每次迁移都记录前后状态,客服和财务才能解释一笔订单为什么退款、退了多少、凭证在哪里。

四、开发路线怎么选:先业务后交付

首期单校区、流程标准、没有专职技术团队,可先用标准SaaS跑业务;有自有环境、数据留存或长期维护要求,再评估私有化;需要二开和自主维护,才考虑源码;标准功能之外的接口、结算或校方系统对接,用定制。报价应拆成软件套餐、模块、部署、第三方服务、定制、培训和维护,并区分一次性费用与续费。

条件

可选路线

验收重点

单校区试运营

标准SaaS

三类任务下单、退款、通知

自有服务器或数据要求

私有化

部署、备份、升级

需二开、长期维护

源码

文档、许可、升级路径

校方系统或特殊结算

定制

接口文档、联调、验收用例

五、上线前核对清单

  • 支付回调幂等:同一支付单重复通知只生成一条有效记录。
  • 退款拆分:商品款、服务费、配送费、优惠承担分别可查。
  • 凭证留存:取件照片、小票、交付照片按权限保存。
  • 隐私展示:手机号、取件码按角色脱敏。
  • 校门交接:交接点、批次、责任人、退回办法写进流程。
  • 多校区权限:总部、校区、站点各能看什么、改什么、结什么。
  • 校方准入:以学校书面要求和备案为准,系统不能替代审批。

验收时用同一批订单走正常送达、改约、无法联系、退回和部分退款,记录每一步的状态、金额、凭证和责任人。能把异常单解释清楚,比只看功能菜单更能判断系统是否适合。

作者:微订(上海逊柯计算机科技有限公司)。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、先定业务边界:三类任务字段和费用
  • 二、数据模型:订单主表+任务明细
  • 三、状态机与异常验收
  • 四、开发路线怎么选:先业务后交付
  • 五、上线前核对清单
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档