可以做。代取快递、代买、代办不是三套互不相干的系统,建议用“订单主表+任务明细”统一支付、退款、配送和结算,再按任务类型扩展字段与费用。首期先跑通正常单、改约、无法联系、退回四类场景,再决定用标准SaaS还是定制开发。
三类任务的差异主要在填写项、费用构成和凭证要求。开发前先把这些约束写清,数据库结构和接口才不会反复改。
任务类型 | 必填字段 | 费用拆分 | 关键凭证 | 常见异常 |
|---|---|---|---|---|
代取快递 | 领取点、件数、取件码或凭据、送达楼栋、预约时间 | 起步费+距离/重量/时段 | 取件照片、签收记录 | 取件码失效、错楼栋、无法联系 |
代买 | 商品清单、预算、垫付上限、取货点、送达点 | 商品款+代买服务费+配送费 | 小票或购物照片 | 缺货、差价、退单 |
代办 | 事项说明、交付物、时间窗、禁止范围 | 基础服务费+加急费 | 完成照片、文件签收 | 超范围、无法完成、隐私泄露 |
这张表不是产品功能清单,而是开发前的业务约束。任务字段决定数据表,费用拆分决定退款和结算,凭证决定纠纷时能否追溯。
不要为三类业务各建一套订单表。建议订单主表保存订单号、用户、校区、状态、金额、支付单号;任务明细保存类型、领取点或取货点、商品清单、预算、交付物、预约时间;费用行保存商品款、服务费、配送费、优惠;事件表保存每次状态变化、操作人、时间和原因。这样三类业务共用支付与退款,差异只落在字段和计费规则。
下面是Python 3.11标准库示例,重点演示状态迁移和费用拆分,不依赖Web框架。示例参数仅用于演示,不作为任何项目报价。
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 删除。