UserOrderService.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. <?php
  2. namespace app\service;
  3. use app\common\model\Area;
  4. use app\common\model\SysConfig;
  5. use app\common\model\User;
  6. use app\common\model\UserCoupon;
  7. use app\common\model\UserOrder;
  8. class UserOrderService{
  9. protected $order;
  10. protected $user;
  11. protected $coupon;
  12. /** @var UserCoupon */
  13. protected $user_coupon;
  14. protected $config=[];
  15. protected $cage_spec;
  16. /** @var Area */
  17. protected $from_area;
  18. /** @var Area */
  19. protected $to_area;
  20. /** @var array */
  21. protected $pet_category;
  22. /** @var UserOrderLog */
  23. protected $log;
  24. public function setLog(UserOrderLog $log)
  25. {
  26. $this->log = $log;
  27. return $this;
  28. }
  29. /**
  30. * @param UserCoupon $coupon
  31. */
  32. public function setCoupon($coupon)
  33. {
  34. if($coupon) {
  35. $c = $this->getUser()->coupon()->use()->find($coupon);
  36. if (!$c) {
  37. throw_user('优惠券不存在');
  38. }
  39. $this->user_coupon = $c;
  40. }
  41. return $this;
  42. }
  43. public function __construct()
  44. {
  45. bcscale(2);
  46. $this->config=SysConfig::look('price_config',config('process'));
  47. }
  48. /**
  49. * @return User
  50. */
  51. public function getUser()
  52. {
  53. return $this->user;
  54. }
  55. /**
  56. * @param mixed $user
  57. */
  58. public function setUser(User $user)
  59. {
  60. $this->user = $user;
  61. $this->coupon = $user->coupon()->use()->select();
  62. return $this;
  63. }
  64. public function checkArea($area){
  65. $city=tm()->getLocation($area['longitude'],$area['latitude']);
  66. return $city['city'];
  67. }
  68. protected function area(){
  69. $from_area=$this->checkArea($this->order['from_addr']);
  70. $to_area=$this->checkArea($this->order['to_addr']);
  71. $this->order['from_city']=$from_area;
  72. $this->order['to_city']=$to_area;
  73. $database_from_area=Area::city()->where('name',$this->order['from_city'])->find();
  74. if(!$database_from_area){
  75. throw_user("数据库未找到取宠城市{$this->order['from_city']}");
  76. }
  77. $this->from_area=$database_from_area;
  78. $database_to_area=Area::city()->where('name',$this->order['to_city'])->find();
  79. if(!$database_to_area){
  80. throw_user("数据库未找到送宠城市{$this->order['to_city']}");
  81. }
  82. $this->to_area=$database_to_area;
  83. $this->order['from_longitude']=$this->order['from_addr']['longitude'];
  84. $this->order['from_latitude']=$this->order['from_addr']['latitude'];
  85. $this->order['to_longitude']=$this->order['to_addr']['longitude'];
  86. $this->order['to_latitude']=$this->order['to_addr']['latitude'];
  87. $this->recordLog("[{$this->order['from_city']}]-[{$this->order['to_city']}]");
  88. #检查空运有没有设置机场坐标
  89. $noneAir=!$this->hasAir($this->order['pick_up']);
  90. if($this->order['freight']==UserOrder::FREIGHT_AIR && $noneAir){
  91. throw_user('未找到机场,无法选择此方式运输');
  92. }
  93. }
  94. protected function distance(){
  95. $this->order['distance']=ll_distance(
  96. $this->order['from_longitude'],
  97. $this->order['from_latitude'],
  98. $this->order['to_longitude'],
  99. $this->order['to_latitude']
  100. );
  101. $this->recordLog("距离:{$this->order['distance']}km");
  102. }
  103. public function import(&$data){
  104. list($data['agree_date'],$data['agree_time'])=explode(' ',$data['agree_time']);
  105. $this->pet_category=Pet::category()[$data['pet_category']]??null;
  106. $data['pet_category']=$this->pet_category['name']??throw_user("宠物类目不存在");
  107. $this->cage_spec=Pet::spec()[$data['spec']]??throw_user('宠具规格不存在');
  108. $data['spec']=$this->cage_spec['name'];
  109. if(!empty($data['protect_id'])) {
  110. $protect=Pet::protect()[$data['protect_id']];
  111. $data['protect_amount'] = $protect['price'];
  112. $data['protect_max'] = $protect['max'];
  113. }
  114. else{
  115. #防止报错
  116. $data['protect_id']=null;
  117. }
  118. #没有优惠券置为null
  119. if(empty($data['coupon_id'])){
  120. $data['coupon_id']=null;
  121. }
  122. #没有选配送方式置为null
  123. if(!isset($data['freight'])){
  124. $data['freight']=null;
  125. }
  126. $data['coupon_amount']=0;
  127. $data['discount_amount']=0;
  128. $data['total_amount']=0;
  129. $data['real_amount']=0;
  130. $this->order=$data;
  131. $this->area();
  132. $this->distance();
  133. return $this;
  134. }
  135. function prepare(){
  136. $items=[];
  137. $total_amount=0;
  138. $freight_amount=0;
  139. $freights=UserOrder::$freights;
  140. if(!$this->pet_category['allow_air']){
  141. unset($freights[UserOrder::FREIGHT_AIR]);
  142. if($this->order['freight']==UserOrder::FREIGHT_AIR){
  143. throw_user('该宠物不支持空运');
  144. }
  145. }
  146. foreach ($freights as $type=>$_){
  147. $item=[
  148. 'type'=>$type,//类型
  149. 'price'=>0,#实付
  150. 'total_price'=>0,#单个类型支付总价
  151. //'coupon'=>[],//可用的优惠券列表
  152. ];
  153. $item['total_price']=$this->{$type}();
  154. $item['price']=$item['total_price'];
  155. $items[$type]=$item;
  156. if($type==$this->order['freight']){
  157. $total_amount=$freight_amount=bcadd($total_amount,$item['total_price']);
  158. }
  159. }
  160. $protect=Pet::protect()[$this->order['protect_id']]??[];
  161. $protect_price=$protect['price']??0;
  162. $total_amount=bcadd($total_amount,$protect['price']??0);
  163. #用户折扣
  164. $user_level_discount_money=$freight_amount;
  165. if($this->order['freight']==UserOrder::FREIGHT_AIR){
  166. $user_level_discount_money=$this->order['first_air_amount'];
  167. }
  168. $level_amount=bcmul($this->user->level_discount,$user_level_discount_money);
  169. $this->order['level_amount']=$level_amount;
  170. $this->recordLog("运费:{$freight_amount},折扣:{$this->user->level_discount},用户折扣:{$level_amount}");
  171. $freight_amount=bcsub($freight_amount,$level_amount);
  172. #券金额
  173. if($this->user_coupon){
  174. if(!$this->user_coupon->fit($freight_amount)){
  175. throw_user('优惠券不满足使用条件');
  176. }
  177. if($this->user->is_vip && $this->order['freight']==UserOrder::FREIGHT_AIR){
  178. throw_user('您已是会员,当空运时无法使用优惠券折扣');
  179. }
  180. $this->order['coupon_amount']=$this->user_coupon->amount($freight_amount);
  181. $this->recordLog("优惠券:".json_encode($this->user_coupon,JSON_UNESCAPED_UNICODE));
  182. }
  183. $freight_amount=bcsub($freight_amount,$this->order['coupon_amount']);
  184. #优惠总金额
  185. $this->order['discount_amount']=bcadd($this->order['coupon_amount'],$this->order['level_amount']);
  186. $this->order['real_amount']=bcadd($freight_amount,$protect_price);
  187. $this->order['total_amount']=$total_amount;
  188. $this->order['freights']=$items;
  189. #可用的优惠券
  190. $coupon=[];
  191. if($this->order['freight']!=UserOrder::FREIGHT_AIR) {
  192. foreach ($this->coupon as $_coupon) {
  193. if ($_coupon->fit($this->order['total_amount'])) {
  194. $coupon[] = $_coupon;
  195. }
  196. }
  197. }
  198. $this->order['allow_coupons']=$coupon;
  199. return $this->order;
  200. }
  201. protected function fast(){
  202. #距离
  203. $distance=$this->order['distance'];
  204. $start_kilo=$this->config['fast']['start_kilo'];
  205. $start_amount=$this->config['fast']['start_amount'];
  206. if($distance<=$start_kilo){
  207. $distance_price=$start_amount;
  208. }else{
  209. $out_price=$start_amount;
  210. foreach ($this->config['fast']['detail'] as $detail){
  211. if($distance>=$detail['start_kilo'] && $distance<$detail['end_kilo']){
  212. $out_price=bcadd($out_price,bcmul($detail['amount'],$distance));
  213. break;
  214. }
  215. }
  216. $distance_price=$out_price;
  217. }
  218. #笼子百分比算出运费
  219. $cage_float=$this->cage_spec['per']/100;
  220. $cage_float_price=bcmul($distance_price,$cage_float);
  221. #宠具+运费
  222. $cage_price=$this->cage();
  223. $total = bcadd($cage_float_price,$cage_price);
  224. #件数,多只按百分比算优惠,按笼子数量
  225. $num=$this->order['piece'];
  226. $per=$this->config['fast']['out_one_per'];
  227. $out_float=bcdiv($per,100);
  228. $num_price=bcmul(bcmul($out_float,$cage_float_price),$num-1);
  229. $total=bcadd($total,$num_price);
  230. $this->recordLog("快车-起步距离:{$start_kilo},起步价:{$start_amount},距离总价:{$distance_price},宠具百分比:{$cage_float}," .
  231. "笼子百分比算出的运费:{$cage_float_price},宠具价格:{$cage_price},".
  232. "只数:{$num},超出百分比:{$this->config['fast']['out_one_per']},超出一只总价:{$num_price}".
  233. "总价:{$total}"
  234. );
  235. return $total;
  236. }
  237. protected function air(){
  238. $total=0;
  239. #基础价格
  240. $base_get_price=$this->from_area['first_air_amount'];
  241. $base_send_price=0;
  242. $start_amount=$this->config['air']['start_amount']??null;
  243. if(is_null($base_get_price)){
  244. if(is_null($start_amount)){
  245. throw_user('未设置出港费');
  246. }
  247. $base_get_price=$start_amount;
  248. }
  249. $base_price=bcadd($base_get_price,$base_send_price);
  250. $total=bcadd($total,$base_price);
  251. #多只出港费半价,按宠物数量
  252. $num=$this->order['num'];
  253. $out_one_per=$this->config['air']['out_one_per'];
  254. $out_one_float=$out_one_per/100;
  255. $first_out_price=bcmul(bcmul($base_price,$out_one_float),$num-1);
  256. $total=bcadd($total,$first_out_price);
  257. $this->recordLog("空运-出港费设置比例:{$out_one_per}%,额外出港费:{$first_out_price}");
  258. $this->order['first_air_amount']=bcadd($first_out_price,$base_price);
  259. #距离
  260. #上门接
  261. $get_price=0;
  262. $send_price=0;
  263. $get_send_price=0;
  264. if($this->order['pick_up']==1 && $this->hasAir(1)) {
  265. $get_price=$this->pickup($this->from_area['air_longitude'],$this->from_area['air_latitude'],$this->order['from_longitude'],$this->order['from_latitude']);
  266. $total=bcadd($total,$get_price);
  267. $get_send_price=$get_price;
  268. }
  269. #上门送
  270. elseif ($this->order['pick_up']==2 && $this->hasAir(2)){
  271. $send_price=$this->pickup($this->to_area['air_longitude'],$this->to_area['air_latitude'],$this->order['to_longitude'],$this->order['to_latitude']);
  272. $total=bcadd($total,$send_price);
  273. $get_send_price=$send_price;
  274. }
  275. #上门接送
  276. elseif ($this->order['pick_up']==4 && $this->hasAir(4)){
  277. $get_price=$this->pickup($this->from_area['air_longitude'],$this->from_area['air_latitude'],$this->order['from_longitude'],$this->order['from_latitude']);
  278. $total=bcadd($total,$get_price);
  279. $send_price=$this->pickup($this->to_area['air_longitude'],$this->to_area['air_latitude'],$this->order['to_longitude'],$this->order['to_latitude']);
  280. $total=bcadd($total,$send_price);
  281. $get_send_price=bcadd($get_price,$send_price);
  282. }
  283. #机场代提费
  284. $take_price=0;
  285. if(in_array($this->order['pick_up'],[2,4])){
  286. //$take_price=$this->config['air']['send']['default_amount'];
  287. if($this->to_area['second_air_amount']){
  288. $take_price=$this->to_area['second_air_amount'];
  289. }
  290. $total=bcadd($total,$take_price);
  291. }
  292. #宠具
  293. $cage_price=$this->cage();
  294. $total=bcadd($total,$cage_price);
  295. #重量
  296. $weight=$this->order['weight'];
  297. $avgWeight=bcdiv($weight,$num);
  298. $base_weight=$this->config['air']['weight']['base_weight'];
  299. $out_weight_price=0;
  300. $out_price=0;
  301. if($avgWeight>$base_weight){
  302. $out_weight=bcsub($weight,$base_weight);
  303. $out_weight_price=$this->config['air']['weight']['out_amount'];
  304. $out_price=bcmul($out_weight,$out_weight_price);
  305. $total=bcadd($total,$out_price);
  306. }
  307. $this->recordLog("空运-从地区取基础价格(出港费):{$base_price},默认起步价:{$start_amount},上门接:{$get_price},上门送:{$send_price},".
  308. "上门接送:{$get_send_price},|地区{$this->to_area['second_air_amount']},".
  309. "宠具价格:{$cage_price},重量:{$weight},平均重量:{$avgWeight},基础重量:{$base_weight},超出后每KG价格:{$out_weight_price},重量总价:{$out_price},".
  310. "总价:{$total}"
  311. );
  312. return $total;
  313. }
  314. protected function special(){
  315. #距离
  316. $distance=$this->order['distance'];
  317. $start_kilo=$this->config['special']['start_kilo'];
  318. $start_amount=$this->config['special']['start_amount'];
  319. if($distance<=$start_kilo){
  320. $distance_price=$start_amount;
  321. }else{
  322. $distance_price=bcmul(bcsub($distance,$start_kilo),$this->config['special']['out_amount']);
  323. $distance_price=bcadd($start_amount,$distance_price);
  324. }
  325. #笼具
  326. $cage_price=$this->cage();
  327. $price=bcadd($distance_price,$cage_price);
  328. $this->recordLog("专车-起步距离:{$start_kilo},起步价:{$start_amount},距离价格:{$distance_price},宠具价格:{$cage_price},总价:{$price}");
  329. return $price;
  330. }
  331. protected function cage(){
  332. if($this->order['has_cage']){
  333. return 0;
  334. }
  335. $cage_num=$this->order['piece'];
  336. return bcmul($cage_num,$this->cage_spec['amount']);
  337. }
  338. protected function pickup($from_longitude,$from_latitude,$to_longitude,$to_latitude){
  339. $this->recordLog("上门接送:$from_latitude,$from_latitude,$to_longitude,$to_latitude");
  340. $distance=ll_distance($from_longitude,$from_latitude,$to_longitude,$to_latitude);
  341. $get_default_price = $this->config['air']['pickup']['start_amount'];
  342. $get_default_kilo = $this->config['air']['pickup']['start_distance'];
  343. if($distance<=$get_default_kilo){
  344. $get_price=$get_default_price;
  345. }else{
  346. $get_price=$get_default_price;
  347. $max_kilo=$this->config['air']['pickup']['out_kilo_num'];
  348. $every_kilo_amount=$this->config['air']['pickup']['every_kilo_amount'];
  349. $out_kilo_amount=$this->config['air']['pickup']['out_kilo_amount'];
  350. if($distance<=$max_kilo){
  351. $get_price=bcadd($get_price,bcmul($every_kilo_amount,$distance));
  352. }else{
  353. $get_price=bcadd($get_price,bcmul($every_kilo_amount,$max_kilo));
  354. $get_price=bcadd($get_price,bcmul($out_kilo_amount,bcsub($distance,$max_kilo)));
  355. }
  356. }
  357. return $get_price;
  358. }
  359. protected function hasAir($pickup){
  360. switch ($pickup){
  361. case 0:
  362. return true;
  363. case 1:
  364. return $this->from_area->has_air_coordinate;
  365. case 2:
  366. return $this->to_area->has_air_coordinate;
  367. case 4:
  368. return $this->from_area->has_air_coordinate && $this->to_area->has_air_coordinate;
  369. default:
  370. throw_user('上门接送类型有误');
  371. }
  372. throw_user('上门接送类型有误');
  373. }
  374. protected function recordLog($content){
  375. $this->log && $this->log->setContent($content);
  376. }
  377. public function __destruct()
  378. {
  379. $this->recordLog("orderInfo:".json_encode($this->order,JSON_UNESCAPED_UNICODE));
  380. $this->log && $this->log->write();
  381. }
  382. }