OrderReserve.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. <?php
  2. namespace common\models\order;
  3. use addons\ReservationService\common\enums\ReservationServiceEnum;
  4. use addons\ReservationService\common\models\ReservationServiceOrder;
  5. use addons\SecondaryCard\common\models\SecondaryCard;
  6. use addons\Store\common\models\StoreOrderReserve;
  7. use common\enums\ClerkEnum;
  8. use common\enums\OrderStatusEnum;
  9. use common\enums\StatusEnum;
  10. use common\models\goods\Goods;
  11. use common\models\goods\GoodsReserve;
  12. use common\models\goods\GoodsReserveSku;
  13. use common\models\goods\GoodsReserveTime;
  14. use common\models\goods\GoodsReserveTimeSpec;
  15. use common\models\user\UserAddress;
  16. use Exception;
  17. use phpDocumentor\Reflection\Types\Static_;
  18. use Yii;
  19. /**
  20. * This is the model class for table "{{%order_reserve}}".
  21. * @property int $id ID
  22. * @property int $order_id 主订单id
  23. * @property int $store_id 门店id
  24. * @property int $mall_id 商城ID
  25. * @property int $user_id 用户id
  26. * @property int $time_id 预约时间id
  27. * @property int $time_spec_id 预约时间id
  28. * @property string $reserve_date_time 预约时间
  29. * @property int $reserve_time_range_type 预约模式(1按天预约 2按时间段预约)
  30. * @property int $province_id 省ID
  31. * @property int $city_id 市ID
  32. * @property int $district_id 区县ID
  33. * @property string $detail 详细地址
  34. * @property string $lat 纬度
  35. * @property string $lng 经度
  36. * @property int $service_type 服务方式(1上门 2到店)
  37. * @property int $order_status 订单状态
  38. * @property int $pay_status 订单付款状态
  39. * @property int $reserve_status 订单预约状态
  40. * @property int $feedback_status 售后状态
  41. * @property string $reserve_code 预约码
  42. * @property int $created_at 创建时间
  43. * @property int $updated_at 更新时间
  44. * @property int $reserve_type 更新时间
  45. * @property int $tech_id 更新时间
  46. * @property int $attr_id 更新时间
  47. * @property int $service_start_at 服务开始时间
  48. * @property int $service_end_at 服务结束时间
  49. * @property int $goods_id 商品id
  50. * @property int $reservation_service_order_id 预约服务订单id
  51. */
  52. class OrderReserve extends \common\models\base\BaseActiveRecord
  53. {
  54. const RESERVE_STATUS_WAIT = 0;
  55. const RESERVE_STATUS_PENDDING = 1;
  56. const RESERVE_STATUS_FINISH = 2;
  57. const CANCEL_TYPE_NOT = 1;
  58. const CANCEL_TYPE_TIME = 2;
  59. const CANCEL_TYPE_RATE = 3;
  60. /**
  61. * {@inheritdoc}
  62. */
  63. public static function tableName()
  64. {
  65. return '{{%order_reserve}}';
  66. }
  67. /**
  68. * {@inheritdoc}
  69. */
  70. public function rules()
  71. {
  72. return [
  73. [['order_id'], 'required'],
  74. [['order_id', 'store_id', 'mall_id', 'user_id', 'time_id', 'time_spec_id', 'reserve_time_range_type',
  75. 'province_id', 'city_id', 'district_id', 'service_type', 'order_status', 'pay_status', 'reserve_status', 'feedback_status',
  76. 'cancel_type', 'created_at', 'updated_at', 'reserve_type', 'tech_id', 'attr_id',
  77. 'service_start_at', 'service_end_at', 'goods_id', 'reservation_service_order_id'], 'integer'],
  78. [['lat', 'lng'], 'number'],
  79. [['reserve_date_time', 'detail', 'region_name', 'reserve_code','cancel_rule'], 'string', 'max' => 255]
  80. ];
  81. }
  82. /**
  83. * {@inheritdoc}
  84. */
  85. public function attributeLabels()
  86. {
  87. return [
  88. 'id' => 'ID',
  89. 'order_id' => '主订单id',
  90. 'store_id' => '门店id',
  91. 'mall_id' => '商城ID',
  92. 'user_id' => '用户id',
  93. 'time_id' => '预约时间id',
  94. 'time_spec_id' => '预约时间id',
  95. 'reserve_date_time' => '预约时间',
  96. 'reserve_time_range_type' => '预约模式(1按天预约 2按时间段预约)',
  97. 'province_id' => '省ID',
  98. 'city_id' => '市ID',
  99. 'district_id' => '区县ID',
  100. 'detail' => '详细地址',
  101. 'lat' => '纬度',
  102. 'lng' => '经度',
  103. 'region_name' => '地区字符串',
  104. 'service_type' => '服务方式(1上门 2到店)',
  105. 'order_status' => '订单状态',
  106. 'pay_status' => '订单付款状态',
  107. 'reserve_status' => '订单预约状态',
  108. 'feedback_status' => '售后状态',
  109. 'cancel_rule' => '取消退款规则',
  110. 'cancel_type' => '取消类型(1不允许取消 2服务开始前取消 3订单类型取消)',
  111. 'reserve_code' => '预约码',
  112. 'created_at' => '创建时间',
  113. 'updated_at' => '更新时间',
  114. 'reservation_service_order_id' => 'reservation_service_order_id',
  115. ];
  116. }
  117. /**
  118. * 预约商品订单处理
  119. * @param [type] $order
  120. * @param [type] $form
  121. * @return void
  122. */
  123. public static function create($order, $form)
  124. {
  125. try {
  126. $timeId = $form['data']['time_id'];
  127. $serviceType = $form['data']['service_type'];
  128. $latitude = $form['data']['latitude'];
  129. $longitude = $form['data']['longitude'];
  130. $address_id = $form['data']['address_id'] ?? 0;
  131. $timeSpecId = $form['data']['time_spec_id'];
  132. $tech_id = intval($form['data']['tech_id'] ?? 0);
  133. $orderDetail = $order->detail[0];
  134. $goodsId = $orderDetail['goods_id'];
  135. $skuId = $orderDetail['goods_attr_id'];
  136. $num = $orderDetail['num'];
  137. if (!empty($address_id)) {
  138. $user_address = UserAddress::findOne(['id' => $address_id]);
  139. if (!empty($user_address['latitude'])) {
  140. $latitude = $user_address['latitude'];
  141. }
  142. if (!empty($user_address['longitude'])) {
  143. $longitude = $user_address['longitude'];
  144. }
  145. }
  146. // 扣减时间或时间段的库存
  147. /**
  148. * @var $reserve GoodsReserve
  149. */
  150. $reserve = GoodsReserve::findOne(['goods_id' => $goodsId]);
  151. if (empty($reserve)) throw new \Exception('找不到该预约商品');
  152. $reserveSku = GoodsReserveSku::findOne(['id' => $skuId, 'goods_id' => $goodsId]);
  153. if (empty($reserveSku)) throw new \Exception('找不到该预约商品SKU');
  154. $goods = Goods::findOne(['id' => $goodsId]);
  155. $service_start_at = 0;
  156. $service_end_at = 0;
  157. if ($reserve->reserve_type == StatusEnum::DISABLED) {
  158. // 预约模式(1按天预约 2按时间段预约)
  159. $reserveTimeRangeType = $reserveSku->reserve_time_range_type;
  160. // 预约时间处理
  161. $time = GoodsReserveTime::findOne($timeId);
  162. if (empty($time)) throw new \Exception('找不到该预约时间日期');
  163. $date = $time->reserve_time;
  164. // if (empty($goods->is_attr)) {
  165. // if ($time->stock < $num) throw new \Exception('该预约时间日期已超过最大预约数量');
  166. // $time->stock -= $num;
  167. // $time->save();
  168. // }
  169. if ($reserveTimeRangeType == 2) {
  170. $timeSpec = GoodsReserveTimeSpec::findOne(['reserve_time_id' => $timeId, 'id' => $timeSpecId]);
  171. if (empty($timeSpec)) throw new \Exception('找不到该预约时间分片');
  172. $startHis = $timeSpec->start;
  173. $endHis = $timeSpec->end;
  174. $start = implode(' ', [$date, $startHis]);
  175. if ($timeSpec->is_next) $date = date('Y-m-d', strtotime('+1 day', strtotime($date)));
  176. $end = implode(' ', [$date, $endHis]);
  177. $reserveDateTime = implode(',', [$start, $end]);
  178. if ($timeSpec->stock < $num) throw new \Exception('该预约时间段已超过最大预约数量');
  179. $timeSpec->stock -= $num;
  180. $timeSpec->save();
  181. } else {
  182. $reserveDateTime = implode(',', [$date . ' 00:00:00', $date . '23:59:59']);
  183. }
  184. } else {
  185. $reserveTimeRangeType = 2; // 技师默认是按时间段预约的
  186. $reserveDateTime = $form['data']['select_time'] ?? '';
  187. if (!empty($reserveDateTime)) {
  188. $reserveDateTime .= ','.date('Y-m-d H:i', strtotime($reserveDateTime) + ($reserveSku->service_min * 60));
  189. $reserve_date_time_arr = explode(',', $reserveDateTime);
  190. if (!empty($reserve_date_time_arr)) {
  191. $service_start_at = strtotime($reserve_date_time_arr[0]);
  192. $service_end_at = strtotime($reserve_date_time_arr[1]);
  193. $res = OrderReserve::find()->where(['attr_id' => $skuId, 'service_start_at' => $service_start_at])
  194. ->andWhere(['tech_id' => $tech_id])
  195. ->andWhere(['!=', 'order_status', -4])
  196. ->select('tech_id')->one();
  197. if (!empty($res)) {
  198. throw new \Exception('该技师该时间已有预约');
  199. }
  200. }
  201. if (empty($goods->is_attr)) {
  202. // 查询今天有多少单
  203. $reserve_max_buy = $reserve->reserve_max_buy;
  204. } else {
  205. $reserve_max_buy = $reserveSku->reserve_max_buy;
  206. }
  207. $date = date('Y-m-d', $service_start_at);
  208. $start_time = strtotime($date . ' 00:00:00');
  209. $end_time = strtotime($date . ' 23:59:59');
  210. if (!empty($reserve_max_buy) && $reserve_max_buy <= self::find()->where(['attr_id' => $skuId])
  211. ->andWhere(['>=', 'service_start_at', $start_time])
  212. ->andWhere(['<=', 'service_start_at', $end_time])
  213. ->andWhere(['>=', 'order_status', 0])->count()) {
  214. throw new \Exception('今天已达到最大预约数');
  215. }
  216. }
  217. // 取出商品的结束时间
  218. }
  219. // 预约商品服务地址
  220. // service_type服务方式(1上门 2到店 3两者都兼容)
  221. if ($serviceType == GoodsReserve::RESERVE_SERVICE_TO) {
  222. // 上门服务使用订单地址
  223. $province = $order->province;
  224. $city = $order->city;
  225. $area = $order->area;
  226. $address = $order->address;
  227. $lat = $latitude;
  228. $lng = $longitude;
  229. $regionName = $order->region_name;
  230. } else {
  231. // 到店使用商品地址
  232. $province = $reserve->province_id;
  233. $city = $reserve->city_id;
  234. $area = $reserve->district_id;
  235. $address = $reserve->detail;
  236. $lat = $reserve->lat;
  237. $lng = $reserve->lng;
  238. $regionName = '';
  239. }
  240. if (empty($province) || empty($city) || empty($area) || empty($address) || empty($lat) || empty($lng)) {
  241. throw new \Exception('服务地址或定位信息错误');
  242. }
  243. $model = new OrderReserve();
  244. $model->loadDefaultValues();
  245. $model->isNewRecord = true;
  246. $model->order_id = $order->id;
  247. $model->store_id = $order->store_id;
  248. $model->mall_id = $order->mall_id;
  249. $model->user_id = $order->user_id;
  250. $model->time_id = $timeId ?? 0;
  251. $model->time_spec_id = $timeSpecId ?? 0;
  252. $model->reserve_date_time = !empty($reserveDateTime) ? $reserveDateTime : '';
  253. $model->reserve_time_range_type = !empty($reserveTimeRangeType) ? $reserveTimeRangeType : 0;
  254. $model->province_id = $province;
  255. $model->city_id = $city;
  256. $model->district_id = $area;
  257. $model->detail = $address;
  258. $model->lat = $lat;
  259. $model->lng = $lng;
  260. $model->region_name = $regionName;
  261. $model->service_type = $serviceType;
  262. $model->order_status = $order->order_status;
  263. $model->pay_status = $order->pay_status;
  264. $model->cancel_type = $reserve->cancel_type;
  265. $model->cancel_rule = $reserve->cancel_rule;
  266. $model->reserve_type = $reserve->reserve_type;
  267. $model->tech_id = $tech_id;
  268. $model->attr_id = $skuId;
  269. $model->service_start_at = $service_start_at;
  270. $model->service_end_at = $service_end_at;
  271. $model->goods_id = $goodsId;
  272. if (!$model->save()) throw new Exception($model->getErrorMessage());
  273. return true;
  274. } catch (Exception $e) {
  275. if (isset($trans)) $trans->rollback();
  276. self::$static_error = $e->getMessage();
  277. return false;
  278. }
  279. }
  280. /**
  281. * 同步订单状态
  282. * @param [type] $order
  283. * @return void
  284. */
  285. public static function syncOrderStatus($order, $tech_id = null)
  286. {
  287. $orderDetail = $order->detail[0];
  288. $num = $orderDetail['num'];
  289. if (empty($order['store_id'])) {
  290. $model = self::findOne(['order_id' => $order->id]);
  291. } else {
  292. $model = StoreOrderReserve::findOne(['main_order_id' => $order->id]);
  293. }
  294. $oldStatus = $model->order_status;
  295. $model->order_status = $order->order_status;
  296. $model->feedback_status = $order->is_feedback;
  297. $model->pay_status = $order->pay_status;
  298. $timeId = $model->time_id;
  299. $timeSpecId = $model->time_spec_id;
  300. $reserveTimeRangeType = $model->reserve_time_range_type;
  301. if (!empty($tech_id) && empty($model->tech_id)) $model->tech_id = $tech_id;
  302. if ($model->order_status === OrderStatusEnum::SHIPMENTS) {
  303. // 预约商品订单支付后,直接变成已发货状态
  304. // 订单支付,时订单处于关闭状态的话,重新扣减可预约数量
  305. // 生成预约码
  306. // $model->reserve_code = date('YmdHis') . $model->id;
  307. if(empty($model->reserve_code)){
  308. if($order->clerk_code){
  309. $reserve_code = $order->clerk_code;
  310. $model->reserve_code = $reserve_code;
  311. }else{
  312. if($order->marketing_type!='SecondaryCard' && $order->order_source=='Reserve'){
  313. $model->reserve_code = self::getClerkCode($order['mall_id'],ClerkEnum::RESERVATION_SERVICE_CLERK_CODE_PRE);
  314. }
  315. }
  316. }
  317. if ($oldStatus === OrderStatusEnum::REPEAL) {
  318. self::handleTimeStock($timeId, $timeSpecId, $reserveTimeRangeType, $num * -1);
  319. }
  320. }
  321. if ($model->order_status === OrderStatusEnum::SING) {
  322. // 预约商品核销订单
  323. $model->reserve_status = self::RESERVE_STATUS_PENDDING;
  324. }
  325. if ($model->order_status === OrderStatusEnum::REPEAL) {
  326. // 订单关闭,归还可预约数量
  327. self::handleTimeStock($timeId, $timeSpecId, $reserveTimeRangeType, $num);
  328. }
  329. if ($model->order_status === OrderStatusEnum::ACCOMPLISH) {
  330. // 订单完成
  331. $model->reserve_status = self::RESERVE_STATUS_FINISH;
  332. }
  333. if (!empty($order['store_id'])) {
  334. $where = [
  335. ['mall_id' => $order['mall_id']],
  336. ['order_id' => $order->id],
  337. ['status' => StatusEnum::ENABLED],
  338. ['store_id' => $order['store_id']]
  339. ];
  340. $reservation_service_order_model = ReservationServiceOrder::getOne($where);
  341. if (!empty($reservation_service_order_model)) {
  342. if (empty($reservation_service_order_model['secondary_card_id'])) {
  343. $reservation_service_order_model->server_status = ReservationServiceEnum::COMPLETED;
  344. $reservation_service_order_model->service_status = ReservationServiceEnum::RESERVE_ORDER_STATUS_FINISH;
  345. $reservation_service_order_model->save();
  346. } else {
  347. $secondary_card_model = SecondaryCard::find()->where(['id' => $reservation_service_order_model['secondary_card_id']])->one();
  348. if (!empty($secondary_card_model)) {
  349. if ($secondary_card_model['reservation_model'] == 2) {
  350. if (in_array($reservation_service_order_model->server_status, [ReservationServiceEnum::ARRIVED_AT_STORE, ReservationServiceEnum::ALREADY_BOOKED])) {
  351. $reservation_service_order_model->server_status = ReservationServiceEnum::TO_BE_ACCEPTED;
  352. $reservation_service_order_model->service_status = ReservationServiceEnum::RESERVE_ORDER_STATUS_UNDERWRITER;
  353. $reservation_service_order_model->save();
  354. } elseif ($reservation_service_order_model->server_status == ReservationServiceEnum::TO_BE_ACCEPTED) {
  355. $reservation_service_order_model->server_status = ReservationServiceEnum::COMPLETED;
  356. $reservation_service_order_model->service_status = ReservationServiceEnum::RESERVE_ORDER_STATUS_FINISH;
  357. $reservation_service_order_model->save();
  358. }
  359. } else if ($secondary_card_model['reservation_model'] == 1) {
  360. if ($reservation_service_order_model->service_status == ReservationServiceEnum::RESERVE_ORDER_STATUS_UNDERWRITER) {
  361. $reservation_service_order_model->server_status = ReservationServiceEnum::COMPLETED;
  362. $reservation_service_order_model->service_status = ReservationServiceEnum::RESERVE_ORDER_STATUS_FINISH;
  363. $reservation_service_order_model->save();
  364. }
  365. }
  366. }
  367. }
  368. }
  369. }
  370. if (!$model->save()) throw new Exception($model->getErrorMessage());
  371. return true;
  372. }
  373. public static function handleTimeStock($timeId, $timeSpecId, $reserveTimeRangeType, $num)
  374. {
  375. $time = GoodsReserveTime::findOne($timeId);
  376. if (empty($time)) return;
  377. $time->stock += $num;
  378. $time->save();
  379. if ($reserveTimeRangeType == 2) {
  380. $timeSpec = GoodsReserveTimeSpec::findOne(['reserve_time_id' => $timeId, 'id' => $timeSpecId]);
  381. if (empty($timeSpec)) return;
  382. $timeSpec->stock += $num;
  383. $timeSpec->save();
  384. }
  385. }
  386. public static function getClerkCode($mall_id,$pre)
  387. {
  388. while (true) {
  389. $clerk_code = ClerkEnum::getCode($pre);
  390. $res = self::findOne(['mall_id' => $mall_id, 'reserve_code' => $clerk_code]);
  391. if (empty($res)) break;
  392. }
  393. if (empty($clerk_code)) throw new \Exception('核销码生成失败');
  394. return $clerk_code;
  395. }
  396. }