feat(order): 下单支持一口价/师傅报价竞价/用户自主定价三种方式

- 下单页新增计价方式选择;自主定价填写总价,师傅报价单进入报价大厅
- 用户订单详情展示师傅报价列表并可选定师傅;选定后同步师傅、金额与订单状态
- 师傅大厅新增用户定价筛选与按价接单;报价单禁止直接抢单
- 后端订单新增 custom 价态、金额口径统一由 calcAmount 输出
- 报价中状态纳入用户可取消范围;超时回流重新开放报价
- 新增 fa_order.price_type custom 枚举迁移脚本(schema_phase6)
这个提交包含在:
linda
2026-10-28 17:48:23 +08:00
父节点 3bba2d6e94
当前提交 e1bf139c5a
共有 11 个文件被更改,包括 500 次插入 和 50 次删除

查看文件

@@ -98,6 +98,7 @@ class Home extends Base
/**
* 费用试算(基础价 + 楼层费 + 加急费 - 优惠)
* 支持下单页实时切换:一口价 / 师傅报价 / 用户自主定价
*/
public function calc()
{
@@ -110,28 +111,48 @@ class Home extends Base
'floor' => (int)$this->request->param('floor', 1),
'has_elevator' => (int)$this->request->param('has_elevator', 1),
'urgent' => (int)$this->request->param('urgent', 0),
], (int)$this->request->param('coupon_id', 0), $this->uid());
], (int)$this->request->param('coupon_id', 0), $this->uid(),
(string)$this->request->param('price_type', ''),
(float)$this->request->param('user_price', 0));
$this->success('', $result);
}
/**
* 金额计算(下单与试算共用,保证前后端口径一致)
*
* @param array $service 服务行
* @param array $params floor / has_elevator / urgent
* @param int $couponId 用户优惠券 id
* @param int $uid 用户 id
* @param string $priceType 计价方式:fixed 一口价 / quote 师傅报价 / custom 用户自主定价
* @param float $userPrice 用户自主定价金额(总价,含楼层/加急)
*/
public static function calcAmount($service, $params, $couponId = 0, $uid = 0)
public static function calcAmount($service, $params, $couponId = 0, $uid = 0, $priceType = '', $userPrice = 0)
{
$allowTypes = ['fixed', 'quote', 'range', 'custom'];
$type = in_array($priceType, $allowTypes, true) ? $priceType : $service['price_type'];
$base = (float)$service['base_price'];
$floor = max(0, (int)$params['floor']);
$freeFloor = (int)$service['free_floor'];
$floorFee = (float)$service['floor_fee'];
$floorAmount = 0;
if ((int)$params['has_elevator'] === 0 && $floor > $freeFloor) {
$floorAmount = ($floor - $freeFloor) * $floorFee;
}
$urgentAmount = 0;
$urgentAmount = (int)$params['urgent'] === 1 ? round($base * 0.2, 2) : 0;
$total = round($base + $floorAmount + $urgentAmount, 2);
if ($type === 'custom') {
// 用户自主定价:出价即订单总价,已包含楼层费/加急费,师傅按此价接单
$base = max(0, round((float)$userPrice, 2));
$total = $base;
} else {
$floor = max(0, (int)$params['floor']);
$freeFloor = (int)$service['free_floor'];
$floorFee = (float)$service['floor_fee'];
if ((int)$params['has_elevator'] === 0 && $floor > $freeFloor) {
$floorAmount = ($floor - $freeFloor) * $floorFee;
}
$urgentAmount = (int)$params['urgent'] === 1 ? round($base * 0.2, 2) : 0;
$total = round($base + $floorAmount + $urgentAmount, 2);
}
$discount = 0;
$couponInfo = null;
@@ -151,7 +172,8 @@ class Home extends Base
}
return [
'price_type' => $service['price_type'],
'price_type' => $type,
'user_price' => $type === 'custom' ? round($base, 2) : 0,
'base_amount' => round($base, 2),
'floor_amount' => round($floorAmount, 2),
'urgent_amount' => round($urgentAmount, 2),
@@ -159,8 +181,8 @@ class Home extends Base
'discount_amount' => round($discount, 2),
'pay_amount' => max(0, round($total - $discount, 2)),
'coupon' => $couponInfo,
'free_floor' => $freeFloor,
'floor_fee' => round($floorFee, 2),
'free_floor' => $type === 'custom' ? 0 : (int)$service['free_floor'],
'floor_fee' => $type === 'custom' ? 0 : round((float)$service['floor_fee'], 2),
];
}