'已关闭', self::STATUS_BIDDING => '招标中', self::STATUS_SIGNING => '签约中', self::STATUS_WORKING => '工作中', self::STATUS_ACCEPTING => '验收中', self::STATUS_CONFIRM_RECEIPT => '待收货', self::STATUS_FINISHED => '已完成', self::STATUS_CANCEL => '已取消', ]; const PUBLISH_STATUS_MAP = [ CommonService::NO => '', CommonService::ONGOING => '审核中', CommonService::SUCCESS => '已发布', CommonService::FAILED => '发布失败', ]; const WORK_STATUS_MAP = [ CommonService::NO => '', CommonService::ONGOING => '工作中', CommonService::SUCCESS => '工作完成', CommonService::FAILED => '工作失败', ]; const ACCEPT_STATUS_MAP = [ CommonService::NO => '', CommonService::ONGOING => '验收中', CommonService::SUCCESS => '验收通过', CommonService::FAILED => '验收失败', ]; const SETTLE_STATUS_MAP = [ CommonService::NO => '', CommonService::ONGOING => '结算中', CommonService::SUCCESS => '结算完成', CommonService::FAILED => '结算失败', ]; const BUTTON_CONFIRM = 'confirm';//确认接单 const BUTTON_CONTRACT_DEV = 'contractDev'; //开发者签约 const BUTTON_CONTRACT_CUSTOMER = 'contractCustomer'; //客户签约 const BUTTON_COMPLETE_WORK = 'completeWork'; //完成工作 const BUTTON_ACCEPT_PASSED = 'acceptPassed'; //验收通过 const BUTTON_CONFIRM_RECEIPT = 'confirmReceipt'; //确认收货 /** * 创建需求 * * @param array $data 需求数据 * @param int $userId 用户ID * @return bool * @throws Exception */ public static function create($data, $userId) { $saveData = [ 'type' => 0, 'user_id' => $userId, 'parent_order_no' => generate_order_sn(), 'title' => mb_substr($data['desc'], 0, 16), 'desc' => $data['desc'], 'develop_type' => $data['develop_type'] ?? '', 'contact' => $data['contact'], 'images' => $data['images'] ?? [], 'files' => $data['files'] ?? [], 'budget_amount' => $data['budget_amount'] ?? 0, 'limit_time' => $data['limit_time'] ?? 0, 'publish_status' => CommonService::ONGOING, 'status' => self::STATUS_NO ]; $result = self::$Model::create($saveData); if (!$result) { throw new Exception('需求保存失败'); } return true; } /** * @throws */ public static function operateDemand($requestData, $userId) { $opType = $requestData['opType'] ?? 0; switch ($opType) { case self::BUTTON_CONFIRM: $res = self::confirmDemand($requestData, $userId); break; case self::BUTTON_CONTRACT_DEV: $res = self::devContract($requestData, $userId); break; case self::BUTTON_CONTRACT_CUSTOMER: $res = self::customerContract($requestData); break; case self::BUTTON_COMPLETE_WORK: $res = self::completeWork($requestData, $userId); break; case self::BUTTON_ACCEPT_PASSED: $res = self::acceptPassed($requestData); break; case self::BUTTON_CONFIRM_RECEIPT: $res = self::confirmReceipt($requestData, $userId); break; default: throw new \Exception('非法操作'); } return $res; } /** * 确认接单 * @param $requestData * @param int $userId * @return mixed * @throws Exception */ public static function confirmDemand($requestData, int $userId = 0) { try { $demandId = $requestData['demandId'] ?? 0; $agree = intval($requestData['agree'] ?? 0); $demand = self::getOne(['id' => $demandId]); if (!$demand) { throw new \Exception('该需求不存在'); } Db::startTrans(); if (!in_array($demand->status, [self::STATUS_BIDDING])) { throw new \Exception('该需求为' . self::STATUS_MAP[$demand->status] . ',不能不能确认'); } $userInfo = UserService::getOne(['id' => $userId]); $where = ['demand_id' => $demandId, 'user_id' => $userId, 'bidding_status' => DemandBiddingService::STATUS_BIDDING_WIN, 'process_status' => DemandBiddingService::STATUS_PROCESS_ONGOING]; $demandBidding = DemandBiddingService::getOne($where); if (!$demandBidding) { throw new \Exception('您无需确认'); } //修改投标状态 if ($agree) { //确认接单 $upRes = DemandBiddingService::where($where)->update(['process_status' => DemandBiddingService::STATUS_PROCESS_CONFIRMED]); if (!$upRes) { throw new \Exception('更新投标状态失败'); } //修改状态 $demand->status = self::STATUS_SIGNING; $saveRes = $demand->save(); if (!$saveRes) { exception('更新需求状态失败'); } //更新显示二维码 DemandGroupChatService::updateQrCode($demandId); //客户发送消息 $msgData = [ 'values' => [ $userInfo->nickname, '需求开发', SubscribeMessageService::MSG_MAP[SubscribeMessageService::MSG_TEMP_DEMAND_MATCH_SUCCESS]['remark'], ] ]; (new EasyWeChatService())->sendMsg($demand->user_id, SubscribeMessageService::MSG_TEMP_DEMAND_MATCH_SUCCESS, $msgData); } else { //拒绝接单 $upRes = DemandBiddingService::where($where)->update(['bidding_status' => DemandBiddingService::STATUS_BIDDING_NO, 'process_status' => DemandBiddingService::STATUS_PROCESS_FAILED]); if (!$upRes) { throw new \Exception('更新投标状态失败'); } //扣除信誉分5分,添加日志 $log_res = AccountLogService::addLog(AccountLogService::ACCOUNT_TYPE_CREDIT_SCORE, $userId, 5, '拒绝接单扣除', 0, AccountLogService::CREDIT_TYPE_DEMAND_REFUSE, AccountLogService::INC_EXP_EXPEND, $demandId); AccountService::updateData($userId, 0, 0, -5, 0, $log_res->id); } Db::commit(); $info = DemandBiddingService::getOne($where); return $info; } catch (\Exception $e) { Db::rollback(); throw $e; } } /** * 开发者签约 * @param $requestData * @param int $userId * @return mixed * @throws Exception */ public static function devContract($requestData, int $userId) { try { $demandId = $requestData['demandId'] ?? 0; $agree = intval($requestData['agree'] ?? 0); $demand = self::getOne(['id' => $demandId]); if (!$demand) { throw new \Exception('该需求不存在'); } Db::startTrans(); if ($agree) { if (!in_array($demand->status, [self::STATUS_SIGNING])) { throw new \Exception('该需求为' . self::STATUS_MAP[$demand->status] . ',不能不能签约'); } //生成两个订单,签约记录 OrderService::createDemandOrder($requestData); Db::commit(); //给客户发送签约提醒 $msgData = [ 'values' => [ $demand->parent_order_no, mb_substr($demand->desc, 0, 6), intval($requestData['formData']['totalCost'] ?? 0), SubscribeMessageService::MSG_MAP[SubscribeMessageService::MSG_TEMP_CONTRACT_SIGN]['remark'], ] ]; (new EasyWeChatService)->sendMsg($demand->user_id, SubscribeMessageService::MSG_TEMP_CONTRACT_SIGN, $msgData); } else { //拒绝签约 //中标去除 $where = ['demand_id' => $demandId, 'user_id' => $userId, 'bidding_status' => DemandBiddingService::STATUS_BIDDING_WIN]; $upRes = DemandBiddingService::where($where)->update(['bidding_status' => DemandBiddingService::STATUS_BIDDING_NO]); if (!$upRes) { exception('更新中标状态失败'); } //扣除信誉分5分,添加日志 $log_res = AccountLogService::addLog(AccountLogService::ACCOUNT_TYPE_CREDIT_SCORE, $userId, 5, '放弃签约扣除', 0, AccountLogService::CREDIT_TYPE_CONTRACT_REFUSE, AccountLogService::INC_EXP_EXPEND, $demandId); AccountService::updateData($userId, 0, 0, -5, 0, $log_res->id); //订单状态还原 $demand->status = self::STATUS_BIDDING; $demand->save(); Db::commit(); } return $demand; } catch (\Exception $e) { Db::rollback(); throw $e; } } /** * 客户签约 * @param $requestData * @return mixed * @throws Exception */ public static function customerContract($requestData) { try { $demandId = $requestData['demandId'] ?? 0; $contractType = $requestData['contractType'] ?? 1; $demand = self::getOne(['id' => $demandId]); $formData = $requestData['formData'] ?? []; if (!$demand) { throw new \Exception('该需求不存在'); } Db::startTrans(); if (!in_array($demand->status, [self::STATUS_SIGNING])) { throw new \Exception('该需求为' . self::STATUS_MAP[$demand->status] . ',不能签约'); } //更新签约记录 $winContract = ContractService::updateCustomerContractData($demandId, $formData); //如果新的合同金额与旧的合同金额不符,则通过修改尾款订单的金额的方式来弥补差价 $firstContract = ContractService::getFirstContract($demandId); if ($firstContract && $firstContract->total_amount != $winContract->total_amount && $winContract->status2 != ContractService::STATUS_FINISHED) { $diffAmount = $winContract->total_amount - $firstContract->total_amount; //更新尾款订单的金额 $finalOrder = OrderService::getDemandOrder($demand->parent_order_no, OrderService::ORDER_TYPE_FINAL_PAYMENT); $finalOrder->total_amount += $diffAmount; $finalOrder->goods_amount += $diffAmount; $finalOrder->payment_amount += $diffAmount; $saveRes = $finalOrder->save(); if (!$saveRes) { exception('弥补订单差价金额失败'); } } //获取签约订单用于支付 $order = OrderService::getDemandOrder($demand->parent_order_no, $contractType); if (!$order) { throw new \Exception('获取签约订单失败'); } //如果客户已支付预付款,则直接调用支付成功的处理 if (in_array($order->order_status, [OrderService::ORDER_STATUS_WAIT_SHIP])) { self::customerContractFinishedDeal(0, $demandId); } Db::commit(); return $order; } catch (\Exception $e) { Db::rollback(); throw $e; } } /** * 完成工作 * @param $requestData * @param $userId * @return mixed * @throws Exception */ public static function completeWork($requestData, $userId) { try { $agree = intval($requestData['agree'] ?? 0); $demandId = $requestData['demandId'] ?? 0; $demand = self::getOne(['id' => $demandId]); if (!$demand) { throw new \Exception('该需求不存在'); } Db::startTrans(); if ($agree) { //完成工作 if (!in_array($demand->status, [self::STATUS_WORKING])) { throw new \Exception('该需求为' . self::STATUS_MAP[$demand->status] . ',不能完成'); } $demand->status = self::STATUS_ACCEPTING; $demand->work_status = CommonService::SUCCESS; $demand->accept_status = CommonService::ONGOING; $demand->save(); Db::commit(); //给客户发送验收通知 $msgData = [ 'values' => [ $demand->parent_order_no, mb_substr($demand->desc, 0, 6), mb_substr($demand->desc, 0, 11), SubscribeMessageService::MSG_MAP[SubscribeMessageService::MSG_TEMP_WORK_ACCEPT]['remark'], ] ]; (new EasyWeChatService())->sendMsg($demand->user_id, SubscribeMessageService::MSG_TEMP_WORK_ACCEPT, $msgData); } else { //完成失败 //中标去除 $where = ['demand_id' => $demandId, 'user_id' => $userId, 'bidding_status' => DemandBiddingService::STATUS_BIDDING_WIN]; $upRes = DemandBiddingService::where($where)->update(['bidding_status' => DemandBiddingService::STATUS_BIDDING_NO]); if (!$upRes) { exception('更新中标状态失败'); } //扣除信誉分5分,添加日志 $log_res = AccountLogService::addLog(AccountLogService::ACCOUNT_TYPE_CREDIT_SCORE, $userId, 10, '需求完成失败-信誉分扣除', 0, AccountLogService::CREDIT_TYPE_DEMAND_FAILED, AccountLogService::INC_EXP_EXPEND, $demandId); AccountService::updateData($userId, 0, 0, -10, 0, $log_res->id); //冻结余额退回 AccountService::freezeAmountRefund($demandId, $userId); Db::commit(); //等待客服操作是否取消订单或重新安排开发人员 } return $demand; } catch (\Exception $e) { Db::rollback(); throw $e; } } /** * 验收通过 * @param $requestData * @return mixed * @throws Exception */ public static function acceptPassed($requestData) { try { $demandId = $requestData['demandId'] ?? 0; $contractType = $requestData['contractType'] ?? 2; $agree = $requestData['agree'] ?? 0; $reason = $requestData['reason'] ?? ''; $demand = self::getOne(['id' => $demandId], ['winBidding']); if (!$demand) { throw new \Exception('该需求不存在'); } Db::startTrans(); if (!in_array($demand->status, [self::STATUS_ACCEPTING])) { throw new \Exception('该需求为' . self::STATUS_MAP[$demand->status] . ',不能验收'); } if (!$agree) { //驳回重新工作 $demand->status = self::STATUS_WORKING; $demand->work_status = CommonService::ONGOING; $demand->accept_status = CommonService::FAILED; $saveRes = $demand->save(); if (!$saveRes) { exception('更新需求状态失败'); } //给开发者发送验收失败的通知 $msgData = [ 'values' => [ '整体功能验收', '验收失败', SubscribeMessageService::MSG_MAP[SubscribeMessageService::MSG_TEMP_WORK_ACCEPT_RESULT]['remarkFail'], mb_substr($reason, 0, 16) ?: '需求未按要求完成开发' ] ]; (new EasyWeChatService)->sendMsg($demand->winBidding->user_id, SubscribeMessageService::MSG_TEMP_WORK_ACCEPT_RESULT, $msgData); } else { //返回订单用于支付尾款 $order = OrderService::getDemandOrder($demand->parent_order_no, $contractType); if (!$order) { throw new \Exception('获取签约订单失败'); } } Db::commit(); return $order ?? []; } catch (\Exception $e) { Db::rollback(); throw $e; } } /** * 确认收货 * @param $requestData * @param $userId * @return mixed * @throws Exception */ public static function confirmReceipt($requestData, $userId) { try { $demandId = $requestData['demandId'] ?? 0; $demand = self::getOne(['id' => $demandId, 'user_id' => $userId]); if (!$demand) { throw new \Exception('该需求不存在'); } Db::startTrans(); if (!in_array($demand->status, [self::STATUS_CONFIRM_RECEIPT])) { throw new \Exception('该需求为' . self::STATUS_MAP[$demand->status] . ',不能确认收货'); } //订单完成 $demand->status = self::STATUS_FINISHED; $demand->settle_status = CommonService::ONGOING; //结算中 //返回订单用于支付尾款 $saveRes = $demand->save(); if (!$saveRes) { exception('更新需求状态失败'); } Db::commit(); return $demand; } catch (\Exception $e) { Db::rollback(); throw $e; } } /** * 客户签约并支付成功处理需求 * @param $parent_order_no * @param int $demandId * @return void * @throws Exception */ public static function customerContractFinishedDeal($parent_order_no, int $demandId = 0) { $where = function ($query) use ($parent_order_no, $demandId) { $query->where('parent_order_no', $parent_order_no)->whereOr('id', $demandId); }; $demand = self::getOne($where, ['winBidding']); if ($demand) { //更新客户的签约状态 $demand->id $contract = ContractService::getWinBiddingContract($demand->id); //如果客户已经签约过,则跳过防止重复给开发者增加冻结金额 if ($contract->status2 == ContractService::STATUS_FINISHED) { return; } $contract->status2 = ContractService::STATUS_FINISHED; $contract->save(); //更新需求的状态 $demand->status = self::STATUS_WORKING; $demand->save(); $userId = $contract->demandBidding->user_id; //开发者冻结金额增加 $demandOrder = OrderService::getDemandOrder($demand->parent_order_no, OrderService::ORDER_TYPE_FIRST_PAYMENT); $ratio = (float)config('site.platform_commission_ratio'); $amount = $demandOrder->payment_amount * (1 - $ratio); $title = OrderService::ORDER_TYPE_MAP[$demandOrder->order_type] ?? '需求收益'; $log_res = AccountLogService::addLog(AccountLogService::ACCOUNT_TYPE_FREEZE, $userId, $amount, $title . '-冻结金额增加', AccountLogService::TYPE_FREEZE, 0, AccountLogService::INC_EXP_INCREASE, $demandOrder->id); AccountService::updateData($userId, 0, $amount, 0, 0, $log_res->id); //给开发者发送开始工作的消息通知 $msgData = [ 'values' => [ mb_substr($demand->desc, 0, 6), date('Y.m.d', strtotime('+1 days')) . ' ~ ' . date('Y.m.d', strtotime("+$contract->need_days days")), SubscribeMessageService::MSG_MAP[SubscribeMessageService::MSG_TEMP_WORK_START]['remark'], ] ]; (new EasyWeChatService)->sendMsg($userId, SubscribeMessageService::MSG_TEMP_WORK_START, $msgData); } } /** * 客户验收完成处理需求 * @param $parent_order_no * @return void * @throws Exception */ public static function customerWorkAcceptFinishedDeal($parent_order_no) { $demand = self::getOne(['parent_order_no' => $parent_order_no], ['winBidding']); if ($demand && $demand->status == self::STATUS_ACCEPTING && $demand->accept_status != CommonService::SUCCESS) { //更新需求的状态 $demand->status = self::STATUS_CONFIRM_RECEIPT; $demand->accept_status = CommonService::SUCCESS; $demand->accept_time = time(); $saveRes = $demand->save(); if (!$saveRes) { exception('更新验收状态失败'); } $userId = $demand->winBidding->user_id; //开发者冻结金额增加 $demandOrder = OrderService::getDemandOrder($demand->parent_order_no, OrderService::ORDER_TYPE_FINAL_PAYMENT); $ratio = (float)config('site.platform_commission_ratio'); $amount = $demandOrder->payment_amount * (1 - $ratio); $title = OrderService::ORDER_TYPE_MAP[$demandOrder->order_type] ?? '需求收益'; $log_res = AccountLogService::addLog(AccountLogService::ACCOUNT_TYPE_FREEZE, $userId, $amount, $title . '-冻结金额增加', AccountLogService::TYPE_FREEZE, 0, AccountLogService::INC_EXP_INCREASE, $demandOrder->id); AccountService::updateData($userId, 0, $amount, 0, 0, $log_res->id); //给开发者发送验收结果的消息通知 $msgData = [ 'values' => [ '整体功能验收', '验收通过', SubscribeMessageService::MSG_MAP[SubscribeMessageService::MSG_TEMP_WORK_ACCEPT_RESULT]['remark'], '所有功能验收完成' ] ]; (new EasyWeChatService)->sendMsg($demand->winBidding->user_id, SubscribeMessageService::MSG_TEMP_WORK_ACCEPT_RESULT, $msgData); } } /** * 重新投标 * @param $demand * @return mixed * @throws Exception */ public static function resetBiddingDeal($demand) { try { Db::startTrans(); if (in_array($demand->status, [self::STATUS_CONFIRM_RECEIPT, self::STATUS_FINISHED, self::STATUS_CANCEL])) { throw new \Exception('该需求为' . self::STATUS_MAP[$demand->status] . ',不能重新投标'); } if (in_array($demand->settle_status, [CommonService::SUCCESS])) { throw new \Exception('该需求为' . self::SETTLE_STATUS_MAP[$demand->settle_status] . ',不能重新投标'); } $demandId = $demand->id; $where = ['demand_id' => $demandId, 'bidding_status' => DemandBiddingService::STATUS_BIDDING_WIN]; $winBidding = DemandBiddingService::where($where)->find(); if ($winBidding) { //中标去除 $winBidding->bidding_status = DemandBiddingService::STATUS_BIDDING_NO; $upRes = $winBidding->save(); if (!$upRes) { exception('更新中标状态失败'); } $userId = $winBidding->user_id; //扣除信誉分5分,添加日志 $log_res = AccountLogService::addLog(AccountLogService::ACCOUNT_TYPE_CREDIT_SCORE, $userId, 5, '系统自动操作-信誉分扣除', 0, AccountLogService::CREDIT_TYPE_ADMIN_OPERATE, AccountLogService::INC_EXP_EXPEND, $demandId); AccountService::updateData($userId, 0, 0, -5, 0, $log_res->id); //冻结余额退回 AccountService::freezeAmountRefund($demandId, $userId); } //继续投标,重置状态 $demand->status = self::STATUS_BIDDING; $demand->work_status = CommonService::NO; $demand->accept_status = CommonService::NO; $demand->settle_status = CommonService::NO; $saveRes = $demand->save(); if (!$saveRes) { exception('更新需求状态失败'); } Db::commit(); return $demand; } catch (\Exception $e) { Db::rollback(); throw $e; } } /** * 处理订单收益解冻 */ public static function unfreeze() { // $where = [ 'order_status' => DemandService::STATUS_CONFIRM_RECEIPT, 'settle_status' => CommonService::ONGOING, ]; $ratio = (float)config('site.platform_commission_ratio'); Demands::with(['demandContracts', 'winBidding'])->where($where)->chunk(100, function ($demands) use ($ratio) { foreach ($demands as $demand) { try { //开启事务 抛出异常时自动回滚 DB::transaction(function () use ($demand, $ratio) { $userId = $demand->merchant->user_id; $show_time = $demand->goodsInfo->show_time ? $demand->goodsInfo->show_time : $demand->goodsInfo->created_at; Log::info('订单收益解冻:', ['show_time' => $show_time, 'unfreeze_time' => strtotime(date('Y-m-d', strtotime($show_time) + 3 * 3600) . ' 09:00')]); if (time() > strtotime(date('Y-m-d', strtotime('+1 day', strtotime($show_time) + 3 * 3600)) . ' 09:00')) { $demand->status = DemandService::STATUS_FINISHED; $demand->settle_status = CommonService::SUCCESS; $demand->save(); $amount = ($demand->demandContracts->total_amount ?? 0) * (1 - $ratio); //写入余额日志 $log_res = AccountLogService::addLog(AccountLogService::ACCOUNT_TYPE_FREEZE, -$amount, '需求收益解冻-冻结扣除', AccountLogService::TYPE_UNFREEZE, 0, $demand->id, $demand->order_amount); $log_res = AccountLogService::addLog(AccountLogService::ACCOUNT_TYPE_AMOUNT, +$amount, '需求收益解冻-余额增加', AccountLogService::TYPE_UNFREEZE, 0, $demand->id, $demand->order_amount); //减去冻结,加到余额里面 AccountService::updateData($userId, $amount, -$amount, 0, 0, $log_res->id); //发送余额解冻通知 } }); } catch (\Exception $e) { Log::info('需求结算处理异常:' . json_encode(['file' => $e->getFile(), 'line' => $e->getLine(), 'error' => $e->getMessage(), 'demand' => $demand], JSON_UNESCAPED_UNICODE)); } } }); } /** * 审核 * @param $id * @return void */ public static function auditPass($id) { Db::transaction(function () use ($id) { $demand = self::getOne(['id' => $id]); if (!$demand) { exception('该需求不存在'); } if ($demand->publish_status == CommonService::SUCCESS) { exception('已经审核通过,请勿重复审核'); } //修改审核状态 $demand->status = self::STATUS_BIDDING; $demand->publish_status = CommonService::SUCCESS; $saveRes = $demand->save(); if (!$saveRes) { exception('更新审核状态失败'); } //发送审核结果-站内消息通知 }); } }