ShippingTemplateRepository.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2024 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\common\repositories\store\shipping;
  12. use app\common\repositories\BaseRepository;
  13. use app\common\dao\store\shipping\ShippingTemplateDao as dao;
  14. use app\common\repositories\store\product\ProductRepository;
  15. use think\exception\ValidateException;
  16. use think\facade\Db;
  17. /**
  18. * Class ShippingTemplateRepository
  19. *
  20. * @mixin dao
  21. */
  22. class ShippingTemplateRepository extends BaseRepository
  23. {
  24. /**
  25. * ShippingTemplateRepository constructor.
  26. * @param dao $dao
  27. */
  28. public function __construct(dao $dao)
  29. {
  30. $this->dao = $dao;
  31. }
  32. /**
  33. * 根据商家ID获取列表
  34. *
  35. * 此方法通过商家ID从数据访问对象(DAO)获取相关列表,并对列表中的项目进行处理,标记系统默认项目。
  36. * 主要用于在前端展示时,区分系统默认项目与其他项目。
  37. *
  38. * @param int $merId 商家ID,用于查询特定商家的数据列表。
  39. * @return array 返回处理后的列表,包含每个项目的详细信息。
  40. */
  41. public function getList(int $merId)
  42. {
  43. // 从DAO获取商家ID对应的列表
  44. $list = $this->dao->getList($merId);
  45. // 遍历列表,标记系统默认项目
  46. foreach ($list as &$item) {
  47. // 如果项目被设置为默认,则在其名称后添加系统默认标记
  48. if ($item['is_default'] == 1) {
  49. $item['name'] .= '(系统默认)';
  50. }
  51. }
  52. // 返回处理后的列表
  53. return $list;
  54. }
  55. /**
  56. * 检查指定商户是否存在
  57. *
  58. * 本函数通过调用DAO层的方法来查询指定商户ID是否在数据库中存在。
  59. * 主要用于验证商户ID的合法性和唯一性,确保后续操作的准确性。
  60. *
  61. * @param int $merId 商户的唯一标识ID
  62. * @param mixed $id 需要验证存在的ID,这个参数的具体类型取决于业务逻辑,可以是用户的ID等
  63. * @return bool 返回true表示指定的商户ID存在,返回false表示不存在
  64. */
  65. public function merExists(int $merId,$id)
  66. {
  67. // 调用DAO层的方法来检查指定的商户ID和主键ID是否存在
  68. return $this->dao->merFieldExists($merId,$this->getPk(),$id);
  69. }
  70. /**
  71. * 检查默认记录是否存在
  72. *
  73. * 本函数用于确定给定商家ID和ID对应的默认记录是否已存在。
  74. * 它通过构建查询条件并查询数据对象来实现这一目的。
  75. * 如果存在匹配的默认记录,则返回true,否则返回false。
  76. *
  77. * @param int $merId 商家ID,用于限定查询的商家范围
  78. * @param mixed $id 需要检查的记录ID,可以是任意类型的值,取决于数据表的主键类型
  79. * @return bool 如果默认记录存在则返回true,否则返回false
  80. */
  81. public function merDefaultExists(int $merId,$id)
  82. {
  83. // 构建查询条件,包括商家ID、是否为默认记录以及主键ID
  84. $where = ['mer_id' => $merId,'is_default' => 1, $this->dao->getPk() => $id];
  85. // 根据构建的查询条件查询数据对象,并根据查询结果的存在与否返回对应的布尔值
  86. return $this->dao->getWhere($where) ? true : false;
  87. }
  88. /**
  89. * 检查商品是否存在
  90. *
  91. * 该方法用于验证指定商家ID和商品ID对应的商品是否存在于临时商品列表中。
  92. * 主要用于场景如活动商品的临时展示和管理,确保商家和商品信息的准确性。
  93. *
  94. * @param int $merId 商家ID,用于指定查询的商家范围。
  95. * @param int $id 商品ID,用于指定查询的商品。
  96. * @return bool 返回true表示商品存在,返回false表示商品不存在。
  97. */
  98. public function getProductUse(int $merId ,int $id)
  99. {
  100. // 通过依赖注入的方式获取ProductRepository实例,并调用其merTempExists方法检查商品是否存在
  101. return app()->make(ProductRepository::class)->merTempExists($merId,$id);
  102. }
  103. /**
  104. * 根据ID获取一条信息,包括关联数据。
  105. *
  106. * 此方法用于根据提供的ID从数据库中检索一条记录,并根据是否在API上下文中加载不同的关联数据。
  107. * 它默认加载‘free’,‘region’和‘undelives’三个关联数据集,根据API的上下文,可能需要加载这些集合中城市名称的额外信息。
  108. *
  109. * @param int $id 主键ID,用于定位特定记录。
  110. * @param int $api 一个标志,当为1时,表示此请求是在API上下文中,会加载额外的城市名称信息。
  111. * @return object 返回包含所需数据的数据库记录对象。
  112. */
  113. public function getOne(int $id, $api = 0)
  114. {
  115. // 定义需要加载的关联数据集
  116. $with = ['free','region','undelives'];
  117. // 根据ID和关联数据集查询记录
  118. $result = $this->dao->getWhere([$this->dao->getPk() => $id],'*',$with);
  119. // 根据API上下文,确定是否需要加载额外的城市名称信息
  120. if ($api){
  121. // 在API上下文中,如果关联数据存在,则准备加载城市名称的属性
  122. if ($result['free']) $append[] = 'free.city_name';
  123. if ($result['region']) $append[] = 'region.city_name';
  124. if ($result['undelives']) $append[] = 'undelives.city_name';
  125. } else {
  126. // 在非API上下文中,加载城市ID的属性
  127. $append = ['free.city_ids','region.city_ids','undelives.city_ids'];
  128. }
  129. // 添加额外的属性到查询结果中
  130. $result->append($append);
  131. // 返回处理后的查询结果
  132. return $result;
  133. }
  134. /**
  135. * 根据条件搜索信息。
  136. *
  137. * 本函数用于根据给定的商家ID、搜索条件、分页信息和每页数量,从数据库中检索相关信息。
  138. * 它首先构造一个查询,然后获取符合条件的记录总数,接着根据分页信息获取具体的数据列表。
  139. * 最后,对列表中的默认项进行标记,并返回包含总数和列表的数据结构。
  140. *
  141. * @param int $merId 商家ID,用于限定搜索范围。
  142. * @param array $where 搜索条件,一个包含搜索关键字的数组。
  143. * @param int $page 当前页码,用于分页查询。
  144. * @param int $limit 每页记录数,用于分页查询。
  145. * @return array 返回一个包含总数和列表的数组,列表中的每个项可能被标记为系统默认。
  146. */
  147. public function search(int $merId, array $where, int $page, int $limit)
  148. {
  149. // 构造查询条件,根据商家ID和搜索条件查询数据
  150. $query = $this->dao->search($merId, $where);
  151. // 计算符合条件的记录总数
  152. $count = $query->count($this->dao->getPk());
  153. // 根据分页信息获取具体的数据列表
  154. $list = $query->page($page, $limit)->select();
  155. // 遍历列表,对默认项进行标记
  156. foreach ($list as &$item) {
  157. if ($item['is_default'] == 1) {
  158. $item['name'] .= '(系统默认)';
  159. }
  160. }
  161. // 返回包含总数和列表的数据结构
  162. return compact('count', 'list');
  163. }
  164. /**
  165. * 更新配送模板信息。
  166. *
  167. * 该方法用于根据给定的ID和数据数组来更新配送模板的详细信息。
  168. * 它首先从数据数组中提取区域、免费配送和未送达信息,然后更新数据库中相应字段。
  169. * 同时,它还会根据新的配置删除旧的区域、免费配送和未送达规则,并根据需要插入新的规则。
  170. * 这个过程在一个数据库事务中完成,以确保数据的一致性。
  171. *
  172. * @param int $id 配送模板的ID。
  173. * @param array $data 包含更新后配送模板信息的数据数组。
  174. */
  175. public function update(int $id,array $data)
  176. {
  177. // 开始一个数据库事务,并传递ID和数据数组作为使用变量。
  178. Db::transaction(function()use ($id,$data) {
  179. // 从数据数组中提取区域、免费配送和未送达信息。
  180. $region = $data['region'];
  181. $free = $data['free'] ?? '';
  182. $undelives = $data['undelives']??'';
  183. // 从数据数组中移除不需要的字段,准备更新配送模板的其他信息。
  184. unset($data['region'],$data['free'],$data['undelives'],$data['city_ids']);
  185. // 使用DAO更新配送模板的基本信息。
  186. $this->dao->update($id, $data);
  187. // 删除与当前模板ID相关的所有区域、免费配送和未送达规则。
  188. (app()->make(ShippingTemplateRegionRepository::class))->batchRemove([], [$id]);
  189. (app()->make(ShippingTemplateFreeRepository::class))->batchRemove([], [$id]);
  190. (app()->make(ShippingTemplateUndeliveRepository::class))->batchRemove([], [$id]);
  191. // 如果配置了指定配送,則根据免费配送信息创建新的免费配送规则。
  192. if($data['appoint']) {
  193. $settlefree = $this->settleFree($free, $id);
  194. (app()->make(ShippingTemplateFreeRepository::class)->insertAll($settlefree));
  195. }
  196. // 根据新的区域配置,创建并插入新的区域配送规则。
  197. $settleRegion = $this->settleRegion($region,$id);
  198. (app()->make(ShippingTemplateRegionRepository::class)->insertAll($settleRegion));
  199. // 如果配置了未送达处理,根据未送达信息创建新的未送达规则。
  200. if($data['undelivery'] == 1){
  201. $settleUndelives = $this->settleUndelives($undelives,$id);
  202. (app()->make(ShippingTemplateUndeliveRepository::class))->create($settleUndelives);
  203. }
  204. });
  205. }
  206. /**
  207. * 删除记录
  208. *
  209. * 本函数用于根据提供的ID删除一条或多条记录。它首先检查传入的ID是否为数组,
  210. * 如果是数组,则遍历每个ID,并调用DAO对象的delete方法来逐个删除这些记录。
  211. * 如果传入的ID不是数组,那么它直接调用DAO对象的delete方法来删除对应的记录。
  212. * 这种设计允许灵活地处理单个或多个记录的删除操作,简化了删除操作的代码。
  213. *
  214. * @param mixed $id 要删除的记录的ID。它可以是一个整数或一个整数数组。
  215. */
  216. public function delete($id)
  217. {
  218. // 检查传入的ID是否为数组
  219. if (is_array($id)) {
  220. // 如果是数组,则遍历每个ID进行删除
  221. foreach ($id as $i) {
  222. $this->dao->delete($i);
  223. }
  224. } else {
  225. // 如果不是数组,则直接删除单个ID对应的记录
  226. $this->dao->delete($id);
  227. }
  228. }
  229. /**
  230. * 创建配送模板
  231. * @param array $data 配送模板相关数据
  232. * 包含区域、是否免费、未配送处理方式等信息
  233. */
  234. public function create(array $data)
  235. {
  236. // 使用数据库事务确保操作的原子性
  237. Db::transaction(function() use ($data) {
  238. // 提取区域信息
  239. $region = $data['region'];
  240. // 提取是否免费配送的信息,默认为空字符串
  241. $free = $data['free'] ?? '';
  242. // 提取未配送处理方式的信息,默认为空字符串
  243. $undelives = $data['undelives'] ?? '';
  244. // 如果指定未配送处理方式为2(即开启状态),则清空未配送处理方式的数据
  245. if (isset($data['undelivery']) && $data['undelivery'] == 2) {
  246. $data['undelives'] = [];//开启状态下过滤数据
  247. }
  248. // 移除不需要的字段,以准备创建配送模板
  249. unset($data['region'], $data['free'], $data['undelives'], $data['city_ids']);
  250. // 调用DAO层创建配送模板
  251. $temp = $this->dao->create($data);
  252. // 如果指定了配送时间预约,则处理免费配送条件与模板的关联
  253. if ($data['appoint']) {
  254. // 计算免费配送条件
  255. $settlefree = $this->settleFree($free, $temp['shipping_template_id']);
  256. // 插入计算后的免费配送条件
  257. (app()->make(ShippingTemplateFreeRepository::class)->insertAll($settlefree));
  258. }
  259. // 处理区域与模板的关联
  260. $settleRegion = $this->settleRegion($region, $temp['shipping_template_id']);
  261. // 插入处理后的区域信息
  262. (app()->make(ShippingTemplateRegionRepository::class)->insertAll($settleRegion));
  263. // 如果指定了未配送处理方式为1(即开启状态),则处理未配送的处理方式与模板的关联
  264. if ($data['undelivery'] == 1) {
  265. // 计算未配送处理方式
  266. $settleUndelives = $this->settleUndelives($undelives, $temp['shipping_template_id']);
  267. // 创建处理后的未配送处理方式
  268. (app()->make(ShippingTemplateUndeliveRepository::class))->create($settleUndelives);
  269. }
  270. });
  271. }
  272. /**
  273. * 处理免费配送信息
  274. *
  275. * 此函数用于根据传入的数据集合并免费配送信息,准备数据库插入操作。
  276. * 数据集中每个元素代表一个配送区域及其对应的配送数量和价格。
  277. * 如果城市ID不是数组形式,则抛出异常,确保数据准确性。
  278. *
  279. * @param array $data 包含配送区域ID、配送数量和价格的数据数组。
  280. * @param int $id 模板ID,用于关联配送信息。
  281. * @return array 返回整理后的免费配送信息数组,适合进行数据库插入操作。
  282. * @throws ValidateException 如果城市ID不是数组,则抛出验证异常。
  283. */
  284. public function settleFree($data,$id)
  285. {
  286. // 遍历数据集,处理每个配送区域的信息
  287. foreach ($data as $v){
  288. // 检查城市ID是否为数组,确保数据格式正确
  289. if (isset($v['city_id']) && !is_array($v['city_id'])) {
  290. throw new ValidateException('包邮参数类型错误');
  291. }
  292. // 将城市ID转换为字符串格式,用于后续处理
  293. $city = '/'.implode('/',$v['city_id']).'/';
  294. // 构建免费配送信息数组
  295. $free[] = [
  296. 'temp_id' => $id,
  297. 'city_id' => $city,
  298. 'number' => $v['number'],
  299. 'price' => $v['price']
  300. ];
  301. }
  302. // 返回处理后的免费配送信息数组
  303. return $free;
  304. }
  305. /**
  306. * 处理未配送的订单结算
  307. *
  308. * 该方法用于处理针对特定未配送订单的结算操作。它验证了传入数据的正确性,并返回一个包含临时ID和城市ID的数组。
  309. * 主要用于在订单系统中,对特定条件下未成功配送的订单进行特别处理,例如,更改配送城市或取消订单等。
  310. *
  311. * @param array $data 包含订单相关信息的数据数组
  312. * @param int $id 临时ID,用于标识待处理的订单
  313. * @throws ValidateException 如果$data['city_id']不是数组且设置了'city_id',则抛出验证异常
  314. * @return array 返回一个包含临时ID和城市ID的数组,用于进一步的订单处理
  315. */
  316. public function settleUndelives($data,$id)
  317. {
  318. // 检查$data中是否设置了'city_id'且其类型不是数组,如果是,则抛出类型错误异常
  319. if (isset($v['city_id']) && !is_array($data['city_id'])) throw new ValidateException('指定不配送参数类型错误');
  320. // 返回一个数组,包含临时ID和城市ID,用于后续的订单处理
  321. return ['temp_id' => $id, 'city_id' => $data['city_id']];
  322. }
  323. /**
  324. * 结算区域信息处理函数
  325. * 该函数用于处理给定的区域数据,并根据这些数据生成一个结构化的数组,每个数组元素代表一个区域的结算信息。
  326. * @param array $data 包含区域详细信息的数组,每个元素本身是一个数组,包含城市ID、首次配送费用、续费配送费用等信息。
  327. * @param int $id 用于标识当前操作的临时ID,具体用途根据上下文决定。
  328. * @return array 返回一个结构化数组,每个元素包含城市ID路径、临时ID、首次配送费用、续费配送费用等信息。
  329. */
  330. public function settleRegion($data,$id)
  331. {
  332. // 初始化一个空数组,用于存储处理后的结果
  333. $result = [];
  334. // 遍历输入的区域数据数组
  335. foreach ($data as $k => $v){
  336. // 构建结果数组的单个元素,包含城市ID路径、临时ID、首次配送费用、续费配送费用等信息
  337. // 如果当前键值$k大于0,说明是一个非首个元素,将城市ID用'/'连接并添加到路径中,否则设置为0
  338. $result[] = [
  339. 'city_id' => ($k > 0 ) ? '/'.implode('/',$v['city_id']).'/' : 0,
  340. 'temp_id' => $id,
  341. 'first' => $v['first'],
  342. 'first_price'=> $v['first_price'],
  343. 'continue' => $v['continue'],
  344. 'continue_price'=> $v['continue_price'],
  345. ];
  346. }
  347. // 返回处理后的结果数组
  348. return $result;
  349. }
  350. /**
  351. * 创建默认配送模板
  352. * 该方法用于生成一个默认的配送模板,适用于没有特定配送设置的商家。默认模板设置了基本的配送规则配置。
  353. *
  354. * @param int $merId 商家ID,用于关联模板和商家。
  355. * @return mixed 返回创建的配送模板的数据,具体类型取决于create方法的实现。
  356. */
  357. public function createDefault(int $merId)
  358. {
  359. // 定义默认配送模板的数据结构
  360. $data = [
  361. "name" => "默认模板", // 模板名称
  362. "type" => 1, // 配送类型,这里假设为一种固定的类型
  363. "appoint" => 0, // 是否允许指定配送,这里设置为不允许
  364. "undelivery" => 0, // 是否允许不配送,这里设置为不允许
  365. 'mer_id' => $merId, // 商家ID,用于关联模板和商家
  366. "region" => [[ // 配送区域设置,这里以最简单的形式初始化
  367. "first" => 0, // 首重价格
  368. "first_price" => 0, // 首重费用
  369. "continue" => 0, // 续重价格
  370. "continue_price" => 0, // 续重费用
  371. "city_id" => 0 // 默认城市ID,用于初始配置
  372. ]]
  373. ];
  374. // 调用create方法来创建配送模板
  375. return $this->create($data);
  376. }
  377. /**
  378. * 检查商户模板是否可以删除。
  379. *
  380. * 此函数用于在删除商户模板前进行一系列的验证,以确保模板可以被安全删除。
  381. * 它主要检查以下三个条件:
  382. * 1. 模板是否存在。
  383. * 2. 模板是否为默认模板,默认模板不允许删除。
  384. * 3. 模板是否正在被使用,正在使用的模板不允许删除。
  385. * 如果任何检查失败,将抛出一个ValidateException异常,异常信息包含具体的错误原因。
  386. *
  387. * @param string $merId 商户ID,用于标识模板所属的商户。
  388. * @param int $id 模板ID,用于唯一标识模板。
  389. * @throws ValidateException 如果模板不存在、是默认模板或正在使用中,则抛出此异常。
  390. */
  391. public function check($merId, $id)
  392. {
  393. // 检查模板是否存在,如果不存在则抛出异常。
  394. if(!$this->merExists($merId, $id))
  395. throw new ValidateException('数据不存在,ID:'.$id);
  396. // 检查模板是否为默认模板,如果是则抛出异常。
  397. if($this->merDefaultExists($merId, $id))
  398. throw new ValidateException('默认模板不能删除,ID:'.$id);
  399. // 检查模板是否正在被使用,如果正在使用则抛出异常。
  400. if($this->getProductUse($merId, $id))
  401. throw new ValidateException('模板使用中,不能删除,ID:'.$id);
  402. }
  403. }