StoreOrderReserve.php 16 KB

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