request->param(); try { $service->verify($payload); $response = ['result' => true, 'msg' => '']; } catch (\Throwable $throwable) { $response = ['result' => false, 'msg' => $throwable->getMessage()]; } $service->recordCall('verify', $payload, $response, $requestTime); return \think\Response::create($response, 'json'); } public function callback(): Json { $requestTime = microtime(true); $service = new LockerPickupService(); $payload = $this->request->param(); try { $service->callback($payload); $response = ['result' => true, 'msg' => '']; } catch (\Throwable $throwable) { $response = ['result' => false, 'msg' => $throwable->getMessage()]; } $service->recordCall('callback', $payload, $response, $requestTime); return \think\Response::create($response, 'json'); } } recordRequest('verify', $payload); $orderNo = $this->getString($payload, 'order'); $device = $this->getString($payload, 'device'); $action = $this->getString($payload, 'action'); if ($orderNo === '' || $device === '') { throw new Exception('参数错误'); } $order = $this->getOrder($orderNo); $equipment = $this->getEquipmentByDevice($device); if ((string)($order['locker_code'] ?? '') === '' || (string)$order['locker_code'] !== (string)$equipment['code']) { throw new Exception('当前订单不允许在当前取餐柜存餐'); } return ['order' => $orderNo, 'action' => $action]; } public function callback(array $payload): array { $this->recordRequest('callback', $payload); $action = $this->getString($payload, 'action'); $orderNo = $this->getString($payload, 'order'); if ($orderNo === '') { throw new Exception('订单号不能为空'); } if ($action === self::ACTION_SAVE_CALLBACK) { $this->updateOrder($orderNo, [ 'locker_status' => self::LOCKER_STATUS_STORED, 'locker_store_time' => date('Y-m-d H:i:s'), 'locker_cell_no' => $this->getString($payload, 'cellNo'), ]); return ['order' => $orderNo, 'locker_status' => self::LOCKER_STATUS_STORED]; } if ($action === self::ACTION_TAKE_BY_CODE_CALLBACK) { $this->updateOrder($orderNo, [ 'locker_status' => self::LOCKER_STATUS_PICKED, 'locker_pickup_time' => date('Y-m-d H:i:s'), ]); return ['order' => $orderNo, 'locker_status' => self::LOCKER_STATUS_PICKED]; } if ($action === self::ACTION_CANCEL_CALLBACK) { $this->updateOrder($orderNo, [ 'locker_status' => self::LOCKER_STATUS_CANCELLED, 'locker_cancel_time' => date('Y-m-d H:i:s'), ]); return ['order' => $orderNo, 'locker_status' => self::LOCKER_STATUS_CANCELLED]; } return ['order' => $orderNo, 'action' => $action]; } public function recordCall(string $scene, array $payload, array $response, float $requestTime): void { $responseTime = microtime(true); $now = date('Y-m-d H:i:s'); $requestDate = date('Y-m-d H:i:s', (int)$requestTime); $success = isset($response['result']) ? (bool)$response['result'] : ((int)($response['code'] ?? 1) === 0); $message = (string)($response['msg'] ?? $response['message'] ?? ''); try { Db::name('locker_pickup_api_log')->insert([ 'scene' => $scene, 'action' => $this->getString($payload, 'action'), 'order_no' => $this->getString($payload, 'order'), 'device' => $this->getString($payload, 'device'), 'trace_id' => (string)(App::$trace_id ?? ''), 'request_body' => json_encode($payload, JSON_UNESCAPED_UNICODE), 'response_body' => json_encode($response, JSON_UNESCAPED_UNICODE), 'response_code' => $success ? 0 : 1, 'response_message' => $message, 'request_time' => $requestDate, 'response_time' => $now, 'duration_ms' => (int)round(($responseTime - $requestTime) * 1000), 'create_time' => $now, 'update_time' => $now, ]); } catch (\Throwable $throwable) { Log::error('[LockerPickup][recordCall] failed=' . $throwable->getMessage()); } } private function getOrder(string $orderNo): array { $order = Db::name('meal_order') ->where(['order_no' => $orderNo, 'is_delete' => 0]) ->field('id,order_no,locker_code,locker_status') ->find(); if (empty($order)) { throw new Exception('订单不存在'); } return $order; } private function getEquipmentByDevice(string $device): array { $equipment = Db::name('equipment') ->where([ 'sn' => $device, 'type' => EquipmentTypeEnum::ENUM50, 'del_flag' => 0, ]) ->field('id,code,name,sn,address') ->find(); if (empty($equipment)) { throw new Exception('取餐柜设备不存在'); } return $equipment; } private function updateOrder(string $orderNo, array $data): void { $updated = Db::name('meal_order') ->where(['order_no' => $orderNo, 'is_delete' => 0]) ->update($data); if ($updated === false) { throw new Exception('订单状态更新失败'); } } private function getString(array $payload, string $key): string { return trim((string)($payload[$key] ?? '')); } private function recordRequest(string $scene, array $payload): void { Log::info('[LockerPickup][' . $scene . '] payload=' . json_encode($payload, JSON_UNESCAPED_UNICODE)); } } where('id', $restaurantId) ->field('id,stall_type') ->find(); if (empty($restaurant) || (int)($restaurant['stall_type'] ?? 0) !== 2) { return ''; } $existing = Db::name('meal_order_pickup') ->where('meal_order_id', $orderId) ->value('pickup_no'); if (!empty($existing)) { return (string)$existing; } $pickupNo = $this->reservePickupNo($restaurantId, $mealDate, $mealTimes); if ($pickupNo === '') { return ''; } return $this->saveForOrder($order, $pickupNo); } public function reservePickupNo(int $restaurantId, string $mealDate, int $mealTimes): string { if ($restaurantId <= 0 || $mealDate === '' || $mealTimes <= 0) { return ''; } $restaurant = Db::name('restaurants') ->where('id', $restaurantId) ->field('id,stall_type') ->find(); if (empty($restaurant) || (int)($restaurant['stall_type'] ?? 0) !== 2) { return ''; } $redis = $this->getPickupRedis(); if (!$redis instanceof \Redis) { Log::record('MealOrderPickup redis unavailable scope=' . $restaurantId . ':' . $mealDate . ':' . $mealTimes); return ''; } $redisKey = sprintf('pickup_no:%d:%s:%d', $restaurantId, $mealDate, $mealTimes); $counter = (int)$redis->incr($redisKey); if ($counter <= 0) { Log::record('MealOrderPickup incr fail key=' . $redisKey); return ''; } return $counter < 1000 ? str_pad((string)$counter, 3, '0', STR_PAD_LEFT) : (string)$counter; } public function saveForOrder(array $order, string $pickupNo = ''): string { $orderId = (int)($order['id'] ?? 0); $restaurantId = (int)($order['restaurant_id'] ?? 0); $mealDate = (string)($order['meal_date'] ?? ''); $mealTimes = (int)($order['meal_times'] ?? 0); $orderNo = (string)($order['order_no'] ?? ''); if ($orderId <= 0 || $restaurantId <= 0 || $mealDate === '' || $mealTimes <= 0 || $orderNo === '') { return ''; } $restaurant = Db::name('restaurants') ->where('id', $restaurantId) ->field('id,stall_type') ->find(); if (empty($restaurant) || (int)($restaurant['stall_type'] ?? 0) !== 2) { return ''; } $existing = Db::name('meal_order_pickup') ->where('meal_order_id', $orderId) ->value('pickup_no'); if (!empty($existing)) { return (string)$existing; } if ($pickupNo === '') { $pickupNo = $this->reservePickupNo($restaurantId, $mealDate, $mealTimes); if ($pickupNo === '') { return ''; } } Db::name('meal_order_pickup')->insert([ 'restaurant_id' => $restaurantId, 'meal_date' => $mealDate, 'meal_times' => $mealTimes, 'meal_order_id' => $orderId, 'order_no' => $orderNo, 'pickup_no' => $pickupNo, 'prepare_status' => PrepareStatusEnum::PREPARING, 'prepare_time' => date('Y-m-d H:i:s'), 'pickup_ready_time' => '2000-01-01 00:00:00', 'serve_equipment_code' => '', 'last_call_time' => '2000-01-01 00:00:00', 'call_count' => 0, ]); Log::record('MealOrderPickup created order_no=' . $orderNo . ' pickup_no=' . $pickupNo); return $pickupNo; } public function getPickupNoByOrderNo(string $orderNo): string { if ($orderNo === '') { return ''; } $pickupNo = Db::name('meal_order_pickup') ->where('order_no', $orderNo) ->value('pickup_no'); return $pickupNo ? (string)$pickupNo : ''; } private function getPickupRedis() { $redis = getRedis(); if ($redis instanceof \Redis) { return $redis; } $config = []; $localConfigFile = APP_PATH . 'config/local/config.php'; if (is_file($localConfigFile)) { $localConfig = require $localConfigFile; if (!empty($localConfig['redis']) && is_array($localConfig['redis'])) { $config = $localConfig['redis']; } } if (empty($config['host'])) { $config = Config::get('redis'); } if (empty($config['host']) || empty($config['port'])) { return null; } try { $client = new \Redis(); $timeout = isset($config['timeout']) ? (float)$config['timeout'] : 0; $client->connect($config['host'], (int)$config['port'], $timeout); if (!empty($config['password'])) { $client->auth($config['password']); } if (isset($config['select'])) { $client->select((int)$config['select']); } return $client; } catch (\Throwable $e) { Log::record('MealOrderPickup redis connect fail: ' . $e->getMessage()); return null; } } } public function serveOrder(string $orderNo, string $equipmentCode): array { $pickup = Db::name('meal_order_pickup') ->where('order_no', $orderNo) ->find(); if (empty($pickup)) { throwError('取餐号不存在'); } $now = date('Y-m-d H:i:s'); $prepareStatus = (int)($pickup['prepare_status'] ?? 0); $callCount = (int)($pickup['call_count'] ?? 0); if ($prepareStatus === PrepareStatusEnum::PREPARING) { Db::name('meal_order_pickup') ->where('id', (int)$pickup['id']) ->update([ 'prepare_status' => PrepareStatusEnum::READY, 'pickup_ready_time' => $now, 'serve_equipment_code' => $equipmentCode, 'last_call_time' => $now, 'call_count' => 1, 'update_time' => $now, ]); return [ 'order_no' => $orderNo, 'prepare_status' => PrepareStatusEnum::READY, 'call_count' => 1, ]; } if ($prepareStatus === PrepareStatusEnum::READY) { $newCallCount = $callCount + 1; Db::name('meal_order_pickup') ->where('id', (int)$pickup['id']) ->update([ 'last_call_time' => $now, 'call_count' => $newCallCount, 'update_time' => $now, ]); return [ 'order_no' => $orderNo, 'prepare_status' => PrepareStatusEnum::READY, 'call_count' => $newCallCount, ]; } return [ 'order_no' => $orderNo, 'prepare_status' => $prepareStatus, 'call_count' => $callCount, ]; } public function orderPayAsync(array $params, $order_type): array { $cardId = (string)$params['card_id']; $money = (float)$params['money']; $msgid = (string)$params['msgid']; $orderSource = (int)($params['order_source'] ?? OrderSourceEnum::CONSUMEORDER); $params['order_source'] = $orderSource; $params['restaurant_id'] = $this->resolveOrderRestaurantId($params); if ((int)$params['restaurant_id'] <= 0) { $params['restaurant_id'] = 1; } $consumeTimestamp = strtotime((string)($params['create_time'] ?? '')); if ($consumeTimestamp === false || $consumeTimestamp === null) { $consumeTimestamp = time(); } $consumeTime = date('Y-m-d H:i:s', $consumeTimestamp); $params = $this->normalizeOrderMealParams($params, $consumeTime); $params['pickup_no'] = $this->reservePickupNo($params['restaurant_id'], $params['meal_date'], $params['meal_times'], $order_type); if (empty($cardId)) { return $this->runAsyncFallback($params, $money, $cardId, $consumeTime, $msgid, $order_type, $orderSource); } $redis = getRedis(); if (!$redis instanceof \Redis) { return $this->runAsyncFallback($params, $money, $cardId, $consumeTime, $msgid, $order_type, $orderSource); } $existingResult = $this->getAsyncOrderResultByMsgid($redis, $msgid, $consumeTime, $orderSource); if ($existingResult !== null) { return $existingResult; } if ((int)$order_type === 2) { return $this->runAsyncFallback($params, $money, $cardId, $consumeTime, $msgid, $order_type, $orderSource, $redis); } $lockKey = $this->buildAsyncConsumeLockKey($cardId); if (!$this->acquireAsyncRedisLock($redis, $lockKey, self::ASYNC_LOCK_TTL)) { return ['code' => 1, 'message' => '系统繁忙,请稍后重试', 'data' => []]; } $result = null; $shouldFallback = false; try { $snapshot = $this->getAsyncConsumeSnapshot($redis, $cardId); if (empty($snapshot) || empty($snapshot['staff_uuid'])) { $shouldFallback = true; } else { $processing = $this->handleAsyncSnapshotPayment($redis, $snapshot, $params, $cardId, $money, $consumeTime, $msgid, $order_type, $orderSource); $initial_menu_item['equipment_code'] = $equipment_code; $initial_menu_item['meal_time'] = $order['bind_time']; $initial_menu_item['pay_status'] = PayStatusEnum::PENDING; $initial_menu[] = $initial_menu_item; } } $orderRemainAmounts = [0, 0, 0]; Db::startTrans(); $aiDb = null; if ($db instanceof \think\db\Connection) { $db->startTrans(); $aiDb = $db; } try { $mealOrderModel = new \app\api\model\meal\Order; $mealOrderModel->save($order); $order['id'] = $mealOrderModel['id']; if (!empty($initial_menu)) { foreach ($initial_menu as $key => $item) { $initial_menu[$key]['meal_order_id'] = $order['id']; $initial_menu[$key]['pay_status'] = PayStatusEnum::SUCCESS; $initial_menu[$key]['pay_time'] = date('Y-m-d H:i:s'); } $this->initialMenuModel->saveAll($initial_menu); } $pickupService = new MealOrderPickupService(); $pickupNo = $pickupService->saveForOrder($order, (string)($params['pickup_no'] ?? '')); if ($order['pay_price'] > 0) { \app\api\model\User::setIncPayMoney($order['user_id'], (float)$order['pay_price']); } $aiUserId = (int)($user['ai_user_id'] ?? 0); $subsidyUsed = '0.00'; $initial_menu_item['create_time'] = date("Y-m-d H:i:s"); $initial_menu_item['create_by'] = 'api'; $initial_menu_item['meal_type'] = 3; $initial_menu_item['meal_order_id'] = 0; $initial_menu_item['dishes_weight'] = $dishes_info['weight'] ?? 0; $initial_menu_item['dishes_price'] = $dishes_info['price'] ?? 0; $initial_menu_item['total_price'] = $dish['money'] ?? 0; $initial_menu_item['plate_code'] = ''; $initial_menu_item['equipment_code'] = $params['equipment_code'] ?? ''; $initial_menu_item['meal_time'] = $bind_time; $initial_menu_item['pay_status'] = PayStatusEnum::SUCCESS; $initial_menu_item['pay_time'] = $payTime; $initial_menu[] = $initial_menu_item; } } Db::startTrans(); $db = get_ai_db(); if ($db instanceof \think\db\Connection) { $db->startTrans(); } $aiDb = $db; try { $mealOrderModel = new \app\api\model\meal\Order; $mealOrderModel->save($order); $order['id'] = $mealOrderModel['id']; if (!empty($initial_menu)) { foreach ($initial_menu as $key => $item) { $initial_menu[$key]['meal_order_id'] = $order['id']; } $this->initialMenuModel->saveAll($initial_menu); } $pickupService = new MealOrderPickupService(); $pickupNo = $pickupService->saveForOrder($order, (string)($params['pickup_no'] ?? '')); if ($order['pay_price'] > 0) { \app\api\model\User::setIncPayMoney($order['user_id'], (float)$order['pay_price']); } $subsidy = '0.00'; $cashUsed = '0.00'; $aiUserId = (int)($params['ai_user_id'] ?? ($user['ai_user_id'] ?? 0)); $remainPay = helper::number2((float)$order['pay_price']); $initialCashBalance = '0.00'; $initialSubsidyBalance = '0.00'; $queueOrderRemainAmounts = [0, 0, 0]; if ($aiUserId > 0) { $userRow = $db->name('user') ->where('user_id', '=', $aiUserId) ->lock(true) ->field('balance') ->find(); $initialCashBalance = helper::number2(isset($userRow['balance']) ? (float)$userRow['balance'] : 0); $userSubsidy = $db->name('user_subsidy') setName('MealOrderPickupComplete')->setDescription('complete expired meal order pickup records'); } protected function execute(Input $input, Output $output) { $output->writeln(date('Y-m-d H:i:s') . ' MealOrderPickupComplete start'); $today = date('Y-m-d'); $now = date('Y-m-d H:i:s'); $updated = Db::name('meal_order_pickup') ->where('meal_date', '<', $today) ->where('prepare_status', 'in', [ PrepareStatusEnum::PREPARING, PrepareStatusEnum::READY, ]) ->update([ 'prepare_status' => PrepareStatusEnum::COMPLETED, 'update_time' => $now, ]); $output->writeln(date('Y-m-d H:i:s') . ' MealOrderPickupComplete updated=' . (int)$updated); $output->writeln(date('Y-m-d H:i:s') . ' MealOrderPickupComplete end'); } } if ($order_total_num > (int)$limit_value2) { return $this->renderError('菜品数量超出限制'); } } } $Checkout = new CheckoutService; $personal = $Checkout->getPersonal(); $order = array_merge($order, $personal); $restaurant_time = '00:00 - 23:59'; switch ($meal_times) { case 1: $restaurant_time = date('H:i', strtotime($Restaurant['morning_start'])).' - '.date('H:i', strtotime($Restaurant['morning_end'])); break; case 2: $restaurant_time = date('H:i', strtotime($Restaurant['noon_start'])).' - '.date('H:i', strtotime($Restaurant['noon_end'])); break; case 3: $restaurant_time = date('H:i', strtotime($Restaurant['night_start'])).' - '.date('H:i', strtotime($Restaurant['night_end'])); break; } $book_rules = json_decode($Restaurant['book_rules'] ?? '', true); $take_time = $book_rules['time'] ?? []; $lockers = $this->getAvailableLockers($restaurant_id); $takeMethods = $this->resolveTakeMethods($book_rules, (float)($Restaurant['package_fee'] ?? 0), (float)($Restaurant['locker_package_fee'] ?? 0), $lockers); $RestaurantInfo = [ 'restaurant_name' => $Restaurant['name'], 'restaurant_location' => $Restaurant['location'], 'take_methods' => $takeMethods, 'restaurant_time' => $restaurant_time, 'take_time' => $take_time, ]; $order = array_merge($order, $RestaurantInfo); return $this->renderSuccess([ 'order' => $order, 'detail' => $cartInfos, ]); } public function submit(): Json { $Checkout = new CheckoutService; $params = $this->getParam(); $params['restaurant_method'] = $this->resolveRestaurantMethodParam($params); $Checkout->setParam($params); $restaurant_id = (int)$this->request->param('restaurant_id', 0); $meal_date = (string)$this->request->param('meal_date', ''); $meal_times = (int)$this->request->param('meal_times', 0); $cartIds = $this->getCartIds(); $MealCartService = new MealCartService; $dishesList = $MealCartService->getOrderGoodsList($cartIds, $restaurant_id, $meal_date, $meal_times); if (empty($dishesList)) { return $this->renderError('菜品信息有误'); } $order_total_price = 0; $order_total_num = 0; foreach ($dishesList as $dishes) { $total_price = helper::bcmul($dishes['price'], $dishes['quantity']); $order_total_price = helper::bcadd($order_total_price, $total_price); $order_total_num += (int)$dishes['quantity']; } if ($restaurant_id > 0) { $RestaurantModel = new RestaurantModel; $Restaurant = $RestaurantModel->getDetail($restaurant_id); if (empty($Restaurant)) { return $this->renderError('餐厅不存在'); } if ((int)$Restaurant['stall_type'] === StallTypeEnum::ENUM1) { return $this->renderError('当前餐厅为堂食类型,不允许订餐'); } $orderList = $RestaurantModel->orderList($restaurant_id); $isOrderable = false; $dateMatched = false; foreach ($orderList['list'] ?? [] as $item) { if (($item['date'] ?? '') !== $meal_date) { continue; } $dateMatched = true; foreach ($item['meal'] ?? [] as $meal) { if ((int)($meal['value'] ?? 0) !== $meal_times) { continue; } if ((int)($meal['is_order'] ?? 2) === 1) { $isOrderable = true; } break; } break; } if (!$dateMatched || !$isOrderable) { return $this->renderError('当前时间不可订餐'); } if ($error = $this->getDishesBookableError($dishesList, $restaurant_id, $meal_date, $meal_times)) { return $this->renderError($error); } $book_rules = json_decode($Restaurant['book_rules'] ?? '', true); $lockers = $this->getAvailableLockers($restaurant_id); $takeMethods = $this->resolveTakeMethods(is_array($book_rules) ? $book_rules : [], (float)($Restaurant['package_fee'] ?? 0), (float)($Restaurant['locker_package_fee'] ?? 0), $lockers); if (!$this->isTakeMethodAllowed($takeMethods, $params['restaurant_method'])) { $params['restaurant_method'] = $this->getDefaultTakeMethod($takeMethods); $Checkout->setParam($params); } if ((int)$params['restaurant_method'] === TakeMethodEnum::LOCKER && !$this->isLockerCodeAllowed($lockers, (string)($params['locker_code'] ?? ''))) { return $this->renderError('请选择可用取餐柜'); } if ((int)$Restaurant['stall_type'] === StallTypeEnum::ENUM3) { $zhaodai_num = (int)$this->request->param('zhaodai_num', 0); $peitong_num = (int)$this->request->param('peitong_num', 0); if ($zhaodai_num <= 0) { return $this->renderError('招待人数不能为空'); } if ($peitong_num < 0) { return $this->renderError('陪同人数不能为空'); return $id !== '' && $id !== null; })); } private function resolveRestaurantMethodParam(array $params): int { $restaurantMethod = (int)($params['restaurant_method'] ?? 0); if (in_array($restaurantMethod, [TakeMethodEnum::DINE_IN, TakeMethodEnum::PICKUP, TakeMethodEnum::LOCKER], true)) { return $restaurantMethod; } $takeMethod = (int)($params['take_method'] ?? 0); if (in_array($takeMethod, [TakeMethodEnum::DINE_IN, TakeMethodEnum::PICKUP, TakeMethodEnum::LOCKER], true)) { return $takeMethod; } return TakeMethodEnum::DINE_IN; } private function resolveTakeMethods(?array $bookRules, float $packageFee, float $lockerPackageFee = 0.0, array $lockers = []): array { $methodList = is_array($bookRules) ? ($bookRules['method'] ?? [TakeMethodEnum::DINE_IN, TakeMethodEnum::PICKUP]) : [TakeMethodEnum::DINE_IN, TakeMethodEnum::PICKUP]; if (!is_array($methodList) || empty($methodList)) { $methodList = [TakeMethodEnum::DINE_IN, TakeMethodEnum::PICKUP]; } $methodList = array_values(array_unique(array_map('intval', $methodList))); $takeMethods = []; foreach ($methodList as $method) { if ($method === TakeMethodEnum::DINE_IN) { $takeMethods[] = [ 'type' => TakeMethodEnum::DINE_IN, 'name' => TakeMethodEnum::getName(TakeMethodEnum::DINE_IN), 'package_fee' => 0, 'package_fee_item' => null, ]; } elseif ($method === TakeMethodEnum::PICKUP) { $takeMethods[] = [ 'type' => TakeMethodEnum::PICKUP, 'name' => TakeMethodEnum::getName(TakeMethodEnum::PICKUP), 'package_fee' => helper::number2((string)$packageFee), 'package_fee_item' => $this->getPackageFeeItem($packageFee), ]; } elseif ($method === TakeMethodEnum::LOCKER && !empty($lockers)) { $takeMethods[] = [ 'type' => TakeMethodEnum::LOCKER, 'name' => TakeMethodEnum::getName(TakeMethodEnum::LOCKER), 'package_fee' => helper::number2((string)$lockerPackageFee), 'package_fee_item' => $this->getPackageFeeItem($lockerPackageFee), 'lockers' => $lockers, ]; } } if (empty($takeMethods)) { $takeMethods = [ [ 'type' => TakeMethodEnum::DINE_IN, 'name' => TakeMethodEnum::getName(TakeMethodEnum::DINE_IN), 'package_fee' => 0, 'package_fee_item' => null, ], [ 'type' => TakeMethodEnum::PICKUP, 'name' => TakeMethodEnum::getName(TakeMethodEnum::PICKUP), 'package_fee' => helper::number2((string)$packageFee), 'package_fee_item' => $this->getPackageFeeItem($packageFee), ], ]; } array_multisort(array_column($takeMethods, 'type'), SORT_ASC, $takeMethods); return $takeMethods; } private function getPackageFeeItem(float $price): array { $item = []; try { $config = \think\facade\Db::connect('mysql_zhct') ->name('config') ->where('config_key', 'package_fee_dish') ->where('status', 1) ->order('id', 'desc') ->value('config_value'); $item = $config ? json_decode((string)$config, true) : []; } catch (\Throwable $e) { $item = []; } if (!is_array($item)) { $item = []; } return [ 'uuid' => (string)($item['uuid'] ?? self::PACKAGE_FEE_UUID), 'name' => (string)($item['name'] ?? self::PACKAGE_FEE_NAME), 'price' => helper::number2((string)$price), 'file_path' => (string)($item['preview_url'] ?? self::PACKAGE_FEE_IMAGE), ]; } private function isTakeMethodAllowed(array $takeMethods, int $takeMethod): bool { if (!in_array($takeMethod, [TakeMethodEnum::DINE_IN, TakeMethodEnum::PICKUP, TakeMethodEnum::LOCKER], true)) { return false; } foreach ($takeMethods as $method) { if ((int)($method['type'] ?? 0) === $takeMethod) { return true; } } return false; } private function getDefaultTakeMethod(array $takeMethods): int { foreach ($takeMethods as $method) { if ((int)($method['type'] ?? 0) === TakeMethodEnum::DINE_IN) { return TakeMethodEnum::DINE_IN; } } return (int)($takeMethods[0]['type'] ?? TakeMethodEnum::DINE_IN); } private function getAvailableLockers(int $restaurantId): array { if ($restaurantId <= 0) { return []; } try { $db = \think\facade\Db::connect('mysql_zhct'); $restaurant = $db->name('restaurants') ->where('id', $restaurantId) ->field('id,restaurant_shop_id') ->find(); if (empty($restaurant['restaurant_shop_id'])) { return []; } $shop = $db->name('restaurant_shop') ->where('id', (int)$restaurant['restaurant_shop_id']) ->field('id,area_id') ->find(); if (empty($shop['area_id'])) { return []; } $shopIds = $db->name('restaurant_shop') ->where('area_id', (int)$shop['area_id']) ->where('is_delete', 0) ->where('status', 1) ->column('id'); if (empty($shopIds)) { return []; } $restaurantIds = $db->name('restaurants') ->where('restaurant_shop_id', 'in', $shopIds) ->where('status', 1) ->column('id'); if (empty($restaurantIds)) { return []; } $rows = $db->name('equipment') ->where('restaurant_id', 'in', $restaurantIds) ->where('type', EquipmentTypeEnum::ENUM50) ->where('del_flag', 0) ->field('id,code,name,sn,address') ->select(); if (is_object($rows) && method_exists($rows, 'toArray')) { $rows = $rows->toArray(); } elseif (!is_array($rows)) { $rows = []; } $lockers = []; foreach ($rows as $row) { $code = (string)($row['code'] ?? ''); if ($code === '') { continue; } $lockers[] = [ 'id' => (int)($row['id'] ?? 0), 'code' => $code, 'name' => (string)($row['name'] ?? ''), 'sn' => (string)($row['sn'] ?? ''), 'address' => (string)($row['address'] ?? ''), ]; } return $lockers; } catch (\Throwable $e) { return []; } } private function isLockerCodeAllowed(array $lockers, string $lockerCode): bool { if ($lockerCode === '') { return false; } foreach ($lockers as $locker) { if ((string)($locker['code'] ?? '') === $lockerCode) { return true; } } return false; } private function getParam(array $define = []): array { return array_merge($define, $this->request->param()); } user = UserService::getCurrentLoginUser(true); $this->model = new OrderModel; $this->storeId = $this->getStoreId(); } public function setParam($param): array { $this->param = $param; return $this->getParam(); } public function getParam(): array { return $this->param; } public function onCheckout($dishesList): array { $this->dishesList = $dishesList; return $this->checkout(); } private function checkout(): array { $this->orderData = $this->getOrderData(); $this->setZhctUser(); $orderTotalNum = (int)helper::getArrayColumnSum($this->dishesList, 'quantity'); $this->setOrderTotalPrice(); $this->setOrderPayPrice(); return array_merge([ 'dishesList' => $this->dishesList, 'orderTotalNum' => $orderTotalNum, 'hasError' => $this->hasError(), 'errorMsg' => $this->getError(), ], $this->orderData); } private function getOrderData(): array { $takeMethod = $this->getTakeMethod(); $packageFee = $this->getPackageFeeByMethod($takeMethod); $locker = $takeMethod === TakeMethodEnum::LOCKER ? $this->getLockerByCode((string)($this->param['locker_code'] ?? '')) : []; return [ 'pay_method' => PaymentMethodEnum::BALANCE, 'pay_status' => PayStatusEnum::PENDING, 'meal_date' => $this->param['meal_date'], 'meal_times' => $this->param['meal_times'], 'bind_id' => 0, 'bind_time' => date('Y-m-d H:i:s'), 'last_meal_time' => date('Y-m-d H:i:s'), 'equipment_code' => '', 'source' => OrderSourceEnum::ORDERFOOD, 'restaurant_id' => $this->param['restaurant_id'], 'user_mobile' => (string)($this->param['user_mobile'] ?? ''), 'user_name' => (string)($this->param['user_name'] ?? ''), 'take_method_text' => $this->getTakeMethodText($takeMethod), 'restaurant_method' => $takeMethod, 'locker_code' => (string)($locker['code'] ?? ''), 'locker_address' => (string)($locker['address'] ?? ''), 'locker_status' => $takeMethod === TakeMethodEnum::LOCKER ? LockerStatusEnum::WAIT_STORE : LockerStatusEnum::NONE, 'package_fee' => helper::number2((string)$packageFee), 'remark' => $this->param['remark'], 'zhaodai_num' => $this->param['zhaodai_num'] ?? 0, 'peitong_num' => $this->param['peitong_num'] ?? 0, 'take_time_start' => $this->param['take_time_start'] ?? '', 'take_time_end' => $this->param['take_time_end'] ?? '', 'print_status' => PrintStatusEnum::PENDING, ]; } private function setZhctUser() { $ZhctUserModel = new ZhctUserModel; $ZhctUser = $ZhctUserModel->getInfoBYAIID($this->user['user_id']); $this->ZhctUser = $ZhctUser; $ZhctStaffModel = new ZhctStaffModel; $ZhctStaff = $ZhctStaffModel->getInfoBYAIID($this->user['user_id']); $this->ZhctStaff = $ZhctStaff; } private function setOrderTotalPrice() { $dishesTotal = helper::getArrayColumnSum($this->dishesList, 'total_price'); $packageFee = (float)$this->orderData['package_fee']; $this->orderData['total_price'] = helper::number2(helper::bcadd((string)$dishesTotal, (string)$packageFee)); } private function setOrderPayPrice() { $this->orderData['order_price'] = $this->orderData['total_price']; $this->orderData['pay_price'] = $this->orderData['order_price']; } public function getPersonal(): array { $cahs_balance = $this->user['balance']; $UserSubsidyModel = new UserSubsidyModel; $subsidy_balance = $UserSubsidyModel->getCount($this->user['user_id']); $balance = $cahs_balance + $subsidy_balance; return [ 'user_id' => $this->user['user_id'], 'user_mobile' => $this->user['mobile'], 'user_name' => $this->user['nick_name'], 'user_balance' => $balance, 'user_cahs_balance' => $cahs_balance, 'user_subsidy_balance' => $subsidy_balance, ]; } public function createOrder(array $order): bool { if (!$this->validateOrderForm($order)) { return false; } return $this->model->transaction(function () use ($order) { return $this->createOrderEvent($order); }); } private function createOrderEvent($order): bool { $this->add($order); $this->saveOrderDishes($order); return true; } private function validateOrderForm(array $order): bool { if (empty($this->ZhctUser) || empty($this->ZhctStaff)) { $this->error = '人员数据错误'; return false; } $takeMethod = (int)($order['restaurant_method'] ?? $order['take_method'] ?? $this->getTakeMethod()); if ($takeMethod == TakeMethodEnum::PICKUP) { return $this->validateOrderFormByPickup($order); } if ($takeMethod == TakeMethodEnum::LOCKER) { return $this->validateOrderFormByLocker($order); } return true; } private function getTakeMethod(): int { $restaurantMethod = (int)($this->param['restaurant_method'] ?? 0); if (in_array($restaurantMethod, [TakeMethodEnum::DINE_IN, TakeMethodEnum::PICKUP, TakeMethodEnum::LOCKER], true)) { return $restaurantMethod; } $takeMethod = (int)($this->param['take_method'] ?? 0); if (in_array($takeMethod, [TakeMethodEnum::DINE_IN, TakeMethodEnum::PICKUP, TakeMethodEnum::LOCKER], true)) { return $takeMethod; } return TakeMethodEnum::DINE_IN; } private function getTakeMethodText(int $takeMethod): string { return TakeMethodEnum::getName($takeMethod) ?: TakeMethodEnum::getName(TakeMethodEnum::DINE_IN); } private function getPackageFeeByMethod(int $takeMethod): float { if (!in_array($takeMethod, [TakeMethodEnum::PICKUP, TakeMethodEnum::LOCKER], true)) { return 0.0; } $restaurantId = (int)($this->param['restaurant_id'] ?? 0); if ($restaurantId <= 0) { return 0.0; } $restaurant = $this->getRestaurantById($restaurantId); if (empty($restaurant)) { return 0.0; } if ($takeMethod === TakeMethodEnum::LOCKER) { return (float)($restaurant['locker_package_fee'] ?? 0); } return (float)($restaurant['package_fee'] ?? 0); } private function getRestaurantById(int $restaurantId): array { $RestaurantModel = new RestaurantModel; $restaurant = $RestaurantModel->getDetail($restaurantId); if (is_array($restaurant)) { return $restaurant; } if (is_object($restaurant) && method_exists($restaurant, 'toArray')) { return $restaurant->toArray(); } return []; } private function validateOrderFormByPickup(array $order): bool { if (empty($order['restaurant_id'])) { $this->error = '您还没有选择取餐餐厅'; return false; } if (empty($order['user_name'])) { $this->error = '取餐人姓名不能为空'; return false; } if (empty($order['user_mobile'])) { $this->error = '取餐人手机号不能为空'; return false; } return true; } private function validateOrderFormByLocker(array $order): bool { if (empty($order['restaurant_id'])) { $this->error = '您还没有选择取餐餐厅'; return false; } if (empty($order['locker_code'])) { $this->error = '请选择取餐柜'; return false; } $locker = $this->getLockerByCode((string)$order['locker_code']); if (empty($locker)) { $this->error = '取餐柜不存在或已停用'; return false; } return true; } private function getLockerByCode(string $lockerCode): array { if ($lockerCode === '') { return []; } try { $locker = \think\facade\Db::connect('mysql_zhct') ->name('equipment') ->where('code', $lockerCode) ->where('type', EquipmentTypeEnum::ENUM50) ->where('del_flag', 0) ->field('id,code,name,sn,address') ->find(); return is_array($locker) ? $locker : []; } catch (\Throwable $e) { return []; } } private function add($order): void { $order['user_id'] = $this->ZhctUser['id']; $order['order_no'] = $this->model->orderNo(); $order['meal_number'] =$order['orderTotalNum']; $this->model->save($order); } private function saveOrderDishes($order): void { $InitialMenu = []; foreach ($order['dishesList'] as $key => $dish) { $item = [ 'uuid' => guid(), 'code' => $key + 1, 'hardware_img_uuid' => '', 'recommend_uuid' => '', 'staff_uuid' => $this->ZhctStaff['uuid'], 'error_dishes_uuid' => '', 'dishes_uuid' => $dish['uuid'], 'menu_date' => $order['meal_date'], 'meal_times' => $order['meal_times'], 'number' => $dish['quantity'], 'weight' => $dish['weight'] * $dish['quantity'], 'source' => 3, 'customer_uuid' => '', 'create_time' => date('Y-m-d H:i:s'), 'create_by' => $this->ZhctStaff['name'], 'meal_type' => 3, 'select_adjust' => 1, 'meal_order_id' => $this->model['id'], 'dishes_weight' => $dish['weight'], 'dishes_price' => $dish['price'], 'total_price' => $dish['total_price'], 'plate_code' => '', 'equipment_code' => '', 'meal_time' => date('Y-m-d H:i:s'), 'pay_status' => PayStatusEnum::PENDING, ]; $InitialMenu[] = $item; } if (in_array((int)($order['restaurant_method'] ?? 0), [TakeMethodEnum::PICKUP, TakeMethodEnum::LOCKER], true) && (float)($order['package_fee'] ?? 0) > 0 ) { $packageFee = helper::number2((string)$order['package_fee']); $InitialMenu[] = [ 'uuid' => guid(), 'code' => count($InitialMenu) + 1, 'hardware_img_uuid' => '', 'recommend_uuid' => '', 'staff_uuid' => $this->ZhctStaff['uuid'], 'error_dishes_uuid' => '', 'dishes_uuid' => self::PACKAGE_FEE_UUID, 'menu_date' => $order['meal_date'], 'meal_times' => $order['meal_times'], 'number' => 1, 'weight' => 0, 'source' => 3, 'customer_uuid' => '', 'create_time' => date('Y-m-d H:i:s'), 'create_by' => $this->ZhctStaff['name'], 'meal_type' => 3, 'select_adjust' => 1, 'meal_order_id' => $this->model['id'], 'dishes_weight' => 0, 'dishes_price' => $packageFee, 'total_price' => $packageFee, 'plate_code' => '', 'equipment_code' => '', 'meal_time' => date('Y-m-d H:i:s'), 'pay_status' => PayStatusEnum::PENDING, ]; } $this->model->dishes()->saveAll($InitialMenu) !== false; } } name('user') ->where('ai_user_id', $ai_user_id) ->where('is_delete', 0) ->find(); if (empty($userInfoZhct)) { return []; } $userId = $userInfoZhct['id']; $field = ['id','order_no','bind_time','user_id','meal_date','meal_times','total_price','pay_price','meal_number','pay_status', 'source','pay_time','order_status','quota','quota_price','evaluate_status','package_fee','restaurant_method', 'locker_code','locker_address','locker_status','locker_cell_no']; $query = $db->name('meal_order') ->where('user_id','=', $userId) ->where('is_delete','=', 0); if (!empty($start_date)) { $query->where('meal_date', '>=', $start_date); } if (!empty($end_date)) { $query->where('meal_date', '<=', $end_date); } $data = $query->field($field) ->order(['bind_time' => 'desc']) ->paginate(15) ->toArray(); if (!empty($data['data'])) { $fileUrlPrefix = getFileUrlPrefix(); $meal_order_ids = array_column($data['data'], 'id'); $dishes = $db->name('initial_menu')->alias('initial_menu') ->join('dishes dishes', 'initial_menu.dishes_uuid = dishes.uuid') ->where('initial_menu.meal_order_id','in', $meal_order_ids) ->field('initial_menu.meal_order_id,dishes.id,dishes.name,dishes.file_path') ->select() ->toArray(); $dishesArr = []; if (!empty($dishes)) { foreach ($dishes as $key => $value) { $value['file_path'] = !empty($value['file_path']) ? str_replace("\\",'/',$fileUrlPrefix . $value['file_path']) : ''; $dishesArr[$value['meal_order_id']][] = $value; } } $takeStatusMap = $this->getTakeStatusMap($db, $meal_order_ids); foreach ($data['data'] as $key => $value) { $data['data'][$key]['goods'] = $dishesArr[$value['id']] ?? []; $data['data'][$key]['take_method'] = (int)($value['take_method'] ?? $value['restaurant_method'] ?? TakeMethodEnum::DINE_IN); $data['data'][$key] = array_merge($data['data'][$key], $takeStatusMap[$value['id']] ?? $this->getDefaultTakeStatus()); if ($data['data'][$key]['take_method'] === TakeMethodEnum::LOCKER) { $data['data'][$key] = array_merge($data['data'][$key], $this->formatLockerStatus((int)($value['locker_status'] ?? LockerStatusEnum::NONE))); } if ($value['order_status'] != OrderStatusEnum::COMPLETED && $value['quota'] == 1 && $value['total_price'] > 0) { $data['data'][$key]['total_price'] = $value['quota_price'];