Compare commits

...

229 Commits

Author SHA1 Message Date
4c69014a12 京东打印 2024-12-28 15:32:20 +08:00
0cd78ff136 京东打印 2024-12-28 11:13:17 +08:00
2019a88fb9 京东打印 2024-12-27 11:05:33 +08:00
e850b3d336 京东打印 2024-12-26 19:19:43 +08:00
968e5137a8 京东打印 2024-12-26 14:32:02 +08:00
fef0f10ade 批量审核和批量已读 2024-12-20 15:17:56 +08:00
95b2182558
!274 批量操作未读信息和批量申请采购单
Merge pull request !274 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-16 08:10:13 +00:00
2eca516eb5
!273 代码合并
Merge pull request !273 from 杨耀威/yyw
2024-12-16 08:09:12 +00:00
c662130a13 yyw提交 2024-12-16 16:06:42 +08:00
f88ae50f45 消息添加一键已读功能
质检入库添加批量审核功能
2024-12-16 16:05:25 +08:00
647adeefbe 批量审核和批量已读 2024-12-16 14:50:38 +08:00
c44ad62315
!272 bug修复
Merge pull request !272 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-14 08:54:26 +00:00
5673e6c19c 库存 2024-12-14 14:13:33 +08:00
36f9624754 库存 2024-12-14 13:52:02 +08:00
f70965aec1
!271 调整库存告警时间和文案
Merge pull request !271 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-13 09:08:13 +00:00
ff27d20316 库存 2024-12-13 13:23:38 +08:00
acd48764fa
!270 问题处理
Merge pull request !270 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-12 08:11:15 +00:00
c1016e8382 库存 2024-12-12 16:09:51 +08:00
12bfcb0241 库存 2024-12-12 16:03:13 +08:00
0b72923c4e 库存 2024-12-12 13:47:22 +08:00
855da74a9a
!269 下载商品bug
Merge pull request !269 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-11 08:24:34 +00:00
43a1c491c1 减少数据生成 2024-12-11 14:27:39 +08:00
fbd286f80a
!268 bug修复
Merge pull request !268 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-11 03:15:47 +00:00
582c1d5022 减少数据生成 2024-12-11 11:12:24 +08:00
4263702e3f
!267 bug修复
Merge pull request !267 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-10 09:50:04 +00:00
fd00fec068 Merge remote-tracking branch 'origin/fix-release-1.0.0/yjc-migrate' into fix-release-1.0.0/yjc-migrate 2024-12-10 17:46:24 +08:00
481042ac22 减少数据生成 2024-12-10 17:46:11 +08:00
52424dc3cd
!266 代码合并
Merge pull request !266 from 杨耀威/yyw
2024-12-10 01:11:59 +00:00
baa5c435b5 yyw提交 2024-12-09 17:08:21 +08:00
fd26bc2568 yyw提交 2024-12-09 17:07:20 +08:00
1b06e308c8 减少数据生成 2024-12-09 13:44:30 +08:00
96efa40eeb
!265 bug修复
Merge pull request !265 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-06 02:25:11 +00:00
8840d1a719 库存扣减 2024-12-06 10:23:36 +08:00
114f4fb477
!264 bug修复
Merge pull request !264 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-05 11:31:53 +00:00
94ccd1e01c 库存扣减 2024-12-05 19:31:20 +08:00
d355d30583
!263 发货状态同步
Merge pull request !263 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-05 11:16:20 +00:00
d1b2d06c6b 库存扣减 2024-12-05 19:03:56 +08:00
5b73d0641e 库存扣减 2024-12-05 18:47:51 +08:00
cc24e05e1a 库存扣减 2024-12-04 13:49:28 +08:00
a72e1dd052
!262 bug修复
Merge pull request !262 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-03 05:59:53 +00:00
56bca0063a 库存扣减 2024-12-03 13:52:22 +08:00
8164f21ab9
!261 bug修复
Merge pull request !261 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-02 10:07:24 +00:00
deea4ea47e 库存扣减 2024-12-02 18:06:59 +08:00
b8a7b9372f
!260 vest
Merge pull request !260 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-12-02 10:02:07 +00:00
5a58e4f144 库存扣减 2024-12-02 17:24:01 +08:00
f13039fe9b
!259 扣减库存
Merge pull request !259 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-30 01:56:40 +00:00
3f776c8d58 库存扣减 2024-11-29 17:18:48 +08:00
81ac07c395
!258 同步发货状态
Merge pull request !258 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-25 08:50:02 +00:00
43b9c9516b 同步发货状态 2024-11-25 16:47:22 +08:00
92eba2d461 同步发货状态 2024-11-25 13:51:50 +08:00
cf8eeb5560
!257 商品总数修复
Merge pull request !257 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-21 08:38:06 +00:00
34aae9e275 11-18需求 2024-11-21 16:36:42 +08:00
088d7dd306
!256 bug修复
Merge pull request !256 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-21 08:27:20 +00:00
a89d0d78cd 11-18需求 2024-11-21 16:26:46 +08:00
b6855614d3
!255 商品列表统计
Merge pull request !255 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-21 08:15:34 +00:00
9628d56779 11-18需求 2024-11-21 15:58:04 +08:00
05d1ad7ae7
!254 代码合并
Merge pull request !254 from 杨耀威/yyw
2024-11-18 09:08:13 +00:00
964ac2af6a yyw提交 2024-11-18 17:06:45 +08:00
10b606e18a 商品列表添加汇总数据
平台订单检索bug
2024-11-18 17:05:32 +08:00
8724b7ef24 11-18需求 2024-11-18 10:26:56 +08:00
4ecc66eb32
!253 编辑问题修复
Merge pull request !253 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-15 07:54:05 +00:00
08270f5a4a 库存盘点修复 2024-11-15 15:49:30 +08:00
ffe74fd672 库存盘点修复 2024-11-15 15:46:36 +08:00
86ba51dc6f
!252 修复bug
Merge pull request !252 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-14 11:33:26 +00:00
1b2481a6be 库存盘点修复 2024-11-14 10:47:07 +08:00
6c4ac570da 库存盘点修复 2024-11-14 10:33:52 +08:00
118cc6c749
!251 修复销量时间
Merge pull request !251 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-13 10:09:49 +00:00
bf1d627e9d bug修复 2024-11-13 17:04:39 +08:00
4b2e57a8a0 Merge remote-tracking branch 'origin/fix-release-1.0.0/yjc-migrate' into fix-release-1.0.0/yjc-migrate 2024-11-13 10:38:36 +08:00
b8cc587c63 bug修复 2024-11-13 10:38:19 +08:00
b68f0f7ed0
!250 修复bug
Merge pull request !250 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-12 06:44:39 +00:00
d2fded39f2
!249 代码合并
Merge pull request !249 from 杨耀威/yyw
2024-11-12 06:40:43 +00:00
274ed2f1c1 yyw提交 2024-11-12 14:39:26 +08:00
82bfc36ab5 yyw提交 2024-11-12 14:37:55 +08:00
ca20c9020e
!248 代码合并
Merge pull request !248 from 杨耀威/yyw
2024-11-12 06:31:02 +00:00
8f156fa71b yyw提交 2024-11-12 14:29:53 +08:00
3475baedd6 商品列表:
订单重置销量
新增筛选字段剩余库存和在售库存
2024-11-12 14:26:09 +08:00
c027c7ef0a bug修复 2024-11-12 13:49:03 +08:00
d347d2436c
!247 去除锁表
Merge pull request !247 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-11 05:36:01 +00:00
837b92e06a bug修复 2024-11-11 13:35:12 +08:00
d6fadaae1e
!246 bug
Merge pull request !246 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-11 02:54:54 +00:00
51e8f3a9fb bug修复 2024-11-11 10:52:37 +08:00
efcde2d5df bug修复 2024-11-11 09:37:19 +08:00
fbc2eeb42c bug修复 2024-11-11 09:35:21 +08:00
13021d439a bug修复 2024-11-08 18:10:36 +08:00
0f4d91e939 bug修复 2024-11-08 18:10:00 +08:00
d9f73a8675
!245 修复库存导入0的异常
Merge pull request !245 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-08 08:42:52 +00:00
1bbb0c4c63 库存支持数据0导入 2024-11-08 16:41:44 +08:00
072e995b70
!244 鲜花
Merge pull request !244 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-08 08:05:36 +00:00
2c965ed7b2 a订单导出 2024-11-08 16:04:31 +08:00
1bff0bf7e8
!240 鲜花2.0迭代
Merge pull request !240 from 杨建炊/fix-release-1.0.0/yjc-migrate
2024-11-08 01:49:35 +00:00
46f32294b8 a订单导出 2024-11-06 16:13:23 +08:00
f946d6c65b
!243 代码合并
Merge pull request !243 from 杨耀威/yyw
2024-11-06 01:42:30 +00:00
4327e6805d yyw提交 2024-11-05 16:22:19 +08:00
9db90e62c2 输入框回车查询 2024-11-05 16:20:39 +08:00
8b3066b210
!242 代码合并
Merge pull request !242 from 杨耀威/yyw
2024-11-04 06:47:52 +00:00
914f59d6c9 yyw提交 2024-11-04 14:46:22 +08:00
47f8d4dab4 导出 2024-11-04 14:44:52 +08:00
f2b52e5c88 Merge remote-tracking branch 'origin/fix-release-1.0.0/yjc-migrate' into fix-release-1.0.0/yjc-migrate 2024-11-04 14:44:47 +08:00
bec14778a3 a订单导出 2024-11-04 14:44:35 +08:00
2283bfcc0c
!241 代码合并
Merge pull request !241 from 杨耀威/yyw
2024-11-04 06:13:04 +00:00
55f1d1f133 yyw提交 2024-11-04 14:10:14 +08:00
0947542239 yyw提交 2024-11-04 14:08:48 +08:00
985d4c8b59 a订单导出 2024-11-04 11:31:56 +08:00
c60786f798 a订单导出 2024-11-02 17:05:46 +08:00
67dd4ea4fb a订单导出 2024-11-02 13:26:07 +08:00
a5e82074a8 数据统计修复 2024-11-01 18:00:46 +08:00
9229309500 数据统计修复 2024-11-01 15:42:23 +08:00
625acfa290 扣减后触发重算 2024-10-31 18:56:50 +08:00
2ca2352ba0 扣减后触发重算 2024-10-31 17:38:43 +08:00
24757518fc 显示销量 2024-10-31 17:12:24 +08:00
a9e542f89b 增加锁 2024-10-31 16:33:15 +08:00
495253e791 增加锁 2024-10-31 15:09:20 +08:00
c017753fe6 增加锁 2024-10-31 14:37:42 +08:00
a8290bd5d0 Merge branch 'yyw' of gitee.com:hzchunfen/erp into fix-release-1.0.0/yjc-migrate 2024-10-30 16:32:40 +08:00
ffc78c9dc3 yyw提交 2024-10-30 16:28:27 +08:00
74b42969b7 商品添加导出、时间筛选 2024-10-30 16:25:58 +08:00
c48c4da83d 调整排序 2024-10-30 16:20:36 +08:00
8af2f75854 调整排序 2024-10-30 16:08:48 +08:00
86554e20d5 批量修改在售库存 2024-10-30 15:27:45 +08:00
ae5cc03c17 批量修改在售库存 2024-10-30 15:15:54 +08:00
44163c5c5c 批量修改在售库存 2024-10-29 17:00:10 +08:00
2e96bc6cdd 批量修改在售库存 2024-10-29 16:47:05 +08:00
67ed7dbe6b 批量修改在售库存 2024-10-29 16:33:30 +08:00
7bcecbe435 批量修改在售库存 2024-10-22 17:32:01 +08:00
e76f78dd8c 批量修改在售库存 2024-10-22 17:31:30 +08:00
c95efcdc15 erp 2024-10-22 17:19:15 +08:00
ab3b8b47f3 Merge remote-tracking branch 'origin/fix-release-1.0.0/yjc-migrate' into fix-release-1.0.0/yjc-migrate 2024-10-22 11:50:09 +08:00
dd482189f9 批量修改在售库存 2024-10-22 11:49:49 +08:00
02cd13c6f7 yyw提交 2024-10-22 10:21:38 +08:00
0e2e982eb2 yyw提交 2024-10-18 16:34:24 +08:00
68168a0936 同步快团团用在售库存其他都是实际库存 2024-09-21 14:41:55 +08:00
a5dacc8a81
!237 合并
Merge pull request !237 from 赵世界/feat/2024
2024-09-18 07:42:25 +00:00
3f1f724cdd 软删除 2024-09-18 15:41:46 +08:00
c62829b1ad 商品列表、组合商品的新增、编辑添加权限控制 2024-09-07 16:55:06 +08:00
f9c0662282 鲜花2.0-采购流程变更 2024-09-07 16:46:29 +08:00
1b6bd4ffc4 鲜花2.0-采购流程变更 2024-09-05 11:01:02 +08:00
53ac852284 Merge branch 'yyw' of gitee.com:hzchunfen/erp into fix-release-1.0.0/yjc-migrate 2024-09-05 10:52:14 +08:00
3410958449 更新提交 2024-09-05 10:51:36 +08:00
fcd6735028 鲜花2.0-采购流程变更 2024-09-05 10:14:22 +08:00
a9d04bdf18 鲜花2.0-采购流程变更 2024-09-04 16:43:15 +08:00
cb56e5a227 1、采购管理拆分为商品采购和质检入库 2、质检入库添加采购审核按钮 3、商品采购新增时添加到货时间字段 4、商品管理-新增、编辑商品代码重写 2024-09-03 17:34:54 +08:00
7fb90a77f9 鲜花2.0-采购流程变更 2024-09-02 17:36:43 +08:00
a43d8b0c81 鲜花2.0-采购流程变更 2024-08-30 17:06:24 +08:00
ea65bcee66 鲜花2.0-sku命名规则变更 2024-08-30 16:15:40 +08:00
20ae896e0c Merge branch 'yyw' of gitee.com:hzchunfen/erp into fix-release-1.0.0/yjc-migrate 2024-08-24 16:00:09 +08:00
fb52234ee5 yyw提交 2024-08-24 15:59:46 +08:00
50229d464e 鲜花2.0-文案调整 2024-08-24 15:31:12 +08:00
d0d10604d5 鲜花2.0-文案调整 2024-08-24 15:27:44 +08:00
d8f181a033 Merge branch 'yyw' of gitee.com:hzchunfen/erp into fix-release-1.0.0/yjc-migrate 2024-08-24 14:51:25 +08:00
10428c0673 yyw提交 2024-08-24 14:51:04 +08:00
7ad3581a9d yyw提交 2024-08-24 14:50:12 +08:00
863f78f73f 鲜花2.0-文案调整 2024-08-24 14:31:32 +08:00
984132bfff Merge branch 'yyw' of gitee.com:hzchunfen/erp into fix-release-1.0.0/yjc-migrate 2024-08-24 14:23:34 +08:00
374787b57c 鲜花2.0-文案调整 2024-08-24 14:23:06 +08:00
43977aea63 yyw提交 2024-08-24 14:22:16 +08:00
cb9261853a yyw提交 2024-08-24 14:20:41 +08:00
2c974cfbc0 鲜花2.0-文案调整 2024-08-23 15:33:10 +08:00
fe7a4e5c33 yyw提交 2024-08-23 15:14:47 +08:00
16b93f1619 鲜花2.0-文案调整 2024-08-22 16:35:34 +08:00
6e8ca109fc 鲜花2.0-文案调整 2024-08-21 18:30:30 +08:00
0477d04bbd 新增商品 规格编码即为最终编码 编辑商商品 规格编码展示为最终编码; 编辑商品保存报错; 组合商品 库存列 改为 可售库存;组合数量,主商品显示为空(也就是什么都不显示),children 显示 2024-08-21 17:52:32 +08:00
70442ebbac Merge branch 'yyw' of gitee.com:hzchunfen/erp into fix-release-1.0.0/yjc-migrate 2024-08-21 09:53:46 +08:00
f54cf6a4a8 优化 2024-08-21 09:53:24 +08:00
f858221f24 Merge branch 'yyw' of gitee.com:hzchunfen/erp into fix-release-1.0.0/yjc-migrate 2024-08-21 09:47:50 +08:00
2468a9c751 鲜花2.0-新增父级分类的数据返回和预警状态维护 2024-08-20 16:58:37 +08:00
825748480c 鲜花erp优化 2024-08-20 16:40:07 +08:00
89a2df396e Merge branch 'yyw' of gitee.com:hzchunfen/erp into fix-release-1.0.0/yjc-migrate 2024-08-19 16:55:58 +08:00
0273ea5255 yyw提交 2024-08-19 16:55:32 +08:00
a70945dc33 Merge branch 'yyw' of gitee.com:hzchunfen/erp into fix-release-1.0.0/yjc-migrate 2024-08-19 16:43:52 +08:00
2ac84ea838 yyw提交 2024-08-19 16:39:53 +08:00
c57304bad2 鲜花2.0-类型软删除+统计接口修改 2024-08-19 16:04:25 +08:00
5214fa853c yyw提交 2024-08-16 17:21:33 +08:00
465b93e035 鲜花2.0-类型软删除+统计接口修改 2024-08-16 17:19:12 +08:00
496aa21138 sku销售统计、品种销售统计、交易趋势统计时间更换
添加报损统计数据
添加入库采购、报损记录、盘点记录批量添加功能
商品列表品种筛选仅可选择二级
2024-08-16 14:45:07 +08:00
267e5ab5cd 鲜花2.0-库存成本,周数据导出功能修改+增加库存变更记录 2024-08-16 13:36:06 +08:00
25f805496d 鲜花2.0-库存成本,周数据导出功能修改+增加库存变更记录 2024-08-16 13:26:22 +08:00
bf67357601 鲜花2.0-库存成本,周数据导出功能修改+增加库存变更记录 2024-08-15 17:06:30 +08:00
0e78cc2d43 鲜花2.0-导购成本价覆盖 2024-08-15 11:23:29 +08:00
2a36638d75 鲜花2.0-接口测试bug修复 2024-08-14 17:40:14 +08:00
9e50b007eb 鲜花2.0-接口测试bug修复 2024-08-14 17:02:31 +08:00
05b5cd400a 鲜花2.0-接口测试bug修复 2024-08-13 18:17:03 +08:00
dda4b5eb2a 鲜花2.0-接口测试bug修复 2024-08-12 19:36:42 +08:00
6261fa78b8 yyw提交 2024-08-10 21:13:06 +08:00
235388bbaa 鲜花2.0-接口测试bug修复 2024-08-10 15:40:10 +08:00
8a76862c0b 鲜花2.0-接口测试bug修复 2024-08-10 14:24:26 +08:00
45ab63dc6d 鲜花2.0-接口测试bug修复 2024-08-10 13:49:12 +08:00
3399bce4db 鲜花2.0-接口测试bug修复 2024-08-09 17:20:34 +08:00
b16e95800d 鲜花2.0-接口测试bug修复 2024-08-09 17:17:32 +08:00
f0b4b4637a 鲜花2.0-接口测试bug修复 2024-08-09 16:17:59 +08:00
e2eb40185f 鲜花2.0-接口测试bug修复 2024-08-09 14:46:37 +08:00
6b20a06619 Merge branch 'fix-release-1.0.0/yjc-migrate' of gitee.com:hzchunfen/erp into fix-release-1.0.0/yjc-migrate 2024-08-09 14:14:45 +08:00
23b9f168a0 鲜花2.0-接口测试bug修复 2024-08-09 14:14:26 +08:00
8eb24162ed
!235 代码合并
Merge pull request !235 from 杨耀威/yyw
2024-08-09 03:42:01 +00:00
7369891fbf yyw打包 2024-08-09 11:38:49 +08:00
887573a81f yyw提交 2024-08-08 19:56:28 +08:00
e0fce64e18 鲜花2.0-接口测试bug修复 2024-08-08 17:06:50 +08:00
ea56daa372 鲜花2.0-接口测试bug修复 2024-08-08 16:17:40 +08:00
9c559f3bdd 鲜花2.0-接口测试bug修复 2024-08-08 16:16:46 +08:00
32edad5d4b 鲜花2.0-接口测试bug修复 2024-08-08 16:10:38 +08:00
616232460e 鲜花2.0-接口测试bug修复 2024-08-08 16:06:56 +08:00
3fbba03d78 鲜花2.0-接口测试bug修复 2024-08-08 15:35:23 +08:00
7e33e1f3f7 鲜花2.0-接口测试bug修复 2024-08-08 15:34:54 +08:00
3313c4a713 鲜花2.0-接口测试bug修复 2024-08-08 09:34:57 +08:00
cfc9870314 yyw提交 2024-08-07 18:17:56 +08:00
de8ecd514e 鲜花2.0-接口测试bug修复 2024-08-07 17:28:14 +08:00
cccebe685f 鲜花2.0-接口测试bug修复 2024-08-07 16:35:51 +08:00
489a0050c4 鲜花2.0-接口测试bug修复 2024-08-07 16:23:04 +08:00
cca19547d2 鲜花2.0-接口测试bug修复+类型树型接口改造 2024-08-06 17:24:01 +08:00
0bdd35f15b 鲜花2.0-接口测试bug修复+类型树型接口改造 2024-08-06 16:59:22 +08:00
5734c08ba4 鲜花2.0-接口测试bug修复 2024-08-06 13:53:22 +08:00
7e3a5fed04 售后单列表、spu销售统计、交易趋势、sku销量统计 2024-08-05 18:04:49 +08:00
45882b4a72 鲜花2.0-gmv接口开发完成+ sku和spu维度的报表接口开发完成 2024-08-05 17:22:26 +08:00
fdaa818c28 鲜花2.0-gmv接口开发完成+ 售后单维护 2024-08-05 16:54:19 +08:00
dd6f9427c7 鲜花2.0-gmv接口开发完成+ sku和spu维度的报表接口开发完成 2024-08-02 16:40:57 +08:00
f9bc1fc65a 鲜花2.0-定时任务统计每日销售金额(后续统计gmv需要使用)+ sku和spu维度的报表接口开发 2024-08-01 17:57:51 +08:00
86ac32d29f 鲜花2.0-定时任务统计每日销售金额(后续统计gmv需要使用)+ sku和spu维度的报表接口开发 2024-08-01 17:53:32 +08:00
dba30f1168 鲜花2.0-定时任务统计每日库存报表数据+库存扣减完成 2024-07-31 17:58:06 +08:00
618979614a 供应商管理、采购记录、报损记录、盘点记录 2024-07-30 18:13:11 +08:00
ab03594786 鲜花2.0-库存盘点和批量导入+订单库存扣减乐观锁和重试机制 2024-07-30 17:41:02 +08:00
91e4e5342e 鲜花2.0-库存盘点和批量导入 2024-07-30 15:30:32 +08:00
f9220ef396 鲜花2.0-库存扣减部分逻辑调整+保质期告警 2024-07-29 18:32:40 +08:00
fa41ac4047 鲜花2.0-报失单导入和价格告警 2024-07-27 17:13:02 +08:00
6841435ea0 鲜花2.0-报失单导入和价格告警 2024-07-27 16:51:00 +08:00
3b61176cda 鲜花2.0-采购单导入 2024-07-26 17:48:07 +08:00
3ac66d6d63 鲜花2.0-站内信管理 2024-07-25 17:54:14 +08:00
9d96dc07ec 鲜花2.0-供应商管理 2024-07-24 17:46:48 +08:00
f1d31ac865 migrate commit 数据库设计 2024-07-24 15:45:36 +08:00
3b2f7cec04 migrate commit 2024-07-22 16:29:45 +08:00
353 changed files with 17472 additions and 17200 deletions

View File

@ -0,0 +1,66 @@
<?php
namespace App\Console\Commands;
use App\Http\Service\MessageService;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CheckPrice extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'check:price';
/**
* The console command description.
*
* @var string
*/
protected $description = '检查价格异常';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
//定时任务每15min执行一次 只会查询15min内的订单数据
$startTime = Carbon::now()->subMinutes(15)->toDateTimeString();
$endTime = Carbon::now()->toDateTimeString();
//查询价格异常订单
$results = DB::table('business_order_items as a')
->select('b.name as cn_name', 'b.title as title', DB::raw('ROUND(a.goods_price / 100,2) AS goods_price')
, 'b.cost','a.created_at','a.business_order_id')
->leftJoin('goods_skus as b', 'a.external_sku_id', '=', 'b.external_sku_id')
->whereBetween('a.created_at', [$startTime,$endTime])
->whereRaw('a.goods_price / 100 < cost')
->get();
if($results->isNotEmpty()){
Log::info($startTime.'异常订单',$results->toArray());
$messageService = new MessageService();
foreach ($results as $v){
$messageService->createPriceExceptionMessage($v->business_order_id,
$v->cn_name,$v->title,$v->goods_price,$v->cost);
}
}
Log::info('任务完成:check-CheckPrice');
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace App\Console\Commands;
use App\Http\Service\MessageService;
use App\Models\GoodsSku;
use App\Models\PurchaseRecords;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CheckSkuQualityPeriod extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'check:sku_quality_period';
/**
* The console command description.
*
* @var string
*/
protected $description = '检查sku保质期';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
//定时任务每天执行一次
$endTime = Carbon::now()->endOfDay()->toDateTimeString();
$startTime = Carbon::now()->subDays(4)->startOfDay()->toDateTimeString();//目前检查范围是5天
//查询未处理过的快过期的异常订单
$purchaseRecords = DB::table('purchase_records as a')
->select("a.created_at","a.date", "a.num", "b.title","b.name", "b.stock", "a.id", "b.id as sku_id", "b.external_sku_id")
->leftJoin('goods_skus as b', 'a.external_sku_id', '=', 'b.external_sku_id')
->where("a.check_status", "=", 0)
->whereBetween('a.expire_time', [$startTime, $endTime])->get();
Log::info('采购临期记录', [$purchaseRecords]);
if ($purchaseRecords->isNotEmpty()) {
$messageService = new MessageService();
$updateIds = [];
foreach ($purchaseRecords as $v) {
// 单独采购单后续总和小于库存表示该sku没有卖完
$totalPurchaseNum = PurchaseRecords::query()->where('created_at', '>', $v->created_at)
->where('external_sku_id', "=", $v->external_sku_id)
->where("status", 1)->sum('num');
if ($totalPurchaseNum < $v->stock) {
$messageService->skuQualityPeriodNoticeMessage((array)$v);
}else{
//
$updateIds[] = $v->id;
}
//更新下状态
}
if(!empty($updateIds)){
PurchaseRecords::query()->whereIn('id', $updateIds)->update([
"check_status" => 1
]);
}
}
Log::info('任务完成:check-CheckSkuQualityPeriod',[$updateIds]);
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace App\Console\Commands;
use App\Models\BusinessOrderItem;
use App\Models\DailyReport;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\LossRecords;
use App\Models\PurchaseRecords;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class DailyStockRecordReport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'daily:report:stock_record {date?}';
/**
* The console command description.
*
* @var string
*/
protected $description = '每日商品库存记录';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$date = $this->argument('date');
if (is_null($date)) {
$date = Carbon::yesterday()->format('Y-m-d');
}
$startDateTime = Carbon::parse($date)->startOfDay()->toDateTimeString();
$endDateTime = Carbon::parse($date)->endOfDay()->toDateTimeString();
//统计订单数量
$orderItems = BusinessOrderItem::query()
->leftJoin("business_orders as b", "business_order_id", "=", "b.id")
->select("business_order_items.external_sku_id"
, DB::raw("sum(goods_number-already_cancel_number) as goods_total")
, DB::raw("ROUND(sum(goods_amount) / 100,2) as order_total_amount"))
->where('b.confirm_at', '>=', Carbon::parse($startDateTime)->getPreciseTimestamp(3))
->where('b.confirm_at', '<=', Carbon::parse($endDateTime)->getPreciseTimestamp(3))
->where("business_order_items.cancel_status", "=", 0)
->groupBy('external_sku_id')->get()->pluck(null, "external_sku_id")->toArray();
//统计采购单数量
$purchaseRecords = PurchaseRecords::query()
->select(DB::raw("sum(num) as arrived_today_num"), "external_sku_id")
->whereBetween("check_time", [$startDateTime, $endDateTime])
->where("status",1)
->groupBy("external_sku_id")->get()->pluck(null, "external_sku_id")->toArray();
//统计报损数量
$lossRecords = LossRecords::query()
->select(DB::raw("sum(num) as loss_num"), "external_sku_id")
->whereBetween("created_at", [$startDateTime, $endDateTime])
->groupBy("external_sku_id")->get()->pluck(null, "external_sku_id")->toArray();
Log::info("{$date}每日库存记录", ["orderItems" => $orderItems
, "purchaseRecords" => $purchaseRecords, "lossRecords" => $lossRecords]);
$needUpdateExternalSkuIds = array_merge(array_keys($orderItems), array_keys($purchaseRecords), array_keys($lossRecords));
//开始更新数据
if (!empty($needUpdateExternalSkuIds)) {
$needUpdateSkuIdsMap = GoodsSku::query()->whereIn("external_sku_id", $needUpdateExternalSkuIds)->pluck("external_sku_id", 'id')->toArray();
collect($needUpdateSkuIdsMap)->each(function ($externalSkuId,$skuId) use ($date, $orderItems, $purchaseRecords, $lossRecords) {
$record = DailyStockRecord::query()->firstOrNew([
'sku_id' => $skuId,
'day' => $date,
]);
$record->arrived_today_num = !empty($purchaseRecords[$externalSkuId]["arrived_today_num"])
? $purchaseRecords[$externalSkuId]["arrived_today_num"] : 0;
$record->loss_num = !empty($lossRecords[$externalSkuId]["loss_num"])
? $lossRecords[$externalSkuId]["loss_num"] : 0;
$record->order_goods_num = !empty($orderItems[$externalSkuId]["goods_total"])
? $orderItems[$externalSkuId]["goods_total"] : 0;
$record->order_total_amount = !empty($orderItems[$externalSkuId]["order_total_amount"])
? $orderItems[$externalSkuId]["order_total_amount"] : 0;
$record->save();
});
}
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Console\Commands;
use App\Http\Service\MessageService;
use App\Models\GoodsSku;
use App\Services\Business\MiaoXuan\Goods;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class InitSaleStock extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'init:sale_stock {ids?}';
/**
* The console command description.
*
* @var string
*/
protected $description = '初始化在售库存';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$ids = $this->argument('ids');
if(!empty($ids)){
$ids = explode(',',$ids);
}
GoodsSku::query()->where("stock",'>',0)->when($ids,function($query)use($ids){
$query->whereIn('id',$ids);
})->update(['sale_stock' => DB::raw('stock')]);
}
}

View File

@ -3,6 +3,7 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
@ -41,19 +42,20 @@ class Inventory extends Command
*/
public function handle()
{
DB::beginTransaction();
try {
$log = new Log();
$log->module = 'goods';
$log->action = 'PATCH';
$log->target_type = 'goods_sku';
$log->target_id = 0;
$log->target_field = 'timingInventory';
$log->user_id = 999;
// 数据库存储过程,7点定时执行
$data = [];
$date = date('Y-m-d');
GoodsSku::query()->chunk(500, static function ($skus) use (&$data, $date) {
$log = new Log();
$log->module = 'goods';
$log->action = 'PATCH';
$log->target_type = 'goods_sku';
$log->target_id = 0;
$log->target_field = 'timingInventory';
$log->user_id = 999;
// 数据库存储过程,7点定时执行
$data = [];
$date = date('Y-m-d');
GoodsSku::query()->chunk(500, static function ($skus) use (&$data, $date, $log) {
DB::beginTransaction();
try {
foreach ($skus as $sku) {
$data[] = [
'sku_id' => $sku->id,
@ -64,15 +66,16 @@ class Inventory extends Command
'two_days_ago_num' => $sku->two_days_ago_num + $sku->yesterday_num,
]);
}
});
$record = new DailyStockRecord();
$record->batchInsert($data);
DB::commit();
$log->message = '7点数据更新成功';
} catch (\Exception $exception) {
$log->message = '7点数据更新失败' . $exception->getMessage();
DB::rollBack();
}
//$record = new DailyStockRecord();
//$record->batchInsert($data);
DB::commit();
$log->message = '7点数据更新成功';
} catch (\Exception $exception) {
$log->message = '7点数据更新失败' . $exception->getMessage();
DB::rollBack();
}
});
$log->save();
$this->info($log->message);
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Console\Commands;
use App\Models\Shop;
use App\Services\Business\BusinessFactory;
use App\Utils\DateTimeUtils;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class KttOrderAfterSaleQuery extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ktt:after_sale_order_query';
/**
* The console command description.
*
* @var string
*/
protected $description = '快团团根据成交时间拉取快团团增量售后单';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
* @throws \Exception
*/
public function handle()
{
$shops = Shop::query()->where('plat_id', Shop::$PLAT_KTT)->where('status', Shop::$STATUS_AUTHORIZED)->get();
$endTime = DateTimeUtils::getMicroTime();
$beginTime = $endTime - (15 * 60 * 1000)-1000;//售后单每15min查询一次 多查询一秒
foreach ($shops as $shop) {
BusinessFactory::init()->make($shop->plat_id)->setShop($shop)->downloadAfterSaleOrdersAndSave($beginTime, $endTime);
}
}
}

View File

@ -6,6 +6,7 @@ use App\Models\Shop;
use App\Services\Business\BusinessFactory;
use App\Utils\DateTimeUtils;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class KttOrderQuery extends Command
{

View File

@ -0,0 +1,67 @@
<?php
namespace App\Console\Commands;
use App\Http\Enum\BusinessOrderShippingStatus;
use App\Models\BusinessOrder;
use App\Models\Shop;
use App\Services\Business\BusinessFactory;
use Carbon\Carbon;
use Illuminate\Console\Command;
class KttOrderSyncStatus extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ktt:order_sync_status {ids?}';
/**
* The console command description.
*
* @var string
*/
protected $description = '快团团同步发货状态';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
* @throws \Exception
*/
public function handle()
{
$ids = $this->argument('ids');
if (!empty($ids)) {
$ids = explode(",", $ids);
}
$startTime = Carbon::now()->subDays(15)->getPreciseTimestamp(3);
BusinessOrder::query()->when($ids, function ($query) use ($ids) {
$query->whereIn('id',$ids);
})
->where('confirm_at', '>=', $startTime)
->where('shipping_status', BusinessOrderShippingStatus::UNSHIP)
->where('cancel_status', 0)
->chunk(200, function ($orders) {
foreach ($orders as $order) {
$shop = Shop::query()->find($order['shop_id']);
if (!empty($shop)) {
BusinessFactory::init()->make($shop->plat_id)->setShop($shop)->queryStatusAndSync($order);
usleep(10);
}
}
});
}
}

View File

@ -2,9 +2,14 @@
namespace App\Console;
use App\Console\Commands\CheckPrice;
use App\Console\Commands\CheckSkuQualityPeriod;
use App\Console\Commands\DailySalesReport;
use App\Console\Commands\DailyStockRecordReport;
use App\Console\Commands\GoodsSkuDailyReport;
use App\Console\Commands\Inventory;
use App\Console\Commands\KttOrderAfterSaleQuery;
use App\Console\Commands\KttOrderSyncStatus;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use App\Console\Commands\KttOrderQuery;
@ -45,6 +50,18 @@ class Kernel extends ConsoleKernel
$schedule->command(DailySalesReport::class, ['S7'])->dailyAt('09:30');
$schedule->command(DeleteKttQuery::class)->daily();
//新增价格校验
$schedule->command(CheckPrice::class)->everyFifteenMinutes();
//保质期
$schedule->command(CheckSkuQualityPeriod::class)->dailyAt('11:00');
//快团团售后单拉取
$schedule->command(KttOrderAfterSaleQuery::class)->everyFifteenMinutes();
//同步售卖信息和报损相关数据
$schedule->command(DailyStockRecordReport::class)->dailyAt('03:30');
//新增快团团订单状态同步
$schedule->command(KttOrderSyncStatus::class)->everyTenMinutes();
}
/**

View File

@ -10,6 +10,7 @@ use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class BatchStockUpdateEvent
{

View File

@ -21,6 +21,8 @@ class BusinessOrdersUpdate
public $businessGoodSku;
public $num;
public $goodsSku;
public $type = "BusinessOrdersUpdate";
public $combinationGoodsUpdate = true;
/**
@ -30,39 +32,50 @@ class BusinessOrdersUpdate
*/
public function __construct($businessGoodSku, $num)
{
$this->businessGoodSku = $businessGoodSku->toArray();
$this->num = $num;
$this->updateStock();
try {
$this->businessGoodSku = $businessGoodSku->toArray();
$this->num = $num;
$updateResult = false;
//暂时设定重试3次
for ($i = 0; $i < 3; $i++) {
if ($this->updateStock()) {
$updateResult = true;
break;
}
usleep(140);
}
if (!$updateResult) {
Log::error("sku 业务更新失败", (array)$this->businessGoodSku);
}
} catch (\Exception $exception) {
Log::error("sku 业务更新发生异常", ["error" => $exception->getMessage()]);
}
}
private function updateStock()
{
$this->goodsSku = GoodsSku::query()
->where('external_sku_id', $this->businessGoodSku['external_sku_id'])
->first();
if (is_null($this->goodsSku)) {
return false;
return true;
}
$oldStock = $this->goodsSku->stock;
$stock = $this->goodsSku->stock + $this->num;
if (0 >= $stock) {
$this->goodsSku->status = GoodsSku::$STATUS_DOWN;
$saleStock = $this->goodsSku->sale_stock + $this->num;
$saleStock = ($saleStock > 0) ? $saleStock : 0;
$updateParam = ["stock" => $stock, "sale_stock" => $saleStock];
if (0 >= $saleStock) {
$updateParam['status'] = GoodsSku::$STATUS_DOWN;
} else {
$this->goodsSku->status = GoodsSku::$STATUS_ON_SALE;
$updateParam['status'] = GoodsSku::$STATUS_ON_SALE;
}
// 今日到货 + 1T 大于20,且当前剩余库存小于4时 直接下架
$arrivedTodayNum = DailyStockRecord::query()
->where('day', DateTimeUtils::getToday())
->where('sku_id', $this->goodsSku->id)
->value('arrived_today_num');
if (20 < $arrivedTodayNum + $this->goodsSku->yesterday_num && 4 > $stock) {
$this->goodsSku->status = GoodsSku::$STATUS_DOWN;
$stock = 0;
}
$this->goodsSku->stock = $stock;
$this->goodsSku->save();
Log::info("sku 业务订单库存更:{$this->goodsSku->id},num:{$this->num}", [$updateParam, (array)$this->goodsSku]);
//乐观锁更新
return GoodsSku::query()->where('external_sku_id', $this->businessGoodSku['external_sku_id'])->
where("stock", "=", $oldStock)->update($updateParam);
}
/**

View File

@ -0,0 +1,40 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CancelLogisticEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $shopId;
public $orderSn;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($shopId, $orderSn)
{
$this->shopId = $shopId;
$this->orderSn = $orderSn;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

View File

@ -33,22 +33,14 @@ class StockUpdateEvent
private function checkStatusAndStock($goodsSku)
{
$stock = $goodsSku->stock;
if (0 >= $goodsSku->stock) {
//新版上下架和真实库存无关
if (0 >= $goodsSku->sale_stock) {
$status = GoodsSku::$STATUS_DOWN;
} else {
$status = GoodsSku::$STATUS_ON_SALE;
}
$arrivedTodayNum = DailyStockRecord::query()
->where('day', DateTimeUtils::getToday())
->where('sku_id', $goodsSku->id)
->value('arrived_today_num');
if (20 < $arrivedTodayNum + $goodsSku->yesterday_num && 4 > $goodsSku->stock) {
$status = GoodsSku::$STATUS_DOWN;
$stock = 0;
}
$goodsSku->status = $status;
$goodsSku->stock = $stock;
$goodsSku->save();
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Exports;
use App\Http\Enum\AfterSaleOrderStatusEnum;
use App\Models\DailyReport;
use App\Models\Shop;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Illuminate\Support\Collection;
class BusinessAfterOrderExport implements FromCollection, ShouldAutoSize
{
private $data;
public function __construct($orders)
{
$this->data = $this->createData($orders);
}
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return new Collection($this->data);
}
private function createData($orders)
{
$headTitle = [
'父订单编号',
'店铺名称',
'退款金额',
'用户申请退运费金额',
'退款原因',
'描述',
'申请类型',
'售后单状态',
'售后单创建时间',
'图片链接',
];
$bodyData = [];
foreach ($orders as $order) {
$bodyData[] = [
$order['order_sn'],
$order['shop']['name'],
bcdiv($order['refund_amount'], 100, 2),
bcdiv($order['refund_shipping_amount'], 100, 2),
$order['reason'],
$order['description'],
empty($order['apply_type']) ? "仅退款" : "退货退款",
AfterSaleOrderStatusEnum::getData($order['after_sales_status']),
$order['after_sale_created_at'],
implode(",", $order['image_list'])
];
}
return [$headTitle, $bodyData];
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Exports;
use App\Models\DailyReport;
use App\Models\Shop;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Illuminate\Support\Collection;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class BusinessOrderExport implements FromCollection, ShouldAutoSize
{
private $data;
public function __construct($orders)
{
$this->data = $this->createData($orders);
}
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return new Collection($this->data);
}
private function createData($orders)
{
$headTitle = [
'订单id/编号',
'商品信息|个数',
'店铺名称',
'跟团号',
'收件人',
'收件地址',
'下单时间',
'发货状态',
'订单状态',
'售后状态',
];
$bodyData = [];
foreach ($orders as $order) {
$productInfo = "";
foreach ($order['items'] as $item) {
$productInfo .= $item['goods_name']. "|" . $item['goods_number'] . ",";
}
rtrim($productInfo, ",");
$bodyData[] = [
$order['id'] . "/" . $order['order_sn'],
$productInfo,
$order['shop']['name'],
$order['is_supplier'].":".$order['participate_no'],
$order['receiver_name'],
$order['receiver_address_province']." ". $order['receiver_address_city']." ".$order['receiver_address_district']." ".$order['receiver_address_detail'],
$order['confirm_at'],
$order['shipping_status'],
$order['cancel_status'],
$order['after_sales_status']
];
}
return [$headTitle, $bodyData];
}
}

View File

@ -2,8 +2,11 @@
namespace App\Exports;
use App\Http\Enum\TargetTypeEnum;
use App\Models\DailyStockRecord;
use App\Models\Goods;
use App\Models\Log;
use App\Models\PurchaseRecords;
use App\Utils\ArrayUtils;
use App\Utils\DateTimeUtils;
use Maatwebsite\Excel\Concerns\FromCollection;
@ -35,56 +38,72 @@ class GoodsSkusExport implements FromCollection, ShouldAutoSize
$headTitle = [
'商品编码',
'商品名称',
'商品种类',
'商品品牌',
'规格编码',
'规格名称',
'在售库存',
"剩余库存",
'创建时间',
];
$map = [
'cost' => ['成本', '更新前成本', '更新后成本'],
'inventory' => ['库存', '盘点', '上新'],
'cost' => ['当前成本', '更新前成本', "更新后成本"],
'inventory' => ['当前库存', '盘点数', '采购总数'],
];
$headTitle = array_merge($headTitle, $map[$this->type]);
$headTitle = array_merge($headTitle, $map[$this->type] ?? []);
if ('cost' === $this->type) {
$update = $this->getChangeCostLogs();
}
if ('inventory' === $this->type) {
$update = $this->getInventoryRecord();
}
$ids = array_keys($update);
if (empty($ids)) {
return [$headTitle];
}
$model = GoodsSku::query()
->when($ids, function ($query, $ids) {
return $query->whereIn('id', $ids);
})
->with(['goods' => function ($query) {
$query->with(['type:id,name', 'brand:id,name'])
->orderBy('type_id')
->orderBy('brand_id');
}]);
$data = $model->get()->toArray();
if (empty($data)) {
return [$headTitle];
if ($this->type === "goods_sku" || $this->type === "goods_combination") {
$builder = GoodsSku::query();
if (request()->get('type_id')) {
$goodsIds = Goods::query()->filter()->pluck('id')->toArray();
$builder->whereIn('goods_id', $goodsIds);
}
if (request()->get('goods_title')) {
$builder->where('name', 'like', '%' . request()->get('goods_title') . '%');
}
if ($this->type === "goods_combination") {
$builder->where('is_combination', 1);
} else {
$builder->where('is_combination', 0);
}
$data = $builder->filter()->orderByDesc('id')->get()->toArray();
} else {
$ids = array_keys($update);
if (empty($ids)) {
return [$headTitle];
}
$model = GoodsSku::query()
->when($ids, function ($query, $ids) {
return $query->whereIn('id', $ids);
})
->with(['goods' => function ($query) {
$query->with(['type:id,name', 'brand:id,name'])
->orderBy('type_id')
->orderBy('brand_id');
}]);
$data = $model->get()->toArray();
if (empty($data)) {
return [$headTitle];
}
}
$bodyData = [];
foreach ($data as $item) {
$arr[0] = $item['goods']['goods_code'];
$arr[1] = $item['goods']['title'];
$arr[2] = $item['goods']['type']['name'];
$arr[3] = $item['goods']['brand']['name'];
$arr[4] = $item['sku_code'];
$arr[5] = $item['title'];
$arr[0] = $item['external_sku_id'];
$arr[1] = $item['name'];
$arr[2] = $item['sale_stock'] ?? '0';
$arr[3] = $item['stock'] ?? '0';
$arr[4] = $item['created_at'];
if ('cost' === $this->type) {
$arr[6] = (string)$item['cost'];
$arr[7] = (string)$update[$item['id']]['before_update'];
$arr[8] = (string)$update[$item['id']]['after_update'];
$arr[5] = (string)$item['cost'];
$arr[6] = (string)$update[$item['id']]['before_update'];
$arr[7] = (string)$update[$item['id']]['after_update'];
}
if ('inventory' === $this->type) {
$arr[6] = (string)$item['stock'];
$arr[7] = (string)$update[$item['id']]['inventory'];
$arr[8] = (string)$update[$item['id']]['arrived_today_num'];
$arr[5] = (string)$item['stock'];
$arr[6] = (string)$update[$item['id']]['inventory'];
$arr[7] = (string)$update[$item['id']]['arrived_today_num'];
}
$bodyData[] = $arr;
}
@ -97,18 +116,23 @@ class GoodsSkusExport implements FromCollection, ShouldAutoSize
$day = DateTimeUtils::getToday();
$logs = Log::query()
->select(['target_id', 'before_update', 'after_update'])
->where('target_type', 'goods_sku')
->where('target_field', 'cost')
->where('target_type', TargetTypeEnum::PURCHASE)
->where('target_field', 'stock')
->where('created_at', '>', $day)
->orderBy('id', 'asc')
->get();
$update = [];
foreach ($logs as $log) {
if ($log['before_update'] !== $log['after_update'] || (int)$log['after_update']) {
if (!isset($update[$log['target_id']])) {
$update[$log['target_id']]['before_update'] = $log['before_update'];
if (!isset($update[$log['target_id']])) {
$beforeData = json_decode($log['before_update'], true);
if (!empty($beforeData['cost'])) {
$update[$log['target_id']]['before_update'] = $beforeData['cost'];
}
$update[$log['target_id']]['after_update'] = $log['after_update'];
}
$afterData = json_decode($log['after_update'], true);
if (!empty($afterData['cost']) && $afterData['cost'] != $beforeData['cost']) {
$update[$log['target_id']]['after_update'] = $afterData['cost'];
}
}

View File

@ -33,9 +33,8 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
{
$headTitle = [
'商品名称',
'品类',
'规格',
'品牌',
'品种',
'规格名称',
'商品编码',
'成本',
'销售数量',
@ -51,8 +50,7 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
->with([
'goods:id,title',
'goodsType:id,name',
'goodsSku:id,title',
'goodsBrand:id,name',
'goodsSku:id,title,name'
])
->where('date', '>=', $this->startDate)
->where('date', '<=', $this->endDate)
@ -66,10 +64,9 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
$number = $item->total_goods_number - $item->total_cancel_number;
if (!isset($arr[$item->external_sku_id])) {
$arr[$item->external_sku_id] = [
'goodsTitle' => $item->goods->title,
'goodsTypeName' => $item->goodsType->name,
'goodsSkuTitle' => $item->goodsSku->title,
'goodsBrandName' => $item->goodsBrand->name,
'goodsTitle' => !empty($item->goodsSku) ? $item->goodsSku->name:'',
'goodsTypeName' => !empty($item->goodsType) ? $item->goodsType->name : "",
'goodsSkuTitle' => !empty($item->goodsSku) ? $item->goodsSku->title : "",
'external_sku_id' => $item->external_sku_id,
'cost' => [],
'number' => [],
@ -97,7 +94,6 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
$item['goodsTitle'],
$item['goodsTypeName'],
$item['goodsSkuTitle'],
$item['goodsBrandName'],
$item['external_sku_id'],
$cost,
array_sum($item['number']),

View File

@ -10,6 +10,9 @@ class BusinessOrderFilter extends Filters
{
protected function ids($value)
{
if(is_string($value)){
$value = explode(",", $value);
}
return $this->builder->whereIn('id', $value);
}

View File

@ -2,6 +2,8 @@
namespace App\Filters;
use Carbon\Carbon;
class GoodsSkuFilter extends Filters
{
protected function skuTitle($value)
@ -28,4 +30,46 @@ class GoodsSkuFilter extends Filters
{
return $this->builder->where('is_combination', $value);
}
protected function createTimeStart($value)
{
return $this->builder->where('created_at', ">=", $value);
}
protected function createTimeEnd($value)
{
$end = Carbon::parse($value)->endOfDay()->toDateTimeString();
return $this->builder->where('created_at', "<=", $end);
}
protected function minStock($value)
{
return $this->builder->where('stock',">=", $value);
}
protected function maxStock($value)
{
return $this->builder->where('stock',"<=", $value);
}
protected function minSaleStock($value)
{
return $this->builder->where('sale_stock',">=", $value);
}
protected function maxSaleStock($value)
{
return $this->builder->where('sale_stock',"<=", $value);
}
protected function neqSaleStock($value)
{
return $this->builder->where('sale_stock',"!=", $value);
}
protected function neqStock($value)
{
return $this->builder->where('stock',"!=", $value);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Business;
use App\Exports\BusinessAfterOrderExport;
use App\Exports\BusinessOrderExport;
use App\Exports\OrderBlankExport;
use App\Http\Controllers\Controller;
use App\Models\BusinessAfterSaleOrder;
use App\Models\BusinessOrder;
use App\Models\BusinessOrderItem;
use App\Models\GoodsSku;
use App\Models\Shop;
use App\Services\Ship\WayBillService;
use App\Utils\DateTimeUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Http\Resources\BusinessOrderResource;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
class BusinessAfterSaleOrderController extends Controller
{
public function index(Request $request)
{
$shopIds = Shop::query()
->where('plat_id', Shop::$PLAT_KTT)
->pluck('id');
$builder = BusinessAfterSaleOrder::query()
->with(["shop:id,name"])
->whereIn('shop_id', $shopIds)
->filter();
if (!empty($request->created_at_start) & !empty($request->created_at_end)) {
$builder = $builder->whereBetween("after_sale_created_at"
, [$request->created_at_start, $request->created_at_end]);
}
if ($request->get("is_export")) {
$params = $request->validate([
'created_at_start' => 'required',
'created_at_end' => 'required',
], [
'created_at_start.required' => '请输入开始确认时间',
'created_at_end.required' => '请输入结束确认时间',
]);
$startDate = Carbon::parse($params['created_at_start']);
$endDate = Carbon::parse($params['created_at_end']);
if ($endDate->gt($startDate->copy()->addMonth())) {
throw new \Exception("导出时间超出一个月");
}
return Excel::download(new BusinessAfterOrderExport($builder->get()->toArray())
, $startDate . '~' . $endDate . "售后订单数据" . '.xlsx');
}
$businessOrders = $builder->orderByDesc('after_sale_created_at')
->paginate($request->get('per_page'));
$businessOrders->getCollection()->map(function ($v) {
$v->refund_amount = bcdiv($v->refund_amount,100,2);
$v->refund_shipping_amount = bcdiv($v->refund_shipping_amount,100,2);
});
return JsonResource::collection($businessOrders);
}
}

View File

@ -93,7 +93,8 @@ class BusinessGoodsSkusController extends Controller
if (empty($sku)) {
$this->setValidatorFailResponse('未找到对应的商品,请核实后再次同步或删除此平台商品');
} else {
event(new BusinessOrdersUpdate($businessGoodsSku, 0));
$shop = $businessGoodsSku->shop;
BusinessFactory::init()->make($shop['plat_id'])->setShopWithId($shop['id'])->incrQuantity($businessGoodsSku, $sku->sale_stock, false);
$this->res['message'] = '库存同步请求发送成功,具体结果查看日志';
}
return response($this->res, $this->res['httpCode']);

View File

@ -2,18 +2,21 @@
namespace App\Http\Controllers\Business;
use App\Exports\BusinessOrderExport;
use App\Exports\OrderBlankExport;
use App\Http\Controllers\Controller;
use App\Models\BusinessOrder;
use App\Models\BusinessOrderItem;
use App\Models\GoodsSku;
use App\Models\Shop;
use App\Services\Ship\WayBillService;
use App\Models\Waybill;
use App\Services\WayBill\JingDong\WayBillService;
use App\Utils\DateTimeUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Http\Resources\BusinessOrderResource;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Facades\Excel;
class BusinessOrderController extends Controller
@ -26,15 +29,41 @@ class BusinessOrderController extends Controller
$builder = BusinessOrder::query()
->with([
'shop:id,name',
'items:id,business_order_id,goods_name,goods_number,external_sku_id'
'items:id,business_order_id,goods_name,goods_number,external_sku_id',
"waybill"
])
->whereIn('shop_id', $shopIds)
->filter();
$externalSkuIds = $request->get('external_sku_ids');
$externalSkuId = $request->get('external_sku_id');
if (!empty($externalSkuId)) {
$externalSkuIds = $externalSkuId;
}
if ($externalSkuIds) {
if (is_string($externalSkuIds)) {
$externalSkuIds = explode(",", $externalSkuIds);
}
$ids = BusinessOrderItem::query()->whereIn('external_sku_id', $externalSkuIds)->pluck('business_order_id');
$builder->whereIn('id', $ids);
}
if ($request->get("is_export")) {
$params = $request->validate([
'confirm_at_start' => 'required',
'confirm_at_end' => 'required',
], [
'confirm_at_start.required' => '请输入开始确认时间',
'confirm_at_end.required' => '请输入结束确认时间',
]);
$startDate = Carbon::parse($params['confirm_at_start']);
$endDate = Carbon::parse($params['confirm_at_end']);
if ($endDate->gt($startDate->copy()->addMonth())) {
throw new \Exception("导出时间超出一个月");
}
return Excel::download(new BusinessOrderExport($builder->get()->toArray()), $startDate . '~' . $endDate . "订单数据" . '.xlsx');
}
$businessOrders = $builder->orderByDesc('confirm_at')
->paginate($request->get('per_page'));
@ -158,24 +187,25 @@ class BusinessOrderController extends Controller
->whereIn('shop_id', $shopIds)
->filter();
$externalSkuIds = $request->get('external_sku_ids');
if ($externalSkuIds) {
$ids = BusinessOrderItem::query()->whereIn('external_sku_id', $externalSkuIds)->pluck('business_order_id');
$builder->whereIn('id', $ids);
}
if ($ids = $request->input('ids')) {
if (is_string($ids)) {
$ids = explode(",", $ids);
}
$builder->whereIn('id', $ids);
}
$businessOrders = $builder->get();
$waybill = new WayBillService();
$waybill->setOrders($businessOrders);
$contents = $waybill->getContents();
// 待打印数据
[$documents, $orderIds] = $waybill->getDocumentsAndOrderIds($contents);
$contents = $waybill->getWayBillContents();
return response([
'documents' => $this->combinationPrintDocuments($documents),
'order_ids' => implode(',', $orderIds),
'data' => $contents
]);
}
@ -232,9 +262,11 @@ class BusinessOrderController extends Controller
$orderIds = $request->input('order_ids');
$orderIds = explode(',', $orderIds);
BusinessOrder::query()
->where('id', $orderIds)
->increment('print_status');
->whereIn('id', $orderIds)
->update(['print_status' => 1]);
Waybill::query()
->where('order_id', $orderIds)
->update(['status' => Waybill::$STATUS_PRINT_SUCCESS]);
return response(['message' => 'success']);
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\Business;
use App\Events\BusinessOrderCancelEvent;
use App\Events\CancelLogisticEvent;
use App\Exports\BusinessOrderExport;
use App\Exports\OrderBlankExport;
use App\Http\Controllers\Controller;
use App\Models\BusinessOrder;
use App\Models\BusinessOrderItem;
use App\Models\GoodsSku;
use App\Models\Shop;
use App\Models\Waybill;
use App\Services\Business\BusinessFactory;
use App\Services\WayBill\JingDong\JingDongService;
use App\Services\WayBill\JingDong\WayBillService;
use App\Utils\DateTimeUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Http\Resources\BusinessOrderResource;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Facades\Excel;
class WaybillController extends Controller
{
public function queryTrace(Request $request)
{
$params = $request->validate([
'order_id' => 'required',
], [
'order_id.required' => '订单id',
]);
$waybill = Waybill::query()->where("order_id", $params['order_id'])->firstOrFail();
$jingDongService = new JingDongService();
return $jingDongService->queryTrace($waybill);
}
public function cancel(Request $request)
{
$params = $request->validate([
'order_id' => 'required',
], [
'order_id.required' => '订单id',
]);
$waybill = Waybill::query()->where("order_id", $params['order_id'])->firstOrFail();
if (empty($waybill->waybill_code)) {
throw new \Exception("无快递单号可取消");
}
try{
//取消快团团物流
$shop = Shop::query()->findOrFail($waybill->shop_id);
$client = BusinessFactory::init()->make($shop['plat_id'])->setShop($shop);
$client->cancelLogistic($waybill->order_sn, $waybill->waybill_code);
//取消京东物流
$jingDongService = new JingDongService();
$jingDongService->cancelOrder($waybill);
}catch(\Exception $exception) {
Log::error("取消快团团或者京东物流异常", ["error" => $exception->getMessage()]);
}
$waybill->waybill_code = '';
$waybill->encryptedData = '';
$waybill->status = Waybill::$STATUS_INIT;
$waybill->cancel = 1;
$waybill->save();
BusinessOrder::query()->where("id", $params['order_id'])->update(['print_status' => 0]);
return response(['message' => 'success']);
}
}

View File

@ -47,4 +47,23 @@ class Controller extends BaseController
return $this->log->add($targetId, $targetField);
}
public function success($data=[], $msg = "")
{
return [
"success" => true,
"msg" => $msg,
"data" => $data
];
}
public function error($errorCode, $msg = "")
{
return [
"success" => false,
"msg" => $msg,
"errorCode" => $errorCode,
"data" => []
];
}
}

View File

@ -3,10 +3,17 @@
namespace App\Http\Controllers\DataCenter;
use App\Http\Controllers\Controller;
use App\Http\Enum\StaticTypeEnum;
use App\Http\Resources\DailySalesReportResource;
use App\Models\BusinessOrderItem;
use App\Models\DailySalesReport;
use App\Models\GoodsSku;
use App\Services\Statistic\SaleDataService;
use App\Utils\FormatUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
class DataCenterController extends Controller
{
@ -37,4 +44,89 @@ class DataCenterController extends Controller
return DailySalesReportResource::collection($dailySalesReports);
}
/**
* 销售报表
* @param Request $request
* @return void
*/
public function saleStatistics(Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'type' => 'required|integer', //1表示今日
'start_day' => 'sometimes|date_format:Y-m-d',
'end_day' => 'sometimes|date_format:Y-m-d',
'start_time' => 'sometimes|date_format:Y-m-d H:i:s',
'end_time' => 'sometimes|date_format:Y-m-d H:i:s'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
return $this->success(SaleDataService::saleStatistics($request));
}
/**
* spu 维度的数据统计
* @param Request $request
* @return array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|mixed
*/
public function spuSaleStatistics(Request $request)
{
//进行校验验证
$validator = Validator::make($request->all(), [
'type' => 'required|integer', //1表示今日
'start_day' => 'sometimes|date_format:Y-m-d',
'end_day' => 'sometimes|date_format:Y-m-d',
'start_time' => 'sometimes|date_format:Y-m-d H:i:s',
'end_time' => 'sometimes|date_format:Y-m-d H:i:s'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
return $this->success(SaleDataService::spuSaleStatistics($request));
}
public function gmvStatistics(Request $request)
{
//进行校验验证
$validator = Validator::make($request->all(), [
'type' => 'required|integer', //1表示今日
'start_day' => 'required_unless:type,1|date_format:Y-m-d',
'end_day' => 'required_unless:type,1|date_format:Y-m-d',
"interval" => 'sometimes|integer|max:120|min:1',
'start_time' => 'sometimes|date_format:Y-m-d H:i:s',
'end_time' => 'sometimes|date_format:Y-m-d H:i:s',
'sku_id' => 'sometimes|integer'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
return $this->success(SaleDataService::gmvStatistics($request));
}
public function lossRecordStatistics(Request $request)
{
//进行校验验证
$validator = Validator::make($request->all(), [
'start_time' => 'sometimes|date_format:Y-m-d H:i:s',
'end_time' => 'sometimes|date_format:Y-m-d H:i:s',
'sku_id' => 'sometimes|integer',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
return $this->success(SaleDataService::lossRecordStatistics($request));
}
}

View File

@ -7,10 +7,14 @@ use App\Http\Resources\GoodsSkuResource;
use App\Imports\CombinationGoodsImport;
use App\Models\BusinessOrderItem;
use App\Models\CombinationGood;
use App\Models\DeveloperConfig;
use App\Models\Goods;
use App\Models\GoodsSku;
use App\Utils\GeneratorUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
@ -20,43 +24,82 @@ class GoodsCombinationController extends Controller
public function index(Request $request)
{
// ToDo
// 可通过子商品查找主商品
$skus = GoodsSku::query()
->with([
'combinationGoods:id,goods_sku_id,item_id,item_num',
'combinationGoods.goodsSkuItem:id,goods_id,title,stock,external_sku_id,updated_at,yesterday_num,reference_price,status',
'combinationGoods.goodsSkuItem.goods:id,title,img_url',
])
->where('is_combination', 1)
->filter()
->orderBy('stock', 'desc')
->paginate($request->get('per_page'));
$sortField = $request->input('sort_field', 'id');//stock sale_stock order_goods_num
$sortValue = $request->input('sort_value', 'desc');
$fields = implode(',', [
'shop_id',
'external_sku_id',
'sum(goods_number) as number',
'sum(already_cancel_number) as cancel_number',
'SUM(goods_number) - SUM(already_cancel_number) as number',
]);
$orderRestTime = DeveloperConfig::query()
->where('key', DeveloperConfig::$ORDER_RESET_TIME)
->value('value');
if (is_null($orderRestTime)) {
$orderRestTime = date('Y-m-d 07:00:00');
}
$businessOrderItems = BusinessOrderItem::query()
->select(DB::raw($fields))
->with([
'shop:id,name',
'goodsSku:id,external_sku_id,is_combination',
'goodsSku.combinationGoods:id,goods_sku_id,item_id,item_num'
])
->where('created_at', '>', $orderRestTime)
->where('external_sku_id', '<>', '')
->groupBy(['shop_id', 'external_sku_id'])
->orderByDesc('number')
->get()
->toArray();
$ids = $externals = [];
foreach ($businessOrderItems as $businessOrderItem) {
if (is_null($businessOrderItem['goods_sku'])) {
continue;
}
$id = $businessOrderItem['goods_sku']['id'];
if (isset($ids[$id])) {
$ids[$id] += (int)$businessOrderItem['number'];
} else {
$ids[$id] = (int)$businessOrderItem['number'];
}
$externals[$id][] = $businessOrderItem;
}
arsort($ids);
// 可通过子商品查找主商品
$goodsSkusBuilder = GoodsSku::query()
->with([
'combinationGoods:id,goods_sku_id,item_id,item_num',
'combinationGoods.goodsSkuItem:id,name,goods_id,title,stock,sale_stock,external_sku_id,updated_at,yesterday_num,reference_price,status',
'combinationGoods.goodsSkuItem.goods:id,title,img_url',
])
->where('is_combination', 1)
->filter();
if ($sortField == "order_goods_num") {
$finalIds = [];
asort($ids);
foreach ($ids as $id => $number) {
$finalIds[] = $id;
}
$idField = implode(',', $finalIds);
$goodsSkusBuilder->orderByRaw("FIELD(id,{$idField}) {$sortValue}");
} else {
$goodsSkusBuilder->orderBy($sortField, $sortValue);
}
$skus = $goodsSkusBuilder
->paginate($request->get('per_page'));
foreach ($skus as &$item) {
$items = [];
$lastInventoryTime = date('Y-m-d 07:00:00');
$orderDetail = BusinessOrderItem::query()
->select(DB::raw($fields))
->with(['shop:id,name'])
->where('external_sku_id', $item['external_sku_id'])
->when($lastInventoryTime, function ($query) use ($lastInventoryTime) {
$query->where('created_at', '>', $lastInventoryTime);
})
->groupBy(['shop_id', 'external_sku_id'])
->get()
->toArray();
$addOrderGoodsNum = $reduceOrderGoodsNum = 0;
if ($orderDetail) {
$addOrderGoodsNum = array_sum(array_column($orderDetail, 'number'));
$reduceOrderGoodsNum = array_sum(array_column($orderDetail, 'cancel_number'));
if (isset($externals[$item['id']])) {
$item['order_detail'] = $externals[$item['id']];
$item['order_goods_num'] = $ids[$item['id']];
} else {
$item['order_detail'] = [];
$item['order_goods_num'] = 0;
}
$item['order_goods_num'] = $addOrderGoodsNum - $reduceOrderGoodsNum;
$item['order_detail'] = $orderDetail;
$number = BusinessOrderItem::query()
->where('external_sku_id', $item['external_sku_id'])
->sum('goods_number');
@ -65,6 +108,9 @@ class GoodsCombinationController extends Controller
->sum('already_cancel_number');
$item['total_orders_num'] = $number - $cancelNumber;
foreach ($item['combinationGoods'] as $combinationItem) {
$title = !empty($combinationItem['goodsSkuItem']['name']) ? $combinationItem['goodsSkuItem']['name'] :
(!empty($combinationItem['goodsSkuItem']['goods']) ? $combinationItem['goodsSkuItem']['goods']['title']
. " " . $combinationItem['goodsSkuItem']['title'] : $combinationItem['goodsSkuItem']['title']);
$items[] = [
'cost' => 0,
'external_sku_id' => $combinationItem['goodsSkuItem']['external_sku_id'],
@ -75,9 +121,10 @@ class GoodsCombinationController extends Controller
'reference_price' => $combinationItem['goodsSkuItem']['reference_price'],
'status' => $combinationItem['goodsSkuItem']['status'],
'stock' => $combinationItem['goodsSkuItem']['stock'],
'thumb_url' => $combinationItem['goodsSkuItem']['goods']['img_url'],
'img_url' => $combinationItem['goodsSkuItem']['goods']['img_url'],
'title' => $combinationItem['goodsSkuItem']['goods']['title'] . $combinationItem['goodsSkuItem']['title'],
'sale_stock' => $combinationItem['goodsSkuItem']['sale_stock'],
'thumb_url' => null,
'img_url' => null,//图片暂时去掉
'title' => $title,
'updated_at' => $combinationItem['goodsSkuItem']['updated_at'],
'yesterday_num' => $combinationItem['goodsSkuItem']['yesterday_num'],
'order_goods_num' => '请在商品列表查看',
@ -88,15 +135,18 @@ class GoodsCombinationController extends Controller
$item['children'] = $items;
unset($item['combinationGoods']);
}
$rolesName = $request->user()->getRoleNames()->toArray();
$data = ["manage" => ["is_admin" => in_array($rolesName[0]
, ["运营", "超级管理员", "管理员", "系统管理员", "店铺运营"]) ? 1 : 0]];
return GoodsSkuResource::collection($skus);
return GoodsSkuResource::collection($skus)->additional($data);
}
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required',
'external_sku_id' => 'required',
'external_sku_id' => 'sometimes',
'combination_goods.*' => 'required',
'combination_goods.*.item_id' => 'required',
'combination_goods.*.item_num' => 'required|gt:0',
@ -106,20 +156,33 @@ class GoodsCombinationController extends Controller
return response($this->res, $this->res['httpCode']);
}
$externalSkuId = $request->input('external_sku_id') ??
GeneratorUtils::generateCombinationGoodNumber($request->combination_goods);
$hasCodeSku = GoodsSku::query()->where("external_sku_id", $externalSkuId)->first();
if (!empty($hasCodeSku) && $hasCodeSku->id != $request->input('id',0)) {
throw new \Exception("该组合商品编码已存在");
}
DB::beginTransaction();
try {
$combinationGoods = $request->input('combination_goods');
$itemIds = array_column($combinationGoods, 'item_id');
$skus = GoodsSku::query()
->whereIn('id', $itemIds)
->pluck('stock', 'id')
->get()
->pluck(null, 'id')
->toArray();
$stock = [];
$saleStock = [];
foreach ($combinationGoods as $item) {
$stock[] = (int)($skus[$item['item_id']] / $item['item_num']);
if (!empty($skus[$item['item_id']])) {
$stock[] = (int)($skus[$item['item_id']]['stock'] / $item['item_num']);
$saleStock[] = (int)($skus[$item['item_id']]['sale_stock'] / $item['item_num']);
}
}
$stock = min($stock);
$status = $stock ? (5 < $stock ? 1 : 2) : 0;
$saleStock = min($saleStock);
$status = $saleStock ? (5 < $saleStock ? 1 : 2) : 0;
if ($id = $request->input('id')) {
$sku = GoodsSku::query()->findOrFail($id);
} else {
@ -127,11 +190,15 @@ class GoodsCombinationController extends Controller
$sku->goods_id = 0;
$sku->is_combination = 1;
}
$sku->status = $status;
$sku->title = $request->input('title');
$sku->sku_code = $request->input('external_sku_id');
$sku->external_sku_id = $request->input('external_sku_id');
$sku->name = $request->input('title');
$sku->sku_code = $externalSkuId;
$sku->external_sku_id = $externalSkuId;
$sku->stock = $stock;
$sku->sale_stock = $saleStock;
$sku->save();
CombinationGood::query()
->where('goods_sku_id', $sku->id)
@ -180,16 +247,17 @@ class GoodsCombinationController extends Controller
public function goodsSkus(Request $request, $title)
{
$goodsIds = Goods::query()
->where('title', 'like', '%' . $title . '%')
->pluck('id');
$skus = GoodsSku::query()
->whereIn('goods_id', $goodsIds)
->where('name', 'like', '%' . $title . '%')
->where('is_combination', 0)
->with('goods:id,title')
->get(['id', 'title', 'goods_id']);
->get(['id', 'title', 'goods_id', "name"]);
foreach ($skus as &$sku) {
$sku['title'] = $sku['goods']['title'] . $sku['title'];
if (!empty($sku['name'])) {
$sku['title'] = $sku['name'];
} else {
$sku['title'] = ($sku['goods']['title'] ?? "") . $sku['title'];
}
}
return GoodsSkuResource::collection($skus);

View File

@ -5,10 +5,13 @@ namespace App\Http\Controllers\Goods;
use App\Http\Controllers\Controller;
use App\Http\Requests\GoodsSkuRequest;
use App\Http\Resources\GoodsResource;
use App\Models\GoodsType;
use App\Models\Log as LogModel;
use App\Services\Good\GoodService;
use App\Utils\DateTimeUtils;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use App\Models\Goods;
use App\Http\Requests\GoodsRequest;
@ -42,28 +45,23 @@ class GoodsController extends Controller
return response($this->res, $this->res['httpCode']);
}
DB::beginTransaction();
try {
if (!empty($request->goods_id)) {
$goods = Goods::query()->find($request->goods_id);
} else {
$goods = new Goods();
$goods->title = $request->title;
$goods->img_url = $request->img_url;
$goods->type_id = $request->type_id;
$goods->brand_id = $request->brand_id;
$goods->goods_code = $request->goods_code;
$goods->save();
}
$goodService = new GoodService();
$goods = $goodService->saveDefaultGoodsByGoodType($request->type_id);
$goodsSkus = [];
foreach ($request->skus as $item) {
$item['goods_id'] = $goods->id;
$item['stock'] = $item['num'];
$item['stock'] = $item['num'] ?? 0;
$item['reference_price'] = $item['cost'] * 1.5;
$item['external_sku_id'] = $goods->goods_code . '_' . $item['sku_code'];
$item['name'] = $goods->title . $item['title'];
$item['sku_code'] = !empty($item['external_sku_id']) ? $item['external_sku_id'] : $goodService->getRandomCode();
$item['external_sku_id'] = !empty($item['external_sku_id']) ? $item['external_sku_id'] : ($goods->goods_code . '_' . $item['sku_code']);
$item['name'] = $goodService->getSkuName($request->type_id,$item);
$goodsSkus[] = $item;
}
$collection = $goods->skus()->createMany($goodsSkus)->toArray();
$this->setAfterUpdateForLog($collection);
$this->addLog(0, 'add');
@ -89,13 +87,14 @@ class GoodsController extends Controller
return response($this->res, $this->res['httpCode']);
}
public function download()
{
$file = resource_path('templates/goods_skus_import.xlsx');
$file = resource_path('templates/sale_stock_import.xlsx');
$headers = [
'Content-Type: application/xlsx',
];
return response()->download($file, 'goods_skus_import.xlsx', $headers);
return response()->download($file, 'sale_stock_import.xlsx', $headers);
}
}

View File

@ -7,16 +7,22 @@ use App\Events\StockUpdateEvent;
use App\Exports\GoodsSkusExport;
use App\Exports\WeekDataExport;
use App\Http\Controllers\Controller;
use App\Http\Enum\ExcelKeyEnum;
use App\Http\Requests\GoodsRequest;
use App\Http\Requests\GoodsSkuRequest;
use App\Imports\InventoryImport;
use App\Imports\NewSetImport;
use App\Imports\SaleStockImport;
use App\Models\BusinessOrderItem;
use App\Models\DailySalesReport;
use App\Models\DeveloperConfig;
use App\Models\Goods;
use App\Models\Log;
use App\Models\Log as LogModel;
use App\Services\DeveloperConfig\DeveloperConfigService;
use App\Services\Good\GoodService;
use App\Services\GoodSku\GoodSkuService;
use App\Services\Ship\WayBillService;
use App\Utils\ArrayUtils;
use App\Utils\DateTimeUtils;
use Carbon\Carbon;
@ -44,6 +50,86 @@ class GoodsSkusController extends Controller
}
public function index(Request $request)
{
$ids = [];
$externals = $this->buildExternals($ids);
$builder = GoodsSku::query();
$this->preparQueryGoodsSkus($request, $builder);
$day = DateTimeUtils::getToday();
$sortField = $request->input('sort_field', 'id');//stock sale_stock order_goods_num
$sortValue = $request->input('sort_value', 'desc');
$goodsSkusBuilder = (clone $builder)->filter()->with(['goods' => function ($query) {
$query->with(['type' => function ($query) {
$query->with("parentType:id,name")->select(["id", "name", "parent_id"]);
}]);
}])
->with(['daily' => function ($query) use ($day) {
$query->where('day', $day);
}])
->where('is_combination', 0);
$sum = null;
if ($request->get('goods_title')) {
$sumData = (clone $goodsSkusBuilder)->select('id',"stock")->get();
$sum['goods_number'] = 0;
$sum['stock']=0;
if (!empty($sumData)) {
foreach ($sumData as $item) {
$sum['goods_number'] += $ids[$item['id']] ?? 0;
$sum['stock'] += $item['stock'] ?? 0;
}
}
$sum['real_stock'] = ($sum['stock'] ?? 0) + $sum['goods_number'];
}
if ($sortField == "order_goods_num") {
$finalIds = [];
asort($ids);
foreach ($ids as $id => $number) {
$finalIds[] = $id;
}
if (empty($finalIds)) {
$goodsSkusBuilder->orderBy("id", $sortValue);
} else {
$idField = implode(',', $finalIds);
$goodsSkusBuilder->orderByRaw("FIELD(id,{$idField}) {$sortValue}");
}
} else {
$goodsSkusBuilder->orderBy($sortField, $sortValue);
}
$goodsSkus = $goodsSkusBuilder->paginate($request->get('per_page'));
$rolesName = $request->user()->getRoleNames()->toArray();
foreach ($goodsSkus as &$sku) {
$lastInventoryTime = !empty($sku['daily']['inventory_time']) ? $sku['daily']['inventory_time'] : date('Y-m-d 07:00:00');
if (isset($externals[$sku['id']])) {
$sku['order_detail'] = $externals[$sku['id']];
$sku['order_goods_num'] = $ids[$sku['id']] ?? 0;
} else {
$sku['order_detail'] = [];
$sku['order_goods_num'] = 0;
}
$sku['inventory_time'] = $lastInventoryTime;
if ('销售' === $rolesName[0]) {
$sku['cost'] = 0;
}
if (!empty($sku['yesterday_num'])) {
$sku['sale_ratio'] = round($sku['stock'] / $sku['yesterday_num'], 2) * 100;
} else {
$sku['sale_ratio'] = 0;
}
$sku['yesterday_num'] = bcadd($sku['stock'], $sku['order_goods_num']);
}
$data = [
"manage" => ["is_admin" => in_array($rolesName[0], ["运营", "超级管理员", "管理员", "系统管理员", "店铺运营"]) ? 1 : 0],
"sum" => $sum
];
return GoodsSkuResource::collection($goodsSkus)->additional($data);
}
private function buildExternals(&$ids)
{
$fields = implode(',', [
'shop_id',
@ -60,7 +146,8 @@ class GoodsSkusController extends Controller
->select(DB::raw($fields))
->with([
'shop:id,name',
'goodsSku:id,external_sku_id'
'goodsSku:id,external_sku_id,is_combination',
'goodsSku.combinationGoods:id,goods_sku_id,item_id,item_num'
])
->where('created_at', '>', $orderRestTime)
->where('external_sku_id', '<>', '')
@ -68,70 +155,33 @@ class GoodsSkusController extends Controller
->orderByDesc('number')
->get()
->toArray();
$ids = $externals = [];
$externals = [];
foreach ($businessOrderItems as $businessOrderItem) {
if (is_null($businessOrderItem['goods_sku'])) {
continue;
}
$id = $businessOrderItem['goods_sku']['id'];
if ($businessOrderItem['goods_sku']['is_combination']) {
foreach ($businessOrderItem['goods_sku']['combination_goods'] ?? [] as $combinationGoods) {
$ids[$combinationGoods['item_id']] = ($ids[$combinationGoods['item_id']] ?? 0)
+ ((int)$businessOrderItem['number']) * $combinationGoods['item_num'];
$businessOrderItem['goods_number'] = ((int)$businessOrderItem['number']) * $combinationGoods['item_num'];
$externals[$combinationGoods['item_id']][] = $businessOrderItem;
}
}
if (isset($ids[$id])) {
$ids[$id] += (int)$businessOrderItem['number'];
} else {
$ids[$id] = (int)$businessOrderItem['number'];
}
$externals[$businessOrderItem['external_sku_id']][] = $businessOrderItem;
}
arsort($ids);
$builder = GoodsSku::query();
$this->preparQueryGoodsSkus($request, $builder);
$day = DateTimeUtils::getToday();
$goodsSkus = (clone $builder)->filter()
->where('is_combination', 0)
->orderByDesc('stock')
->pluck('stock', 'id')
->toArray();
$finalIds = [];
foreach ($ids as $id => $number) {
if (isset($goodsSkus[$id])) {
$finalIds[] = $id;
unset($goodsSkus[$id]);
}
}
$finalIds = array_merge($finalIds, array_keys($goodsSkus));
$idField = implode(',', $finalIds);
$goodsSkus = (clone $builder)->with(['goods' => function ($query) {
$query->with(['type:id,name', 'brand:id,name']);
}])
->with(['daily' => function ($query) use ($day) {
$query->where('day', $day);
}])
->whereIn('id', $finalIds)
->orderByRaw("FIELD(id, {$idField})")
->paginate($request->get('per_page'));
$rolesName = $request->user()->getRoleNames()->toArray();
foreach ($goodsSkus as &$sku) {
$lastInventoryTime = $sku['daily']['inventory_time'] ?: date('Y-m-d 07:00:00');
if (isset($externals[$sku['external_sku_id']])) {
$sku['order_detail'] = $externals[$sku['external_sku_id']];
$sku['order_goods_num'] = array_sum(array_column($externals[$sku['external_sku_id']], 'number'));
} else {
$sku['order_detail'] = [];
$sku['order_goods_num'] = 0;
}
$sku['order_goods_num'] -= $sku['daily']['reissue_num'];
$sku['inventory_time'] = $lastInventoryTime;
if ('销售' === $rolesName[0]) {
$sku['cost'] = 0;
}
$externals[$id][] = $businessOrderItem;
}
return GoodsSkuResource::collection($goodsSkus);
return $externals;
}
private function preparQueryGoodsSkus(Request $request, &$builder)
{
if ($request->get('keyword_type') && $request->get('keyword_value')) {
@ -189,22 +239,14 @@ class GoodsSkusController extends Controller
$sku = GoodsSku::query()->find($id);
$this->setBeforeUpdateForLog($sku->toArray());
$skuInfo = $request->sku;
$skuInfo['external_sku_id'] = $request->goods['goods_code'] . '_' . $request->sku['sku_code'];
$skuInfo['name'] = $request->goods['title'] . $request->sku['title'];
$goodService = new GoodService();
$goods = $goodService->saveDefaultGoodsByGoodType($request->goods['type_id']);
$skuInfo['goods_id'] = $goods->id;
$skuInfo['name'] = $goodService->getSkuName($request->goods['type_id'], $skuInfo);
$sku->update($skuInfo);
$this->setAfterUpdateForLog($sku->toArray());
$this->addLog($id, 'update');
// 商品更新
$goods = Goods::query()->find($sku->goods_id);
$this->log = new LogModel([
'module' => 'goods',
'action' => $request->getMethod(),
'target_type' => 'goods',
]);
$this->setBeforeUpdateForLog($goods->toArray());
$goods->update($request->goods);
$this->setAfterUpdateForLog($goods->toArray());
$this->addLog($sku->goods_id, 'update');
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
@ -221,7 +263,7 @@ class GoodsSkusController extends Controller
public function batchUpdate(Request $request)
{
$appendRules = [
'updateType' => ['required', 'string', Rule::in(['newest', 'inventory', 'stock'])],
'updateType' => ['required', 'string', Rule::in(['newest', 'inventory', 'stock', "saleStock"])],
'skus' => ['required', 'array'],
'skus.*.id' => [
'required',
@ -240,6 +282,62 @@ class GoodsSkusController extends Controller
return $this->$function($request);
}
/**
* 新版本运营管理 主要修改在线库存和成本价(修订) 看看是否需要单独提供修改
*
* @param $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
*/
private function saleStock($request)
{
$updateIds = [];
DB::beginTransaction();
try {
$logs = [];
foreach ($request->skus as $sku) {
if ($sku['sale_stock'] < 0) {
throw new \Exception("商品id{$sku['id']}在售库存数不能小于0");
}
$costLog = [
'module' => 'goods',
'action' => $request->getMethod(),
'target_type' => 'goods_sku',
'target_id' => $sku['id'],
'user_id' => $request->user()->id
];
// 成本
$goodsSku = GoodsSku::query()->where('id', $sku['id'])->first(['id', 'cost', 'sale_stock']);
$costLog['target_field'] = 'sale_stock';
$costLog['before_update'] = $goodsSku->sale_stock;
$goodsSku->sale_stock = $sku['sale_stock'];
if (0 >= $goodsSku->sale_stock) {
$status = GoodsSku::$STATUS_DOWN;
} else {
$status = GoodsSku::$STATUS_ON_SALE;
}
$goodsSku->status = $status;
$goodsSku->save();
$costLog['after_update'] = $goodsSku->sale_stock;
$logs[] = $costLog;
$updateIds[] = $sku['id'];
}
$log = new LogModel();
$log->batchInsert($logs);
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
$this->res = [
'httpCode' => 400,
'errorCode' => 400500,
'errorMessage' => $exception->getMessage(),
];
}
if (!empty($updateIds)) {
event(new BatchStockUpdateEvent($updateIds));
}
return response($this->res, $this->res['httpCode']);
}
/**
* 上新
*
@ -297,7 +395,7 @@ class GoodsSkusController extends Controller
}
/**
* 库存盘点
* 库存盘点 准备废弃中
*
* @param $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
@ -377,6 +475,7 @@ class GoodsSkusController extends Controller
}
return response($this->res, $this->res['httpCode']);
}
/**
@ -472,7 +571,7 @@ class GoodsSkusController extends Controller
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
goto end;
return response($this->res, $this->res['httpCode']);
}
$updateField = \request('updateField');
$sku = GoodsSku::query()->find($id);
@ -494,10 +593,11 @@ class GoodsSkusController extends Controller
$changeNum = $sku->reserve - $request->reserve;
if (0 > $changeNum + $sku->stock) {
$this->setValidatorFailResponse('预留量超过库存数量');
goto end;
return response($this->res, $this->res['httpCode']);
}
$sku->stock += $changeNum;
}
$sku->$updateField = $request->$updateField;
$sku->save();
$this->setAfterUpdateForLog($sku->$updateField);
@ -551,6 +651,7 @@ class GoodsSkusController extends Controller
$endDate = Carbon::now()->subWeek()->endOfWeek()->toDateString();
return Excel::download(new WeekDataExport($startDate, $endDate), $startDate . '~' . $endDate . '.xlsx');
}
return Excel::download(new GoodsSkusExport($type), $type . '.xlsx');
}
@ -659,4 +760,25 @@ class GoodsSkusController extends Controller
return $data;
}
public function saleStockImport(Request $request)
{
if (!$request->hasFile(ExcelKeyEnum::SALE_STOCK_FILE)) {
$this->res = [
'httpCode' => 404,
'errorCode' => 404404,
'errorMessage' => 'not found inventory file',
];
}
try {
$import = new SaleStockImport();
$path = $request->file(ExcelKeyEnum::SALE_STOCK_FILE);
Excel::import($import, $path);
$this->addLog(0, 'import', ExcelKeyEnum::SALE_STOCK_FILE);
} catch (ValidationException $exception) {
$this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Http\Resources\GoodsTypeResource;
use App\Models\GoodsType;
use App\Models\Log as LogModel;
use App\Utils\FormatUtils;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
@ -23,7 +24,11 @@ class GoodsTypesController extends Controller
public function index(Request $request)
{
$goodsTypes = GoodsType::query()->paginate($request->get('per_page'));
$build = GoodsType::query();
if(!empty($request->level)){
$build->where("level","=",$request->level);
}
$goodsTypes = $build->paginate($request->get('per_page'));
return GoodsTypeResource::collection($goodsTypes);
}
@ -31,27 +36,34 @@ class GoodsTypesController extends Controller
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'names' => 'required|array',
'names.*' => 'required|string|max:191|unique:goods_types,name',
'name' => 'required|string|max:191',
'parent_id' => 'sometimes',
'show' => 'sometimes|integer',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsTypes = [];
foreach ($request->names as $name) {
$goodsTypes[] = ['name' => $name];
$level = 1;
if ($request->parent_id) {
$parentGoodsType = GoodsType::query()->where("id", "=", $request->parent_id)->get()->toArray();
$level = $parentGoodsType['level'] ?? 1 + 1;
$existTypeName = GoodsType::query()->where("parent_id", "=", $request->parent_id)
->where("name",$request->name)->first();
if($existTypeName){
throw new \Exception("当前品类下该名称已存在");
}
}
$goodsType = new GoodsType();
if (!$goodsType->batchInsert($goodsTypes)) {
$this->res = [
'httpCode' => 500,
'errorCode' => 500500,
'errorMessage' => '批量添加失败',
];
}
$this->setAfterUpdateForLog($goodsTypes);
$goodsType->parent_id = $request->parent_id ?? 0;
$goodsType->show = $request->show ?? 1;
$goodsType->name = $request->name;
$goodsType->level = $level;
$goodsType->save();
$this->setAfterUpdateForLog($request->all());
$this->addLog(0, 'add');
return response($this->res, $this->res['httpCode']);
@ -65,12 +77,9 @@ class GoodsTypesController extends Controller
public function update($id, Request $request)
{
$validator = Validator::make($request->all(), [
'name' => [
'required',
'string',
'max:191',
Rule::unique('goods_types')->ignore($id),
]
'name' => 'sometimes|string|max:191',
'parent_id' => 'sometimes',
'show' => 'sometimes|integer',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
@ -79,7 +88,18 @@ class GoodsTypesController extends Controller
}
$goodsType = GoodsType::query()->find($id);
$this->setBeforeUpdateForLog($goodsType->name);
$goodsType->name = request('name');
$level= 1;
if ($request->parent_id) {
$parentGoodsType = GoodsType::query()->where("id", "=", $request->parent_id)->get()->toArray();
$level = $parentGoodsType['level'] ?? 1 + 1;
}
$goodsType->parent_id = $request->parent_id ?? 0;
$goodsType->show = $request->show ?? 1;
if($request->name){
$goodsType->name = $request->name;
}
$goodsType->level = $level;
$goodsType->save();
$this->setAfterUpdateForLog($goodsType->name);
$this->addLog($id, 'name');
@ -95,4 +115,11 @@ class GoodsTypesController extends Controller
return response($this->res, $this->res['httpCode']);
}
public function tree(Request $request)
{
$goodsTypes = GoodsType::query()->get()->toArray();
$menus = FormatUtils::formatTreeData($goodsTypes, $request->parent_id??0);
return $this->success($menus);
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace App\Http\Controllers\Message;
use App\Http\Controllers\Controller;
use App\Http\Enum\Goods\SkuStatusEnum;
use App\Http\Enum\Message\MessageTypeEnum;
use App\Models\GoodsSku;
use App\Models\WebsiteMessages;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class WebsiteMessageController extends Controller
{
public function index(Request $request)
{
$roleIds = collect($request->user()->roles)->pluck('id')->toArray() ?? [];
$status = $request->input('status') ?? 0;
$websiteMessage = WebsiteMessages::query()->where('status', '=', $status);
if (!empty($roleIds)) {
$websiteMessagePage = $websiteMessage->whereIn('role_id', $roleIds)
->paginate($request->get('per_page'));
return JsonResource::collection($websiteMessagePage);
}
return JsonResource::collection([]);
}
public function update($id, Request $request)
{
$validator = Validator::make($request->all(), [
'status' => ['required', 'integer', 'in:0,1']
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
//更新站內信狀態
$websiteMessages = WebsiteMessages::query()->find($id);
if (empty($websiteMessages)) {
$this->res = [
'httpCode' => 400,
'errorCode' => 400404,
'errorMessage' => "站内信数据不存在",
];
return response($this->res, "400");
}
$websiteMessages->status = $request->status;
Log::info("管理員更新站內信", (array)$request->user());
$websiteMessages->save();
if ($request->status == 1) {
//标记已读触发
$this->hook($websiteMessages);
}
return $websiteMessages;
}
public function hook($websiteMessages)
{
if ($websiteMessages->type == MessageTypeEnum::LOW_STOCK_NOTICE) {
$keyArray = explode("-", $websiteMessages->unique_key);//时间Ymd-对应id-角色id
$skuId = $keyArray[1] ?? 0;
if (!empty($skuId)) {
//变更回上架状态
GoodsSku::query()->where("id", "=", $skuId)->update(["status" => SkuStatusEnum::UP]);
}
}
}
public function batchRead(Request $request)
{
$params = $request->validate([
'ids' => 'required|array'
], [
'ids.required' => '需要操作的ids'
]);
WebsiteMessages::query()->whereIn('id', $params['ids'])->update(['status' => 1]);
return $this->success();
}
}

View File

@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Models\Log as LogModel;
use App\Utils\ArrayUtils;
use App\Utils\FormatUtils;
use Illuminate\Support\Facades\Log;
use Spatie\Permission\Models\Permission;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

View File

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Shop;
use App\Http\Controllers\Controller;
use App\Models\BusinessGoodsSku;
use App\Models\DeveloperConfig;
use App\Models\GoodsSku;
use App\Models\Shop;
use App\Http\Resources\ShopsResource;
use App\Models\ShopSender;
use App\Models\ShopShip;
use App\Services\Business\KuaiTuanTuan\FaceSheet;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use App\Services\Business\BusinessFactory;
use Illuminate\Validation\Rule;
use App\Models\BusinessOrderItem;
class ShopSendsController extends Controller
{
public function index(Request $request)
{
$shopSender = ShopSender::query()->filter()->paginate($request->get('per_page'));
return JsonResource::collection($shopSender);
}
public function store(Request $request)
{
$params = $request->validate([
'id' => 'sometimes',
'shop_id' => 'required|int',
'province' => 'required',
'city' => 'required',
'district' => 'required',
'detail' => 'required',
'name' => 'required',
'mobile' => 'required',
'sort' => 'sometimes',
'wp_code' => 'sometimes',
], [
'shop_id.required' => '请选择店铺',
'province.required' => '请选择省份',
'city.required' => '请选择城市',
'district.required' => '请选择地区',
'detail.required' => '请填写详细地址',
]);
$params['wp_code'] = $params['wp_code'] ?? 'JD';
$params['country'] = $params['country'] ?? '中国';
if (empty($params['id'])) {
$shopSender = new ShopSender();
$shopSender->fill($params);
$shopSender->save();
} else {
ShopSender::query()->where('id', $params['id'])->update($params);
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -13,6 +13,7 @@ use App\Models\ShopShip;
use App\Services\Business\KuaiTuanTuan\FaceSheet;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use App\Services\Business\BusinessFactory;
use Illuminate\Validation\Rule;
@ -52,13 +53,16 @@ class ShopsController extends Controller
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:191|unique:shops,name',
'plat_id' => 'required|integer',
'ratio' => 'required',
'ratio' => 'sometimes',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
if(empty($request->ratio)){
$request->ratio = "*1";
}
$operator = substr($request->ratio, 0, 1);
if (!in_array($operator, ['+', '-', '*', '/'])) {
$this->res->errorMessage = '运算符号仅允许+,-,*,/';
@ -128,6 +132,7 @@ class ShopsController extends Controller
$business->bindGoods($request->get('data'));
}
if ('orders' === $request->get('type')) {
Log::info('秒选order',[$request->get('data')]);
$business->saveOrders($request->get('data'));
}
@ -203,10 +208,11 @@ class ShopsController extends Controller
} else {
$shops = $builder->where('id', $shopId)->get();
}
//同步三方的是在售库存
$skus = GoodsSku::query()
->where('updated_at', '>', date('Y-m-d 07:01:00'))
->whereNotNull('external_sku_id')
->pluck('stock', 'external_sku_id')
->pluck('sale_stock', 'external_sku_id')
->toArray();
foreach ($shops as $shop) {
$business = BusinessFactory::init()->make($shop->plat_id);
@ -220,6 +226,7 @@ class ShopsController extends Controller
->get()
->toArray();
$business->batchIncrQuantity($businessGoodsSkus, $stock, false);
usleep(10);
}
}

View File

@ -0,0 +1,141 @@
<?php
namespace App\Http\Controllers\Supplier;
use App\Events\BatchStockUpdateEvent;
use App\Http\Controllers\Controller;
use App\Http\Enum\ExcelKeyEnum;
use App\Imports\InventoryImport;
use App\Imports\LossImport;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\Log as LogModel;
use App\Services\GoodSku\GoodSkuService;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
class DailyStockRecordController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'supplier',
'action' => $request->getMethod(),
'target_type' => 'dailyStockRecord',
]);
}
public function index(Request $request)
{
$build = DailyStockRecord::query()->whereNotNull("inventory_time")->filter()->with("goodsSku:id,name,title,external_sku_id");
if (!empty($request->title)) {
$build->whereHas('goodsSku', function ($query) use ($request) {
$query->where('name', 'like', '%' . $request->title . '%');
});
}
if (!empty($request->get('external_sku_id'))) {
$build->whereHas('goodsSku', function ($query) use ($request) {
$query->where('external_sku_id', '=', $request->external_sku_id);
});
}
$start = $request->get("start_time");
$end = $request->get("end_time");
$dailyStockRecord = $build->when($start, function ($query) use ($start) {
$query->where("created_at", ">=", $start);
})->when($end, function ($query) use ($end) {
$query->where("created_at", "<=", $end);
})->orderByDesc("id")->paginate($request->get('per_page'));
return JsonResource::collection($dailyStockRecord);
}
/**
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'external_sku_id' => 'required|string',
'inventory' => 'required|integer',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsSku = GoodsSku::query()->with("combinationGoods")->where('external_sku_id', "=", $request->external_sku_id)->first();
if (empty($goodsSku)) {
$this->res = [
'httpCode' => 400,
'message' => '查询不到sku信息',
'errorCode' => "ERP001",
'errorMessage' => '查询不到sku信息',
];
return response($this->res, $this->res['httpCode']);
}
$goodsSkuWithInventory = array_merge($goodsSku->toArray(), ["inventory" => $request->inventory]);
//同批量逻辑操作
$goodSkuService = new GoodSkuService();
$goodSkuService->inventory([$goodsSkuWithInventory]);
return response($this->res, $this->res['httpCode']);
}
/**
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
*/
public function inventoryBatchStore(Request $request)
{
$validator = Validator::make($request->all(), [
'inventoryOrders' => 'required|array',
'inventoryOrders.*.external_sku_id' => 'required|string',
'inventoryOrders.*.inventory' => 'required|integer',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$inventoryKeyByExternalSkuIdMap = collect($request->inventoryOrders)->pluck(null, "external_sku_id")->toArray();
$goodsSkus = GoodsSku::query()->with("combinationGoods")->whereIn('external_sku_id', array_keys($inventoryKeyByExternalSkuIdMap))
->get()->toArray();
$goodsSkus = collect($goodsSkus)->map(function ($v) use ($inventoryKeyByExternalSkuIdMap) {
if (!empty($inventoryKeyByExternalSkuIdMap[$v['external_sku_id']])) {
$v['inventory'] = $inventoryKeyByExternalSkuIdMap[$v['external_sku_id']]['inventory'];
return $v;
}
})->toArray();
Log::info("goodsSkus", $goodsSkus);
$goodSkuService = new GoodSkuService();
$goodSkuService->inventory($goodsSkus);
return response($this->res, $this->res['httpCode']);
}
public function inventoryImport(Request $request)
{
if (!$request->hasFile('inventoryFile')) {
$this->res = [
'httpCode' => 404,
'errorCode' => 404404,
'errorMessage' => 'not found inventory file',
];
}
try {
$import = new InventoryImport();
$path = $request->file('inventoryFile');
Excel::import($import, $path);
$this->addLog(0, 'import', 'inventory');
} catch (ValidationException $exception) {
$this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -0,0 +1,231 @@
<?php
namespace App\Http\Controllers\Supplier;
use App\Events\BatchStockUpdateEvent;
use App\Http\Controllers\Controller;
use App\Http\Enum\ExcelKeyEnum;
use App\Imports\LossImport;
use App\Models\DailyStockRecord;
use App\Models\Goods;
use App\Models\GoodsSku;
use App\Models\Log;
use App\Models\Log as LogModel;
use App\Models\LossRecords;
use App\Models\Suppliers;
use App\Models\User;
use App\Services\GoodSku\GoodSkuService;
use App\Utils\DateTimeUtils;
use App\Utils\GeneratorUtils;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
class LossRecordController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'supplier',
'action' => $request->getMethod(),
'target_type' => 'loss_record',
]);
}
public function index(Request $request)
{
$builder = LossRecords::query()->filter()->with("goodsSku:name,external_sku_id,title");
if (!empty($request->start_time) && !empty($request->end_time)) {
$builder->whereBetween('created_at', [$request->start_time, $request->end_time]);
}
if (!empty($request->title)) {
$builder->whereHas('goodsSku', function ($query) use ($request) {
$query->where('name', 'like', '%' . $request->title . '%');
});
}
$dailyStockRecord = $builder->orderByDesc("id")->paginate($request->get('per_page'));
return JsonResource::collection($dailyStockRecord);
}
public function store(Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'external_sku_id' => 'required|string',
'num' => 'required|integer',
'reason' => 'sometimes|string',
'buyer_name' => 'sometimes|string'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsSku = GoodsSku::query()->where('external_sku_id', "=", $request->external_sku_id)->first();
if (!empty($goodsSku)) {
$buyerUserId = User::query()->where("name", $allParams['buyer_name'] ?? '')
->pluck("id")->first();
$today = DateTimeUtils::getToday();
DB::beginTransaction();
try {
//保存記錄
$lossRecords = new LossRecords();
$lossRecords->batch_number = GeneratorUtils::generateBatchNumber();
$lossRecords->external_sku_id = $allParams['external_sku_id'];
$lossRecords->num = $allParams['num'];
$lossRecords->cost = $allParams['cost'];
$lossRecords->date = $allParams['date'] ?? $today;
$lossRecords->buyer_user_id = $allParams['buyer_user_id'] ?? ($buyerUserId ?? 0);
$lossRecords->buyer_name = $allParams['buyer_name'] ?? '';
$lossRecords->reason = $allParams['reason'] ?? '';
$lossRecords->phenomenon = $v['phenomenon'] ?? '';
$lossRecords->save();
$updateIds = GoodSkuService::computeSkuStock($goodsSku->toArray()
, ['num' => 0 - $allParams['num'], "cost" => $allParams['cost'], "user_id" => $request->user()->id]);
DB::commit();
event(new BatchStockUpdateEvent($updateIds));
} catch (\Exception $exception) {
DB::rollBack();
}
} else {
$this->res = [
'httpCode' => 400,
'message' => '查询不到sku信息',
'errorCode' => "ERP001",
'errorMessage' => '查询不到sku信息',
];
}
return response($this->res, $this->res['httpCode']);
}
public function update($id, Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'reason' => 'sometimes|string',
'buyer_name' => 'sometimes|string',
'buyer_user_id' => 'sometimes|integer'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$lossRecords = LossRecords::query()->find($id);
if (!empty($lossRecords)) {
//更新記錄
$lossRecords->buyer_name = $allParams['buyer_name'] ?? '';
$lossRecords->buyer_user_id = $allParams['buyer_user_id'] ?? 0;
$lossRecords->reason = $allParams['reason'] ?? '';
$lossRecords->phenomenon = $allParams['phenomenon'] ?? '';
$lossRecords->save();
} else {
$this->res = [
'httpCode' => 400,
'message' => '查询不到sku信息',
'errorCode' => "ERP001",
'errorMessage' => '查询不到sku信息',
];
}
return response($this->res, $this->res['httpCode']);
}
public function lossBatchStore(Request $request)
{
$validator = Validator::make($request->all(), [
'lossOrders' => 'required|array',
'lossOrders.*.external_sku_id' => 'required|string',
'lossOrders.*.num' => 'required|integer',
'lossOrders.*.reason' => 'sometimes|string',
'lossOrders.*.buyer_name' => 'sometimes|string',//采购商
'lossOrders.*.phenomenon' => 'sometimes|string',
'lossOrders.*.date' => 'sometimes|date_format:Y-m-d'
]);
//参数校验
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$lossOrders = $request->input('lossOrders') ?? [];
$externalSkuIds = collect($lossOrders)->pluck("external_sku_id")->toArray();
$goodsSku = GoodsSku::query()->whereIn('external_sku_id', $externalSkuIds)->get();
if ($goodsSku->pluck("external_sku_id")->diff($externalSkuIds)->isNotEmpty()) {
$content = implode(',', $goodsSku->pluck("external_sku_id")->diff($externalSkuIds)->toArray());
$content .= "以上sku编码数据库中不存在";
return response($content, $this->res['httpCode']);
}
$goodsSkuMap = $goodsSku->pluck(null, 'external_sku_id')->toArray();
$allUpdateIds = [];
$batchNumber = GeneratorUtils::generateBatchNumber();
$today = DateTimeUtils::getToday();
//开始保存数据
foreach ($lossOrders as $v) {
DB::beginTransaction();
try {
$goodsSkuItem = $goodsSkuMap[$v['external_sku_id']];
//保存記錄
$lossRecords = new LossRecords();
$lossRecords->batch_number = $batchNumber;
$lossRecords->external_sku_id = $v['external_sku_id'];
$lossRecords->num = $v['num'];
$lossRecords->cost = $v['cost'];
$lossRecords->date = $v['date'] ?? $today;
$lossRecords->buyer_user_id = $v['buyer_user_id'] ?? 0;
$lossRecords->buyer_name = $v['buyer_name'] ?? '';
$lossRecords->reason = $v['reason'] ?? '';
$lossRecords->phenomenon = $v['phenomenon'] ?? '';
$lossRecords->save();
$updateIds = GoodSkuService::computeSkuStock($goodsSkuItem
, ['num' => 0 - $v['num'], "cost" => $v['cost'], "user_id" => $request->user()->id]);
DB::commit();
$allUpdateIds = array_merge($allUpdateIds, $updateIds);
} catch (\Exception $exception) {
DB::rollBack();
}
}
event(new BatchStockUpdateEvent(collect($allUpdateIds)->unique()->toArray()));
return response($this->res, $this->res['httpCode']);
}
/**
* 报损单单后台批量导入
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|void
*/
public function lossImport(Request $request)
{
if (!$request->hasFile(ExcelKeyEnum::LOSS_KEY)) {
$this->res = [
'httpCode' => 404,
'errorCode' => 404404,
'errorMessage' => 'not found loss file',
];
}
try {
$import = new LossImport();
$path = $request->file(ExcelKeyEnum::LOSS_KEY);
Excel::import($import, $path);
$this->addLog(0, 'import', ExcelKeyEnum::LOSS_KEY);
} catch (ValidationException $exception) {
$this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -0,0 +1,320 @@
<?php
namespace App\Http\Controllers\Supplier;
use App\Events\BatchStockUpdateEvent;
use App\Http\Controllers\Controller;
use App\Http\Enum\ExcelKeyEnum;
use App\Http\Enum\Purchase\PurchaseConfigEnum;
use App\Http\Enum\Purchase\PurchaseStatusEnum;
use App\Http\Enum\TargetTypeEnum;
use App\Http\Resources\GoodsSkuResource;
use App\Http\Resources\RolesResource;
use App\Imports\PurchaseImport;
use App\Models\GoodsSku;
use App\Models\Log;
use App\Models\Log as LogModel;
use App\Models\PurchaseRecords;
use App\Models\Suppliers;
use App\Models\User;
use App\Services\DeveloperConfig\DeveloperConfigService;
use App\Services\GoodSku\GoodSkuService;
use App\Utils\DateTimeUtils;
use App\Utils\GeneratorUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
class PurchaseRecordController extends Controller
{
public $isAutoCheck = 0;
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'supplier',
'action' => $request->getMethod(),
'target_type' => 'purchase_record',
]);
}
public function index(Request $request)
{
$builder = PurchaseRecords::query()->filter()->with("goodsSku:id,name,external_sku_id,title");
if (!empty($request->start_time) && !empty($request->end_time)) {
$builder->whereBetween('created_at', [$request->start_time, $request->end_time]);
}
if (!empty($request->date_start_time) && !empty($request->date_end_time)) {
$dateStart = Carbon::parse($request->date_start_time)->toDateString();
$dateEnd = Carbon::parse($request->date_end_time)->subDay()->toDateString();
$builder->whereBetween('date', [$dateStart, $dateEnd]);
}
if (!empty($request->title)) {
$builder->whereHas('goodsSku', function ($query) use ($request) {
$query->where('name', 'like', '%' . $request->title . '%');
});
}
$dailyStockRecord = $builder->orderByDesc("id")->paginate($request->get('per_page'));
return JsonResource::collection($dailyStockRecord);
}
public function store(Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'external_sku_id' => 'required|string',
'num' => 'required|integer',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsSku = GoodsSku::query()->where('external_sku_id', "=", $request->external_sku_id)->first();
if (!empty($goodsSku)) {
$expireDay = DeveloperConfigService::getDefaultExpireDay();
$today = DateTimeUtils::getToday();
if (empty($allParams['buyer_user_id'])) {
$buyerUserId = User::query()->where("name", $allParams['buyer_name'] ?? '')
->pluck("id")->first();
}
if (empty($allParams['supplier_id'])) {
$supplierId = Suppliers::query()->where("supplier_name", $allParams['supplier_name'] ?? '')
->pluck("id")->first();
}
//保存記錄
$purchaseRecords = new PurchaseRecords();
$purchaseRecords->external_sku_id = $allParams['external_sku_id'];
$purchaseRecords->batch_number = GeneratorUtils::generateBatchNumber();
$purchaseRecords->num = $allParams['num'];
$purchaseRecords->cost = $allParams['cost'];
$purchaseRecords->date = $today;
$purchaseRecords->buyer_user_id = $allParams['buyer_user_id'] ?? ($buyerUserId ?? 0);
$purchaseRecords->buyer_name = $allParams['buyer_name'] ?? '';
$purchaseRecords->supplier_name = $allParams['supplier_name'] ?? '';
$purchaseRecords->supplier_id = $allParams['supplier_id'] ?? ($supplierId ?? 0);
if (!empty($allParams['expire_time'])) {
$purchaseRecords->expire_time = Carbon::parse($allParams['expire_time'])->toDateTimeString();
} else {
$purchaseRecords->expire_time = Carbon::now()->addDays($expireDay)->toDateTimeString();
}
$purchaseRecords->save();
if (PurchaseConfigEnum::IS_AUTO_CHECK) {
$allParams['user_id'] = $request->user()->id;
$updateIds = GoodSkuService::computeSkuStock($goodsSku->toArray(), $allParams, TargetTypeEnum::PURCHASE);
event(new BatchStockUpdateEvent($updateIds));
}
} else {
$this->res = [
'httpCode' => 400,
'message' => '查询不到sku信息',
'errorCode' => "ERP001",
'errorMessage' => '查询不到sku信息',
];
}
return response($this->res, $this->res['httpCode']);
}
public function update($id, Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'buyer_name' => 'sometimes|string',
'supplier_name' => 'sometimes|string',
"expire_time" => 'sometimes|string'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsSku = GoodsSku::query()->where('id', "=", $id)->get();
if (!empty($goodsSku)) {
//可以修改的記錄
$purchaseRecords = PurchaseRecords::query()->find($id);
$purchaseRecords->buyer_user_id = $allParams['buyer_user_id'] ?? 0;
$purchaseRecords->buyer_name = $allParams['buyer_name'] ?? '';
$purchaseRecords->supplier_name = $allParams['supplier_name'] ?? '';
$purchaseRecords->supplier_id = $allParams['supplier_id'] ?? 0;
if ($purchaseRecords->status == PurchaseStatusEnum::WAIT_CHECK) {
$purchaseRecords->cost = $allParams['cost'] ?? 0;
$purchaseRecords->num = $allParams['num'] ?? 0;
}
if (!empty($allParams['expire_time'])) {
$purchaseRecords->expire_time = Carbon::parse($allParams['expire_time'])->toDateTimeString();
}
if (!empty($allParams['date'])) {
$purchaseRecords->date = Carbon::parse($allParams['date'])->toDateString();
}
if (!empty($allParams['arrived_time'])) {
$purchaseRecords->arrived_time = Carbon::parse($allParams['arrived_time'])->toDateTimeString();
}
$purchaseRecords->save();
} else {
$this->res = [
'httpCode' => 400,
'message' => '查询不到sku信息',
'errorCode' => "ERP001",
'errorMessage' => '查询不到sku信息',
];
}
return response($this->res, $this->res['httpCode']);
}
/**
* 采购单后台批量存储
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|void
*/
public function purchaseBatchStore(Request $request)
{
$validator = Validator::make($request->all(), [
'purchaseOrders' => 'required|array',
'purchaseOrders.*.external_sku_id' => 'required|string',
'purchaseOrders.*.num' => 'required|integer',
'purchaseOrders.*.cost' => 'required',]);
//参数校验
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$purchaseOrders = $request->input('purchaseOrders') ?? [];
$externalSkuIds = collect($purchaseOrders)->pluck("external_sku_id")->toArray();
$goodsSku = GoodsSku::query()->whereIn('external_sku_id', $externalSkuIds)->get();
if (collect($externalSkuIds)->diff($goodsSku->pluck("external_sku_id"))->isNotEmpty()) {
$content = implode(',', collect($externalSkuIds)->diff($goodsSku->pluck("external_sku_id"))->toArray());
$content .= "以上sku编码数据库中不存在";
return response($content, 400);
}
$goodsSkuMap = $goodsSku->pluck(null, 'external_sku_id')->toArray();
$allUpdateIds = [];
$expireDay = DeveloperConfigService::getDefaultExpireDay();
$today = DateTimeUtils::getToday();
$batchNumber = GeneratorUtils::generateBatchNumber();
//开始保存数据
foreach ($purchaseOrders as $v) {
$goodsSkuItem = $goodsSkuMap[$v['external_sku_id']];
//保存記錄
$purchaseRecords = new PurchaseRecords();
$purchaseRecords->batch_number = $batchNumber;
$purchaseRecords->external_sku_id = $v['external_sku_id'];
$purchaseRecords->num = $v['num'];
$purchaseRecords->cost = $v['cost'];
$purchaseRecords->date = $v['date'] ?? $today;
$purchaseRecords->buyer_user_id = $v['buyer_user_id'] ?? 0;
$purchaseRecords->buyer_name = $v['buyer_name'] ?? '';
$purchaseRecords->supplier_name = $v['supplier_name'] ?? '';
$purchaseRecords->supplier_id = $v['supplier_id'] ?? 0;
$purchaseRecords->arrived_time = $v['arrived_time'] ?? Carbon::now()->toDateTimeString();
$purchaseRecords->expire_time = Carbon::now()->addDays($expireDay)->toDateTimeString();
$purchaseRecords->save();
if (PurchaseConfigEnum::IS_AUTO_CHECK) {
$v['user_id'] = $request->user()->id;
$updateIds = GoodSkuService::computeSkuStock($goodsSkuItem, $v, TargetTypeEnum::PURCHASE);
$allUpdateIds = array_merge($allUpdateIds, $updateIds);
}
}
if (PurchaseConfigEnum::IS_AUTO_CHECK) {
//如果是組合商品会触发重算逻辑
event(new BatchStockUpdateEvent(collect($allUpdateIds)->unique()->toArray()));
}
return response($this->res, $this->res['httpCode']);
}
/**
* 报损单后台批量存储
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|void
*/
public function purchaseImport(Request $request)
{
if (!$request->hasFile(ExcelKeyEnum::PURCHASE_KEY)) {
$this->res = [
'httpCode' => 404,
'errorCode' => 404404,
'errorMessage' => 'not found purchase file',
];
}
try {
$import = new PurchaseImport();
$path = $request->file(ExcelKeyEnum::PURCHASE_KEY);
Excel::import($import, $path);
$this->addLog(0, 'import', ExcelKeyEnum::PURCHASE_KEY);
} catch (ValidationException $exception) {
$this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
}
return response($this->res, $this->res['httpCode']);
}
/**
* 报损单后台批量存储
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|void
*/
public function purchaseBatchCheck(Request $request)
{
$params = $request->validate([
'purchaseOrders' => 'required|array',
'purchaseOrders.*.id' => 'required|integer',
'purchaseOrders.*.status' => 'required|in:1,2',
'purchaseOrders.*.num' => 'sometimes|integer',
]);
$allUpdateIds = [];
$now = Carbon::now()->toDateTimeString();
foreach ($params['purchaseOrders'] as $v) {
$purchaseRecordBuilder = PurchaseRecords::query()->with("goodsSku")->where("id", $v['id'])->first();
if (empty($purchaseRecordBuilder)) {
continue;
}
$purchaseRecords = $purchaseRecordBuilder->toArray();
if (empty($purchaseRecords['goods_sku'])) {
continue;
}
if ($purchaseRecords['status'] == $v['status']) {
continue;
}
if (!empty($v['num'])) {
$purchaseRecordBuilder->num = $v['num'];
$purchaseRecords['num'] = $v['num'];
}
DB::beginTransaction();
try {
if (PurchaseStatusEnum::CHECK_SUCCESS == $v['status']) {
$updateIds = GoodSkuService::computeSkuStock($purchaseRecords['goods_sku'], $purchaseRecords, TargetTypeEnum::PURCHASE);
$allUpdateIds = array_merge($allUpdateIds, $updateIds);
}
$purchaseRecordBuilder->status = $v['status'];
$purchaseRecordBuilder->check_time = $now;
$purchaseRecordBuilder->save();
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
\Illuminate\Support\Facades\Log::error("质检审核事务异常", ["error" => $exception->getMessage()]);
}
}
if (!empty($allUpdateIds)) {
event(new BatchStockUpdateEvent(collect($allUpdateIds)->unique()->toArray()));
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace App\Http\Controllers\Supplier;
use App\Http\Controllers\Controller;
use App\Models\Log;
use App\Models\Log as LogModel;
use App\Models\Suppliers;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Validator;
class SuppliersController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'supplier',
'action' => $request->getMethod(),
'target_type' => 'supplier',
]);
}
public function index(Request $request)
{
$suppliers = Suppliers::query()->paginate($request->get('per_page'));
return JsonResource::collection($suppliers);
}
public function store(Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'supplier_name' => 'required|string',
'company_name' => 'required|string',
'address' => 'sometimes|string',
'link_tel' => 'sometimes|string',
'payment_account' => 'sometimes|string',
'supply_type' => 'sometimes|string',
'agent_id' => 'sometimes|integer',
'agent_name' => 'sometimes|string',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
//保存数据
$suppliers = new Suppliers();
$suppliers->supplier_name = $request->supplier_name;
$suppliers->company_name = $request->company_name ?? '';
$suppliers->link_tel = $request->link_tel ?? '';
$suppliers->address = $request->address ?? '';
$suppliers->payment_account = $request->payment_account ?? '';
$suppliers->supply_type = $request->supply_type ?? '';
$suppliers->agent_id = $request->agent_id ?? 0;
$suppliers->agent_name = $request->agent_name ?? '';
$suppliers->save();
return response($this->res, $this->res['httpCode']);
}
public function update($id, Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'supplier_name' => 'required|string',
'company_name' => 'required|string',
'address' => 'sometimes|string',
'link_tel' => 'sometimes|string',
'payment_account' => 'sometimes|string',
'supply_type' => 'sometimes|string',
'agent_id' => 'sometimes|integer',
'agent_name' => 'sometimes|string',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
//保存数据
$suppliers = Suppliers::query()->find($id);
$suppliers->supplier_name = $request->supplier_name;
$suppliers->company_name = $request->company_name ?? '';
$suppliers->link_tel = $request->link_tel ?? '';
$suppliers->address = $request->address ?? '';
$suppliers->payment_account = $request->payment_account ?? '';
$suppliers->supply_type = $request->supply_type ?? '';
$suppliers->agent_id = $request->agent_id ?? 0;
$suppliers->agent_name = $request->agent_name ?? '';
$suppliers->save();
return response($this->res, $this->res['httpCode']);
}
public function destroy($id)
{
$goodsType = Suppliers::query()->find($id);
$goodsType->delete();
$this->addLog($id, 'status');
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -101,4 +101,9 @@ class UsersController extends Controller
return response($this->res, $this->res['httpCode']);
}
public function userRoles(Request $request)
{
return $request->user()->toArray();
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Enum;
class AfterSaleOrderStatusEnum
{
const WAIT = 0;
const REFUNDING = 1;
const REFUNDED_SUCCESS = 2;
const WAIT_HANDLE = 3;
const REFUND_REFUSE = 4;
const WAIT_RETURN_GOOD = 6;
const WAIT_CONFIRM_RETURN_GOOD = 7;
const USER_CANCEL = 8;
const CLOSE = 9;
const MAP = [
self::WAIT => "未发起售后",
self::REFUNDING => "退款中",
self::REFUNDED_SUCCESS => "退款成功",
self::WAIT_HANDLE => "待处理",
self::REFUND_REFUSE => "拒绝退款",
self::WAIT_RETURN_GOOD => "待退货",
self::WAIT_CONFIRM_RETURN_GOOD => "待团长确认退货",
self::USER_CANCEL => "用户取消",
self::CLOSE => "系统已关闭",
];
public static function getData($key)
{
return self::MAP[$key] ?? '';
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Enum;
class BusinessOrderShippingStatus
{
const UNSHIP = 0;
const SHIPPED = 1;//已发货
const SHIPPING = 2;//部分发货
const RECEIVED = 3;//已收货
const SHIPPED_MAP = [
self::SHIPPED, self::SHIPPING, self::RECEIVED,
];
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Http\Enum;
class CacheKeyEnum
{
const STOCK_RULE_PROPORTION = "stock_rule_proportion";
const DEFAULT_EXPIRE_DAY = "default_expire_day";
const SPU_STATISTIC_BY_DATE = "spu_statistic_by_date";
const SKU_ADMIN_ROLE_IDS = "sku_admin_role_ids";
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Enum;
class DevConfigKeyEnum
{
const STOCK_RULE_PROPORTION = "stock_rule_proportion";
const DEFAULT_STOCK_RULE_PROPORTION = 0.2;
const SKU_EXPIRE_DAY = "sku_expire_day";
const DEFAULT_EXPIRE_DAY = 3;
const SKU_ADMIN_ROLE_IDS = "sku_admin_role_ids";
const DEFAULT_SKU_ADMIN_ROLE_IDS = "1,2";
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Http\Enum;
class ExcelKeyEnum
{
const PURCHASE_KEY = "purchaseFile";
const LOSS_KEY = "lossFile";
const SALE_STOCK_FILE = "saleStockFile";
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Http\Enum\Goods;
class SkuStatusEnum
{
const DOWN = 0;
const UP = 1;
const NOTICE = 2;
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Enum\Message;
class MessageTypeEnum
{
const PRICE_EXCEPTION_NOTICE = "price_exception_notice";
const LOW_STOCK_NOTICE = "low_stock_notice";
const QUALITY_PERIOD_EXPIRE_NOTICE = "quality_period_expire_notice";
const DEFAULT_ROLE_IDS = [11];
const MESSAGE_ALL_TYPE = [
self::LOW_STOCK_NOTICE,
self::PRICE_EXCEPTION_NOTICE,
self::QUALITY_PERIOD_EXPIRE_NOTICE
];
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Enum\Purchase;
class PurchaseConfigEnum
{
const IS_AUTO_CHECK = 0;
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Enum\Purchase;
class PurchaseStatusEnum
{
const WAIT_CHECK = 0;
const CHECK_SUCCESS = 1;
const CHECK_FAILED = 2;
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Enum;
class StaticTypeEnum
{
const TODAY = 1;
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Http\Enum;
class TargetTypeEnum
{
const PURCHASE = "sku_stock_purchase";
const LOSS = "sku_stock_loss";
const INVENTORY = "sku_stock_inventory";
}

View File

@ -16,6 +16,9 @@ class CheckPermissions
*/
public function handle($request, Closure $next)
{
//目前permission仅菜单权限和接口权限耦合 这里先放开
$permissions = $request->user()->getPermissionsViaRoles()->toArray();
return $next($request);
// 获取当前路由名称
$currentRouteName = Route::currentRouteName();
// 引入当前守卫的权限文件

View File

@ -25,11 +25,7 @@ class GoodsRequest extends FormRequest
public function rules()
{
return [
'id' => ['sometimes', 'required', 'integer', 'exists:goods,id'],
'title' => ['required', 'string', 'max:191'],
'type_id' => ['required', 'integer', 'exists:goods_types,id'],
'brand_id' => ['integer', 'exists:goods_brands,id'],
'goods_code' => ['required', 'alpha_dash', 'max:32', Rule::unique('goods')->ignore(request('goods_id'))],
];
}

View File

@ -28,10 +28,10 @@ class GoodsSkuRequest extends FormRequest
'id' => ['sometimes', 'required', 'integer', 'exists:goods_skus,id'],
'goods_id' => ['sometimes', 'required', 'integer', 'exists:goods,id'],
'title' => ['sometimes', 'required', 'string', 'max:191'],
'sku_code' => ['sometimes', 'required', 'distinct', 'alpha_dash', 'max:32'],
'status' => ['sometimes', 'required', 'integer', Rule::in([0, 1, 2])],
'num' => ['sometimes', 'required', 'integer'],
'cost' => ['sometimes', 'required', 'numeric'],
'attribute' => ['sometimes', 'required', 'string'],
'reference_price' => [
'sometimes',
'numeric',

View File

@ -9,11 +9,21 @@ class BusinessOrderResource extends JsonResource
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
$data = parent::toArray($request);
$data['confirm_at'] = date('Y-m-d H:i:s', $data['confirm_at'] / 1000);
$map = ['未发货', '已发货', '部分发货', "已收货", '' => ''];
$data['shipping_status'] = $map[$data['shipping_status']] ?? '';
$map = ['帮忙团订单', '自卖团订单', '' => ''];
$data['is_supplier'] = $map[$data['is_supplier']] ?? '';
$map = ['未取消', '已取消', '' => ''];
$data['cancel_status'] = $map[$data['cancel_status']] ?? '';
$map = ['未发起售后 ', '退款中', '退款成功', '待处理', '拒绝退款', '待(顾客)退货', '待(团长)确认退货', '撤销', '撤销', '(系统)关闭' => ''];
$data['after_sales_status'] = $map[$data['after_sales_status']] ?? '';
return $data;
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace App\Http\Service;
use App\Http\Enum\Message\MessageTypeEnum;
use App\Models\DeveloperConfig;
use App\Models\WebsiteMessages;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class MessageService
{
public $roleIdsMapKeyByNoticeType = [];
public function __construct()
{
$DeveloperConfig = DeveloperConfig::query()->whereIn("key", MessageTypeEnum::MESSAGE_ALL_TYPE)
->pluck("value", "key")->toArray();
$this->roleIdsMapKeyByNoticeType = [
MessageTypeEnum::PRICE_EXCEPTION_NOTICE => !empty($DeveloperConfig[MessageTypeEnum::PRICE_EXCEPTION_NOTICE])
? explode(",", $DeveloperConfig[MessageTypeEnum::PRICE_EXCEPTION_NOTICE]) : MessageTypeEnum::DEFAULT_ROLE_IDS,
MessageTypeEnum::LOW_STOCK_NOTICE => !empty($DeveloperConfig[MessageTypeEnum::LOW_STOCK_NOTICE])
? explode(",", $DeveloperConfig[MessageTypeEnum::LOW_STOCK_NOTICE]) : MessageTypeEnum::DEFAULT_ROLE_IDS,
MessageTypeEnum::QUALITY_PERIOD_EXPIRE_NOTICE => !empty($DeveloperConfig[MessageTypeEnum::QUALITY_PERIOD_EXPIRE_NOTICE])
? explode(",", $DeveloperConfig[MessageTypeEnum::QUALITY_PERIOD_EXPIRE_NOTICE]) : MessageTypeEnum::DEFAULT_ROLE_IDS
];
}
//值为角色ids
public function createPriceExceptionMessage(string $businessOrderId, string $productName, string $skuName, string $goodsPrice, string $cost)
{
$roleIds = $this->roleIdsMapKeyByNoticeType[MessageTypeEnum::PRICE_EXCEPTION_NOTICE] ?? [];
if (empty($roleIds)) {
Log::error("消息配置异常", $this->roleIdsMapKeyByNoticeType);
}
$date = Carbon::now()->format('Ymd');
foreach ($roleIds as $v) {
$arr['title'] = "订单价格异常告警";
$arr['role_id'] = $v;
$arr['unique_key'] = $date . "-" . $businessOrderId . "-" . $v;
$arr['type'] = MessageTypeEnum::PRICE_EXCEPTION_NOTICE;
$arr['content'] = $date . "订单号:{$businessOrderId}-商品{$productName}
规格{$skuName}价格有异常,当前售价{$goodsPrice}/支,当前成本价{$cost}/";
$this->saveWebsiteMessages($arr);
}
}
/**
* 低库存告警
* @param $inventory
* @param $goodsSku
* @return void
*/
public function createLowerStockNoticeMessage($inventory, $goodsSku)
{
$roleIds = $this->roleIdsMapKeyByNoticeType[MessageTypeEnum::LOW_STOCK_NOTICE] ?? [];
if (empty($roleIds)) {
Log::error("消息配置异常", $this->roleIdsMapKeyByNoticeType);
}
$date = Carbon::now()->format('Ymd');
foreach ($roleIds as $v) {
$arr['title'] = "商品库存不足告警";
$arr['role_id'] = $v;
$arr['unique_key'] = $date . "-" . $goodsSku['id'] . "-" . $v;
$arr['type'] = MessageTypeEnum::LOW_STOCK_NOTICE;
$arr['content'] = $date . "规格{$goodsSku['name']}库存可能需要补货,当前实际库存{$goodsSku['stock']},上次库存盘点数{$inventory}";
$this->saveWebsiteMessages($arr);
}
}
/**
* 保質期告警
* @param $goodsSku
* @return void
*/
public function skuQualityPeriodNoticeMessage($goodsSku)
{
$roleIds = $this->roleIdsMapKeyByNoticeType[MessageTypeEnum::QUALITY_PERIOD_EXPIRE_NOTICE] ?? [];
if (empty($roleIds)) {
Log::error("消息配置异常", $this->roleIdsMapKeyByNoticeType);
}
$date = Carbon::now()->format('Ymd');
foreach ($roleIds as $v) {
$arr['title'] = "商品保质期告警";
$arr['role_id'] = $v;
$arr['unique_key'] = $date . "-" . $goodsSku['id'] . "-" . $v;//这个场景下实际是采购单的id
$arr['type'] = MessageTypeEnum::QUALITY_PERIOD_EXPIRE_NOTICE;
$arr['content'] = $date . "规格{$goodsSku['name']}编码{$goodsSku['external_sku_id']},即将过期,目前实际库存{$goodsSku['stock']},当时采购数量为{$goodsSku['num']},录入采购时间为{$goodsSku['created_at']}";
$this->saveWebsiteMessages($arr);
}
}
public function saveWebsiteMessages($arr)
{
$hasMessage = WebsiteMessages::query()->where("type", "=", $arr['type'])
->where("unique_key", "=", $arr['unique_key'])->first();
if (!empty($hasMessage)) {
//已经写入过了
return true;
}
$websiteMessages = new WebsiteMessages();
$websiteMessages->title = $arr['title'];
$websiteMessages->type = $arr['type'];
$websiteMessages->role_id = $arr['role_id'];
$websiteMessages->content = $arr['content'];
$websiteMessages->unique_key = $arr['unique_key'];
if (!empty($arr['uid'])) {
$websiteMessages->uid = $arr['uid'];
}
Log::info("站内消息保存", (array)$arr);
return $websiteMessages->save($arr);
}
}

View File

@ -6,6 +6,7 @@ use App\Models\CombinationGood;
use App\Models\GoodsSku;
use App\Utils\ArrayUtils;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray;
use Maatwebsite\Excel\Concerns\WithStartRow;
@ -50,18 +51,23 @@ class CombinationGoodsImport implements ToArray, SkipsEmptyRows, WithStartRow
$itemCodes = array_column($info['item'], 'item_code');
$skus = GoodsSku::query()
->whereIn('external_sku_id', $itemCodes)
->get(['id', 'external_sku_id', 'stock'])
->get(['id', 'external_sku_id', 'stock', "sale_stock"])
->toArray();
$skus = ArrayUtils::index($skus, 'external_sku_id');
$stock = [];
$saleStock = [];
foreach ($info['item'] as $item) {
$stock[] = (int)($skus[$item['item_code']]['stock'] / $item['item_num']);
$saleStock[] = (int)($skus[$item['item_code']]['sale_stock'] / $item['item_num']);
}
$stock = min($stock);
$status = $stock ? (5 < $stock ? 1 : 2) : 0;
$saleStock = min($saleStock);
$status = $saleStock ? (5 < $saleStock ? 1 : 2) : 0;
$sku = GoodsSku::query()->updateOrCreate(
['external_sku_id' => $info['external_sku_id'], 'is_combination' => 1],
['title' => $info['title'], 'goods_id' => 0, 'sku_code' => $info['external_sku_id'], 'stock' => $stock, 'status' => $status]
['title' => $info['title'],'name' => $info['title'], 'goods_id' => 0, 'sku_code' => $info['external_sku_id']
, 'stock' => $stock, "sale_stock" => $saleStock, 'status' => $status]
);
CombinationGood::query()
->where('goods_sku_id', $sku->id)

View File

@ -2,16 +2,12 @@
namespace App\Imports;
use App\Events\BatchStockUpdateEvent;
use App\Jobs\SyncCostToMiaoXuan;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\TodayPrice;
use App\Utils\DateTimeUtils;
use App\Services\GoodSku\GoodSkuService;
use Exception;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray;
use App\Utils\ArrayUtils;
class InventoryImport implements ToArray, SkipsEmptyRows
{
@ -20,85 +16,30 @@ class InventoryImport implements ToArray, SkipsEmptyRows
*/
public function array(array $collection)
{
$header = $collection[0];
unset($collection[0]);
$externalSkuId = [];
foreach ($collection as &$row) {
$row = array_map(static function ($v) {
return trim($v);
}, $row);
$externalSkuId[] = $row[0];
}
unset($row);
$updateIds = $todayPrice = [];
$day = DateTimeUtils::getToday();
$dateTime = date('Y-m-d H:i:s');
$hasGoodsSkus = GoodsSku::query()
->whereIn('external_sku_id', $externalSkuId)
->get(['id', 'status', 'external_sku_id'])
->toArray();
$hasGoodsSkus = ArrayUtils::index($hasGoodsSkus, 'external_sku_id');
foreach ($collection as $row) {
if (!isset($hasGoodsSkus[$row[0]])) {
continue;
if (!empty($collection)) {
unset($collection[0]);
$externalSkuIds = [];
$inventoryKeyByExternalSkuIdMap = [];
foreach ($collection as &$row) {
$row = array_map(static function ($v) {
return trim($v);
}, $row);
$inventoryKeyByExternalSkuIdMap[$row[0]] = $row[2];
$externalSkuIds[] = $row[0];
}
$goodsSku = $hasGoodsSkus[$row[0]];
if ('下架' === $goodsSku['status']) {
GoodsSku::query()->where('id', $goodsSku['id'])->update([
'stock' => $row[2] + $row[3],
'cost' => $row[4],
'status' => 1,
]);
} else {
GoodsSku::query()->where('id', $goodsSku['id'])->update([
'stock' => $row[2] + $row[3],
'cost' => $row[4],
]);
}
SyncCostToMiaoXuan::dispatch($row[0], $row[4]);
$updateIds[] = $goodsSku['id'];
DailyStockRecord::query()->where('sku_id', $goodsSku['id'])->where('day', $day)->update([
'arrived_today_num' => $row[3],
'inventory' => $row[2],
'inventory_time' => $dateTime
]);
$shopPrice = [];
foreach ($row as $i => $v) {
if ($i > 4) {
$shopPrice[$header[$i]] = $v;
unset($row);
//新版盘点excel字段 编码 商品名称 盘点数
$goodsSkus = GoodsSku::query()->with("combinationGoods")->whereIn('external_sku_id', $externalSkuIds)
->get()->toArray();
$goodsSkus = collect($goodsSkus)->map(function ($v) use ($inventoryKeyByExternalSkuIdMap) {
if (isset($inventoryKeyByExternalSkuIdMap[$v['external_sku_id']])) {
$v['inventory'] = $inventoryKeyByExternalSkuIdMap[$v['external_sku_id']];
return $v;
}
}
$todayPrice[] = [
'day' => $day,
'external_sku_id' => $goodsSku['external_sku_id'],
'shop_price' => json_encode($shopPrice, 256)
];
})->toArray();
Log::info("goodsSkus",$goodsSkus);
$goodSkuService = new GoodSkuService();
$goodSkuService->inventory($goodsSkus);
}
if ($todayPrice) {
TodayPrice::query()->delete();
$model = new TodayPrice();
$model->batchInsert($todayPrice);
}
sleep(2);
$onSkuIds = GoodsSku::query()
->where('is_combination', 0)
->where('status', '>', 0)
->pluck('id')
->toArray();
$downSkuIds = array_diff($onSkuIds, $updateIds);
if ($downSkuIds) {
$goodsSkus = GoodsSku::query()->whereIn('id', $downSkuIds)
->get(['id', 'yesterday_num', 'stock'])
->toArray();
foreach ($goodsSkus as $goodsSku) {
GoodsSku::query()->where('id', $goodsSku['id'])->update([
'yesterday_num' => $goodsSku['yesterday_num'] - $goodsSku['stock'],
'stock' => 0,
]);
}
}
sleep(2);
// 批量更新
event(new BatchStockUpdateEvent($onSkuIds));
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace App\Imports;
use App\Events\BatchStockUpdateEvent;
use App\Jobs\SyncCostToMiaoXuan;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\LossRecords;
use App\Models\PurchaseRecords;
use App\Models\User;
use App\Services\DeveloperConfig\DeveloperConfigService;
use App\Services\GoodSku\GoodSkuService;
use App\Utils\DateTimeUtils;
use App\Utils\GeneratorUtils;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray;
use App\Utils\ArrayUtils;
class LossImport implements ToArray, SkipsEmptyRows
{
/**
* @throws Exception
*/
public function array(array $collection)
{
if (!empty($collection)) {
unset($collection[0]);
$externalSkuIds = [];
$buyerNames = [];
foreach ($collection as &$row) {
$row = array_map(static function ($v) {
return trim($v);
}, $row);
$externalSkuIds[] = $row[0];
$buyerNames[] = $row[4];
}
unset($row);
$allUpdateIds = [];
$hasGoodsSkus = GoodsSku::query()
->whereIn('external_sku_id', $externalSkuIds)
->get(['id', 'status', 'external_sku_id', 'stock', "sale_stock", "cost", "is_combination"])
->toArray();
$hasGoodsSkus = ArrayUtils::index($hasGoodsSkus, 'external_sku_id');
$buyerUserIdKeyByNameMap = User::query()->whereIn("name", $buyerNames)->get()
->pluck("id", "name")->toArray();
$today = DateTimeUtils::getToday();
$batchNumber = GeneratorUtils::generateBatchNumber("import");
//excel字段排序 編碼 商品名稱 报损數量 成本价 采购人名称 报损现象 报损原因 报损日期
foreach ($collection as $row) {
if (!isset($hasGoodsSkus[$row[0]])) {
continue;
}
DB::beginTransaction();
try {
//执行库存操作
$goodsSkuItem = $hasGoodsSkus[$row[0]];
//保存記錄
$lossRecords = new LossRecords();
$lossRecords->batch_number = $batchNumber;
$lossRecords->external_sku_id = $row[0];
$lossRecords->num = $row[2];
$lossRecords->cost = $row[3];
$lossRecords->buyer_user_id = $buyerUserIdKeyByNameMap[$row[4]] ?? 0;
$lossRecords->buyer_name = $row[4] ?? '';
$lossRecords->phenomenon = $row[5] ?? '';
$lossRecords->reason = $row[6] ?? '';
$lossRecords->date = $today;
if (!empty($row[7])) {
$lossRecords->date = DateTimeUtils::excelUploadDateToString($row[7], $today);
}
$lossRecords->save();
$updateIds = GoodSkuService::computeSkuStock($goodsSkuItem, ["num" => 0 - $row[2], 'cost' => $row[3]]);
DB::commit();
$allUpdateIds = array_merge($allUpdateIds, $updateIds);
} catch (\Exception $exception) {
DB::rollBack();
}
}
Log::info("报损导入内容:", $collection);
// 批量更新
event(new BatchStockUpdateEvent(collect($allUpdateIds)->unique()->toArray()));
}
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace App\Imports;
use App\Events\BatchStockUpdateEvent;
use App\Http\Enum\Purchase\PurchaseConfigEnum;
use App\Http\Enum\TargetTypeEnum;
use App\Jobs\SyncCostToMiaoXuan;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\PurchaseRecords;
use App\Models\Suppliers;
use App\Models\User;
use App\Services\DeveloperConfig\DeveloperConfigService;
use App\Services\GoodSku\GoodSkuService;
use App\Utils\DateTimeUtils;
use App\Utils\GeneratorUtils;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray;
use App\Utils\ArrayUtils;
class PurchaseImport implements ToArray, SkipsEmptyRows
{
/**
* @throws Exception
*/
public function array(array $collection)
{
if (!empty($collection)) {
unset($collection[0]);
$externalSkuIds = [];
$supplierNames = [];
$buyerNames = [];
foreach ($collection as &$row) {
$row = array_map(static function ($v) {
return trim($v);
}, $row);
$externalSkuIds[] = $row[0];
$buyerNames[] = $row[4];
$supplierNames[] = $row[5];
}
unset($row);
$allUpdateIds = [];
$hasGoodsSkus = GoodsSku::query()
->whereIn('external_sku_id', $externalSkuIds)
->get(['id', 'status', 'external_sku_id', 'stock', "sale_stock", "cost", "is_combination"])
->toArray();
$hasGoodsSkus = ArrayUtils::index($hasGoodsSkus, 'external_sku_id');
//获取供货商
$supplierIdKeyByNameMap = Suppliers::query()->whereIn("supplier_name", $supplierNames)->get()
->pluck("id", "supplier_name")->toArray();
$buyerUserIdKeyByNameMap = User::query()->whereIn("name", $buyerNames)->get()
->pluck("id", "name")->toArray();
$expireDay = DeveloperConfigService::getDefaultExpireDay();
$today = DateTimeUtils::getToday();
$batch_number = GeneratorUtils::generateBatchNumber("import");
//excel字段排序 編碼 商品名稱 导购數量 成本价 采购人名称 供应商名称 采购日期
foreach ($collection as $row) {
if (!isset($hasGoodsSkus[$row[0]])) {
continue;
}
//执行库存操作
$goodsSkuItem = $hasGoodsSkus[$row[0]];
//保存記錄
$purchaseRecords = new PurchaseRecords();
$purchaseRecords->external_sku_id = $row[0];
$purchaseRecords->batch_number = $batch_number;
$purchaseRecords->num = $row[2];
$purchaseRecords->cost = $row[3];
$purchaseRecords->date = $today;
if (!empty($row[6])) {
$purchaseRecords->date = DateTimeUtils::excelUploadDateToString($row[6], $today);
}
if (!empty($row[7])) {
$purchaseRecords->arrived_time = DateTimeUtils::excelUploadDateToString($row[7], $today,"Y-m-d H:i:s");
}else{
$purchaseRecords->arrived_time = Carbon::now()->toDateTimeString();
}
$purchaseRecords->buyer_user_id = $buyerUserIdKeyByNameMap[$row[4]] ?? 0;
$purchaseRecords->buyer_name = $row[4] ?? '';
$purchaseRecords->supplier_name = $row[5] ?? '';
$purchaseRecords->supplier_id = $supplierIdKeyByNameMap[$row[5]] ?? 0;
$purchaseRecords->expire_time = Carbon::now()->addDays($expireDay)->toDateTimeString();
$purchaseRecords->save();
if (PurchaseConfigEnum::IS_AUTO_CHECK) {
$updateIds = GoodSkuService::computeSkuStock($goodsSkuItem, ["num" => $row[2], 'cost' => $row[3]], TargetTypeEnum::PURCHASE);
$allUpdateIds = array_merge($allUpdateIds, $updateIds);
}
}
Log::info("采购导入内容:", $collection);
if (PurchaseConfigEnum::IS_AUTO_CHECK) {
// 批量更新
event(new BatchStockUpdateEvent(collect($allUpdateIds)->unique()->toArray()));
}
}
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace App\Imports;
use App\Events\BatchStockUpdateEvent;
use App\Models\GoodsSku;
use App\Services\GoodSku\GoodSkuService;
use Exception;
use App\Models\Log as LogModel;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray;
class SaleStockImport implements ToArray, SkipsEmptyRows
{
/**
* @throws Exception
*/
public function array(array $collection)
{
if (!empty($collection)) {
unset($collection[0]);
$externalSkuIds = [];
$inventoryKeyByExternalSkuIdMap = [];
foreach ($collection as &$row) {
$row = array_map(static function ($v) {
return trim($v);
}, $row);
if ($row[2] < 0) {
throw new Exception("商品编码{$row[0]}在售库存数不能小于0");
}
$inventoryKeyByExternalSkuIdMap[$row[0]] = $row[2];
$externalSkuIds[] = $row[0];
}
unset($row);
//新版盘点excel字段 编码 在售库存值 商品名称
$updateIds = [];
DB::beginTransaction();
try {
$logs = [];
foreach ($externalSkuIds as $externalSkuId) {
// 成本
$goodsSku = GoodsSku::query()->where('external_sku_id', $externalSkuId)->first(['id', 'cost', 'sale_stock']);
Log::info("SKU", [$goodsSku]);
if (empty($goodsSku)) {
continue;
}
$costLog = [
'module' => 'goods',
'action' => "SaleStockImport",
'target_type' => 'goods_sku',
'target_id' => $goodsSku['id'],
'user_id' => Auth::id() ?? 999
];
$costLog['target_field'] = 'sale_stock';
$costLog['before_update'] = $goodsSku->sale_stock;
$goodsSku->sale_stock = $inventoryKeyByExternalSkuIdMap[$externalSkuId] ?? 0;
if (0 >= $goodsSku->sale_stock) {
$status = GoodsSku::$STATUS_DOWN;
} else {
$status = GoodsSku::$STATUS_ON_SALE;
}
$goodsSku->status = $status;
$goodsSku->save();
$costLog['after_update'] = $goodsSku->sale_stock;
$logs[] = $costLog;
$updateIds[] = $goodsSku['id'];
}
$log = new LogModel();
$log->batchInsert($logs);
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
throw new Exception($exception->getMessage());
}
if (!empty($updateIds)) {
event(new BatchStockUpdateEvent($updateIds));
}
}
}
}

View File

@ -8,6 +8,8 @@ use App\Models\Shop;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use App\Services\Business\BusinessFactory;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class BatchStockUpdateListener implements ShouldQueue
{
@ -35,18 +37,23 @@ class BatchStockUpdateListener implements ShouldQueue
if (empty($shops)) {
return;
}
foreach ($shops as $shop) {
foreach ($event->goodsSkus as $goodsSku) {
$num = $goodsSku->stock;
$businessGoodsSkus = BusinessGoodsSku::query()
->select(['goods_id', 'sku_id', 'external_sku_id'])
->where('shop_id', $shop->id)
->where('is_sync', 1)
->where('external_sku_id', $goodsSku->external_sku_id)
->get();
BusinessFactory::init()->make($shop['plat_id'])->setShopWithId($shop['id'])->batchIncrQuantity($businessGoodsSkus->toArray(), $num, false);
usleep(140);
try {
foreach ($shops as $shop) {
foreach ($event->goodsSkus as $goodsSku) {
//后续同步三方使用在售库存
$num = $goodsSku->sale_stock;
$businessGoodsSkus = BusinessGoodsSku::query()
->select(['goods_id', 'sku_id', 'external_sku_id'])
->where('shop_id', $shop->id)
->where('is_sync', 1)
->where('external_sku_id', $goodsSku->external_sku_id)
->get();
BusinessFactory::init()->make($shop['plat_id'])->setShopWithId($shop['id'])->batchIncrQuantity($businessGoodsSkus->toArray(), $num, false);
usleep(100);
}
}
} catch (\Exception $exception) {
Log::error("同步三方库存出现异常", [$exception->getMessage()]);
}
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace App\Listeners;
use App\Events\BatchStockUpdateEvent;
use App\Events\BusinessOrdersUpdate;
use App\Http\Enum\CacheKeyEnum;
use App\Http\Enum\DevConfigKeyEnum;
use App\Http\Enum\Goods\SkuStatusEnum;
use App\Http\Service\MessageService;
use App\Models\BusinessGoodsSku;
use App\Models\DailyStockRecord;
use App\Models\DeveloperConfig;
use App\Models\GoodsSku;
use App\Models\Shop;
use Carbon\Carbon;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use App\Services\Business\BusinessFactory;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class BusinessOrderUpdateListener implements ShouldQueue
{
use InteractsWithQueue;
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param BusinessOrdersUpdate $event
* @return void
*/
public function handle(BusinessOrdersUpdate $event)
{
try {
if (!empty($event->goodsSku)) {
//查询库存是否满足告警规则
//查找昨日统计的库存数据
$record = DailyStockRecord::query()->where('sku_id', $event->goodsSku->id)->where("inventory", '>', 0)->orderByDesc('day')->first();
Log::info("库存告警record", [$record]);
$inventory = $record->inventory ?? 0;
$expireTime = Carbon::now()->addMinutes(30)->toDateTimeString();
$proportion = Cache::remember(CacheKeyEnum::STOCK_RULE_PROPORTION, $expireTime, function () {
$developerConfig = DeveloperConfig::query()->where("key", "=", DevConfigKeyEnum::STOCK_RULE_PROPORTION)->first();
return $developerConfig['value'] ?? DevConfigKeyEnum::DEFAULT_STOCK_RULE_PROPORTION;
});
//库存比例小于最近盘点多少告警 一天也只是告警一次
if ($inventory > 10 && $inventory * $proportion > $event->goodsSku->stock) {
$messageService = new MessageService();
$messageService->createLowerStockNoticeMessage($inventory, $event->goodsSku->toArray());
//更新库存状态
GoodsSku::query()->where("id", "=", $event->goodsSku->id)->update([
"status" => SkuStatusEnum::NOTICE
]);
}
}
} catch (\Exception $exception) {
Log::error("库存告警发生异常", ["error" => $exception->getMessage()]);
}
}
}

View File

@ -2,6 +2,7 @@
namespace App\Listeners;
use App\Events\CancelLogisticEvent;
use App\Events\CreateLogisticEvent;
use App\Models\Shop;
use App\Models\Waybill;
@ -11,9 +12,6 @@ use Illuminate\Queue\InteractsWithQueue;
class CancelLogisticListener implements ShouldQueue
{
public $connection = 'redis';
public $queue = 'listeners';
/**
* Create the event listener.
@ -31,7 +29,7 @@ class CancelLogisticListener implements ShouldQueue
* @param CreateLogisticEvent $event
* @return void
*/
public function handle(CreateLogisticEvent $event)
public function handle(CancelLogisticEvent $event)
{
$waybillNo = Waybill::query()
->where('shop_id', $event->shopId)

View File

@ -9,6 +9,8 @@ use App\Utils\DateTimeUtils;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use App\Events\BatchStockUpdateEvent;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CombinationGoodsStockUpdateListener implements ShouldQueue
{
@ -53,20 +55,32 @@ class CombinationGoodsStockUpdateListener implements ShouldQueue
}
}
}
// 减子商品库存
$updateIds = [];
//拉取三分订单时可能出现组合订单的情况 需要同步扣减库存
if ($combinationGoodsIds) {
$combinationGoods = CombinationGood::query()
->with('goodsSku:id,stock')
->whereIn('goods_sku_id', $combinationGoodsIds)
->get();
$num = !empty($event->num) ? $event->num : -1;
foreach ($combinationGoods as $item) {
$goodsSku = GoodsSku::query()->find($item['item_id']);
$stock = $goodsSku->stock - $item['item_num'];
[$status, $stock] = $this->checkStatusAndStock($goodsSku, $stock);
$goodsSku->status = $status;
$goodsSku->stock = $stock;
$goodsSku->save();
$updateIds[] = $goodsSku->id;
DB::transaction(function () use ($item, &$updateIds, $num) {
$goodsSku = GoodsSku::query()->find($item['item_id']);
$stock = $goodsSku->stock + $item['item_num'] * $num;
//新增逻辑 在线库存同步扣减
$saleStock = max($goodsSku->sale_stock + $item['item_num'] * $num, 0);
[$status, $stock] = $this->checkStatusAndStock($goodsSku, $stock, $saleStock);
$goodsSku->status = $status;
$goodsSku->stock = $stock;
$goodsSku->sale_stock = $saleStock;
$goodsSku->save();
Log::info("sku 业务订单库存更:{$goodsSku->id},num:{$num}", [$stock, $saleStock]);
$mainGoodsSku = GoodsSku::query()->find($item['goods_sku_id']);
$mainGoodsSku->stock = min($mainGoodsSku->stock, (int)($stock / $item['item_num']));
$mainGoodsSku->sale_stock = min($mainGoodsSku->sale_stock, (int)($saleStock / $item['item_num']));
$mainGoodsSku->save();
$updateIds[] = $goodsSku->id;
});
}
}
// 计算主商品库存
@ -76,22 +90,30 @@ class CombinationGoodsStockUpdateListener implements ShouldQueue
->pluck('goods_sku_id');
foreach ($goodsSkuIds as $goodsSkuId) {
$combinationGoods = CombinationGood::query()
->with('goodsSkuItem:id,stock')
->with('goodsSkuItem:id,stock,sale_stock')
->where('goods_sku_id', $goodsSkuId)
->get();
$stock = [];
$saleStock = [];
foreach ($combinationGoods as $goods) {
$stock[] = (int)($goods['goodsSkuItem']['stock'] / $goods['item_num']);
$saleStock[] = (int)($goods['goodsSkuItem']['sale_stock'] / $goods['item_num']);
}
//库存和在线可售库存都是通过子商品维护的
$stock = min($stock);
$saleStock = min($saleStock);
$goodsSku = GoodsSku::query()->find($goodsSkuId);
[$status, $stock] = $this->checkStatusAndStock($goodsSku, $stock);
//新增在线可售逻辑判断 前置已经完成逻辑扣减或者增加
[$status, $stock] = $this->checkStatusAndStock($goodsSku, $stock, $saleStock);
$goodsSku->status = $status;
$goodsSku->stock = $stock;
$goodsSku->sale_stock = $saleStock;
$goodsSku->save();
$updateIds[] = $goodsSkuId;
}
}
if ($updateIds) {
$updateIds = array_unique($updateIds);
// 批量更新
@ -99,21 +121,14 @@ class CombinationGoodsStockUpdateListener implements ShouldQueue
}
}
private function checkStatusAndStock($goodsSku, $stock)
private function checkStatusAndStock($goodsSku, $stock, $saleStock)
{
if (0 >= $stock) {
//下线库存判断以在线可售库存为准
if (0 >= $saleStock) {
$status = GoodsSku::$STATUS_DOWN;
} else {
$status = GoodsSku::$STATUS_ON_SALE;
}
$arrivedTodayNum = DailyStockRecord::query()
->where('day', DateTimeUtils::getToday())
->where('sku_id', $goodsSku->id)
->value('arrived_today_num');
if (20 < $arrivedTodayNum + $goodsSku->yesterday_num && 4 > $stock) {
$status = GoodsSku::$STATUS_DOWN;
$stock = 0;
}
return [$status, $stock];
}

View File

@ -3,6 +3,7 @@
namespace App\Listeners;
use App\Events\CreateLogisticEvent;
use App\Models\BusinessOrder;
use App\Models\Shop;
use App\Services\Business\BusinessFactory;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -10,10 +11,6 @@ use Illuminate\Queue\InteractsWithQueue;
class CreateLogisticListener implements ShouldQueue
{
public $connection = 'redis';
public $queue = 'listeners';
/**
* Create the event listener.
*
@ -27,12 +24,12 @@ class CreateLogisticListener implements ShouldQueue
/**
* Handle the event.
*
* @param CreateLogisticEvent $event
* @param CreateLogisticEvent $event
* @return void
*/
public function handle(CreateLogisticEvent $event)
{
$shop = Shop::query()->findOrFail($event->shopId);
$shop = Shop::query()->findOrFail($event->shopId);;
$client = BusinessFactory::init()->make($shop['plat_id'])->setShop($shop);
$client->createLogistic($event->orderSn, $event->waybillNo);
}

View File

@ -36,7 +36,7 @@ class StockUpdateListener implements ShouldQueue
return;
}
foreach ($shops as $shop) {
$num = $event->goodsSku->stock;
$num = $event->goodsSku->sale_stock;
$businessGoodsSkus = BusinessGoodsSku::query()
->select(['goods_id', 'sku_id', 'external_sku_id'])
->where('shop_id', $shop->id)

View File

@ -60,7 +60,7 @@ class UpdateBusinessGoodsStock implements ShouldQueue
}
foreach ($shops as $shop) {
$num = $event->goodsSku->stock;
$num = $event->goodsSku->sale_stock;
$businessGoodsSkus = BusinessGoodsSku::query()
->where('shop_id', $shop->id)
->where('is_sync', 1)

View File

@ -0,0 +1,37 @@
<?php
namespace App\Models;
use App\Models\traits\Filter;
class BusinessAfterSaleOrder extends Model
{
use Filter;
protected $table = 'business_after_sale_orders';
protected $fillable = [
'after_sales_biz_sn',
];
public $fieldSearchable = [
'after_sales_status',
"shop_id",
"order_sn",
"after_sales_biz_sn"
];
public function getImageListAttribute($value)
{
return !empty($value) ? json_decode($value, true) : $value;
}
public function getSubExtensionsAttribute($value)
{
return !empty($value) ? json_decode($value, true) : $value;
}
public function shop()
{
return $this->belongsTo(Shop::class, 'shop_id', 'id');
}
}

View File

@ -23,6 +23,8 @@ class BusinessOrder extends Model
'print_status',
'ids',
'pno',
'order_sn',
'id',
];
protected $fillable = [
@ -62,36 +64,6 @@ class BusinessOrder extends Model
'order_sn',
];
public function getConfirmAtAttribute($value)
{
return date('Y-m-d H:i:s', $value / 1000);
}
public function getShippingStatusAttribute($value)
{
$map = ['未发货', '已发货', '部分发货', '' => ''];
return $map[$value];
}
public function getIsSupplierAttribute($value)
{
$map = ['帮忙团订单', '自卖团订单', '' => ''];
return $map[$value];
}
public function getCancelStatusAttribute($value)
{
$map = ['未取消', '已取消', '' => ''];
return $map[$value];
}
public function getAfterSalesStatusAttribute($value)
{
return empty($value) ? '未售后' : '有售后';
}
public function items()
{
@ -102,4 +74,9 @@ class BusinessOrder extends Model
{
return $this->belongsTo(Shop::class, 'shop_id', 'id');
}
public function waybill()
{
return $this->belongsTo(Waybill::class, 'id', 'order_id');
}
}

View File

@ -2,9 +2,23 @@
namespace App\Models;
use App\Models\traits\Filter;
class DailyStockRecord extends Model
{
use Filter;
protected $hidden = ['created_at', 'updated_at'];
protected $guarded = [];
public $fieldSearchable = [
'day',
];
public function goodsSku()
{
return $this->belongsTo(GoodsSku::class, 'sku_id', 'id');
}
}

View File

@ -15,6 +15,14 @@ class GoodsSku extends Model
'exclude_ids',
'external_sku_id',
'is_combination',
"create_time_start",
"create_time_end",
'min_stock',
'max_stock',
'min_sale_stock',
'max_sale_stock',
"neq_stock",
"neq_sale_stock",
];
protected $fillable = [
@ -33,10 +41,10 @@ class GoodsSku extends Model
'external_sku_id',
'is_combination',
'name',
'sale_stock',
'attribute'
];
protected $hidden = ['created_at'];
public static $STATUS_ON_SALE = 1;
public static $STATUS_DOWN = 0;
@ -63,10 +71,17 @@ class GoodsSku extends Model
public function getThumbUrlAttribute($value)
{
return json_decode($value, true);
}
public function getNameAttribute($value)
{
if(empty($value)){
return $this->attributes['title']??'';
}
return $value;
}
/**
* 此规格从属于一个商品
*/

View File

@ -2,12 +2,20 @@
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
class GoodsType extends Model
{
use SoftDeletes;
/**
* 数组中的属性会被隐藏。
*
* @var array
*/
protected $hidden = ['deleted_at'];
public function parentType()
{
return $this->hasOne(GoodsType::class, 'id', 'parent_id');
}
}

View File

@ -72,6 +72,9 @@ class Log extends Model
'kuaituantuan' => '快团团',
'miaoxuan' => '妙选',
'goods' => '商品',
"sku_stock_purchase" => "入库采购",
"sku_stock_loss" => "报损记录",
"sku_stock_inventory" => "盘点记录",
];
return $map[$value] ?? $value;
@ -89,6 +92,7 @@ class Log extends Model
'set' => '设置',
'cost' => '成本',
'stock' => '库存',
'sale_stock' => '在售库存',
'inventory' => '库存盘点',
'reserve' => '预留量 ',
'timingInventory' => '7点盘点',

View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use App\Models\traits\Filter;
use Illuminate\Database\Eloquent\Model;
class LossRecords extends Model
{
use Filter;
protected $table = 'loss_records';
protected $guarded = [];
public $fieldSearchable = [
'external_sku_id',
];
public function goodsSku()
{
return $this->belongsTo(GoodsSku::class, 'external_sku_id', 'external_sku_id');
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Models;
use App\Models\traits\Filter;
use Illuminate\Database\Eloquent\Model;
class PurchaseRecords extends Model
{
use Filter;
protected $table = 'purchase_records';
protected $guarded = [];
public $fieldSearchable = [
'external_sku_id',
'status'
];
public function goodsSku()
{
return $this->belongsTo(GoodsSku::class, 'external_sku_id', 'external_sku_id');
}
}

View File

@ -2,9 +2,16 @@
namespace App\Models;
use App\Models\traits\Filter;
use Illuminate\Database\Eloquent\Model;
class ShopSender extends Model
{
use Filter;
protected $guarded = [];
//查询字段
public $fieldSearchable = [
'shop_id',
];
}

11
app/Models/Suppliers.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Suppliers extends Model
{
//
protected $table = 'suppliers';
}

View File

@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
@ -11,6 +12,7 @@ class User extends Authenticatable
{
use Notifiable;
use HasRoles;
use SoftDeletes;
/**
* The attributes that are mass assignable.

View File

@ -10,4 +10,9 @@ class Waybill extends Model
public static $BUSINESS_EXPRESS_CODE = 247;
public static $AIR_FREIGHT_CODE = 266;
public static $STATUS_INIT = 0;
public static $STATUS_CREATE_WAYBILL_CODE = 1;
public static $STATUS_CREATE_WAYBILL_ENCRYPTED_DATA = 2;
public static $STATUS_PRINT_SUCCESS = 3;
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class WebsiteMessages extends Model
{
//
protected $table = 'website_messages';
}

View File

@ -3,10 +3,12 @@
namespace App\Providers;
use App\Events\BusinessOrdersUpdate;
use App\Events\CancelLogisticEvent;
use App\Events\StockUpdateEvent;
use App\Events\GroupSetEvent;
use App\Events\BatchStockUpdateEvent;
use App\Listeners\BatchStockUpdateListener;
use App\Listeners\BusinessOrderUpdateListener;
use App\Listeners\CreateLogisticListener;
use App\Listeners\GroupQueryListener;
use App\Listeners\StockUpdateListener;
@ -27,7 +29,7 @@ class EventServiceProvider extends ServiceProvider
protected $listen = [
BusinessOrdersUpdate::class => [
UpdateBusinessGoodsStock::class,
CombinationGoodsStockUpdateListener::class,
CombinationGoodsStockUpdateListener::class
],
BatchStockUpdateEvent::class => [
BatchStockUpdateListener::class,
@ -46,6 +48,9 @@ class EventServiceProvider extends ServiceProvider
CreateLogisticEvent::class => [
CreateLogisticListener::class
],
CancelLogisticEvent::class => [
CancelLogisticListener::class
],
];
/**

View File

@ -4,6 +4,7 @@ namespace App\Services\Business;
use App\Events\BusinessOrderCancelEvent;
use App\Events\BusinessOrdersUpdate;
use App\Events\CancelLogisticEvent;
use App\Models\BusinessGoodsSku;
use App\Models\BusinessOrder;
use App\Models\BusinessOrderItem;
@ -38,6 +39,10 @@ abstract class BusinessClient
abstract public function downloadOrdersAndSave($beginTime, $endTime, $downloadType = 'default', $page = 1);
abstract public function downloadAfterSaleOrdersAndSave($beginTime, $endTime, $page = 1);
abstract public function queryStatusAndSync($order);
public function saveOrders($orders)
{
$shopId = $this->getShop()->id;
@ -51,7 +56,7 @@ abstract class BusinessClient
$orderRecord->update($order);
}
if (!empty($order['cancel_status'])) {
event(new BusinessOrderCancelEvent($shopId, $order['order_sn']));
event(new CancelLogisticEvent($shopId, $order['order_sn']));
}
$goodsSkuNum = 0;
foreach ($order['sub_order_list'] as $item) {
@ -92,6 +97,7 @@ abstract class BusinessClient
}
$orderItem->update($item);
}
// 增量更新库存
if ($num && $item['external_sku_id']) {
event(new BusinessOrdersUpdate($orderItem, $num));
@ -149,14 +155,14 @@ abstract class BusinessClient
'headers' => ['Content-type' => 'application/x-www-form-urlencoded;charset=UTF-8'],
'form_params' => $params
];
$res = (new Client())->request($method, $url, $headers);
$res = (new Client(['verify'=>false]))->request($method, $url, $headers);
$size = $res->getBody()->getSize();
$res = json_decode($res->getBody()->getContents(), true);
$paramsJson = json_encode($params, 256);
if (strlen($paramsJson) > 1024) {
$paramsJson = '';
}
if (!in_array($params['type'], ['pdd.ktt.increment.order.query', 'pdd.ktt.order.list'], true)) {
if (!in_array($params['type'], ['pdd.ktt.increment.order.query', 'pdd.ktt.order.list',"pdd.ktt.after.sales.increment.list","pdd.ktt.order.get"], true)) {
$log = new Log();
$log->module = 'plat';
$log->action = $method;
@ -171,7 +177,7 @@ abstract class BusinessClient
}
$log->save();
}
if (in_array($params['type'], ['pdd.ktt.increment.order.query', 'pdd.ktt.order.list'], true)) {
if (in_array($params['type'], ['pdd.ktt.increment.order.query', 'pdd.ktt.order.list',"pdd.ktt.after.sales.increment.list"], true)) {
LogFile::info('快团团请求: ' . $paramsJson);
LogFile::info('快团团返回: ' . json_encode($res, 256));
}

View File

@ -4,6 +4,9 @@ namespace App\Services\Business\KuaiTuanTuan;
use App\Events\BusinessOrdersUpdate;
use App\Models\BusinessGoodsSku;
use App\Models\GoodsSku;
use App\Services\Business\BusinessFactory;
use Illuminate\Support\Facades\Log;
class Goods
{
@ -43,7 +46,14 @@ class Goods
if (empty($businessGoodSku->id)) {
$businessGoodSku->save();
if (!empty($businessGoodSku->external_sku_id)) {
event(new BusinessOrdersUpdate($businessGoodSku, 0));
$shop = $businessGoodSku->shop;
$sku = GoodsSku::query()
->where('external_sku_id', $businessGoodSku->external_sku_id)
->first();
Log::info("商品下载新增sku",[$businessGoodSku]);
if(!empty($sku)){
BusinessFactory::init()->make($shop['plat_id'])->setShopWithId($shop['id'])->incrQuantity($businessGoodSku, $sku->sale_stock, false);
}
}
} else {
$businessGoodSku->update($data);

View File

@ -2,13 +2,17 @@
namespace App\Services\Business\KuaiTuanTuan;
use App\Events\BusinessOrdersUpdate;
use App\Models\BusinessAfterSaleOrder;
use App\Models\BusinessGoodsSku;
use App\Models\BusinessOrderItem;
use App\Models\GoodsSku;
use App\Models\GroupGoods;
use App\Models\Shop;
use App\Models\ShopShip;
use App\Services\Business\BusinessClient;
use App\Models\Groups as GroupsModel;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class KuaiTuanTuan extends BusinessClient
@ -123,6 +127,104 @@ class KuaiTuanTuan extends BusinessClient
}
}
public function downloadAfterSaleOrdersAndSave($beginTime, $endTime, $page = 1)
{
[$type, $appendParams] = Order::downloadIncrementAfterSaleOrders($beginTime, $endTime, $page);
$responseName = 'ktt_after_sales_incermet_list_response';
$res = $this->doRequest($type, $appendParams);
if (!isset($res[$responseName])) {
return;
}
$this->saveAfterSaleOrdersServer($res[$responseName]['list']);
if ($res[$responseName]['has_next'] == false) {
return;
}
$pageNum = ceil($res[$responseName]['total_count'] / $appendParams['page_size']);
if ($pageNum > $page && 30 >= $page) {
$this->downloadAfterSaleOrdersAndSave($beginTime, $endTime, $page + 1);
}
}
public function saveAfterSaleOrdersServer($orders)
{
$shopId = $this->getShop()->id;
$now = Carbon::now()->toDateTimeString();
foreach ($orders as $k => $v) {
$model = BusinessAfterSaleOrder::query()->where("shop_id", "=", $shopId)
->where("after_sales_biz_sn", "=", $v['after_sales_biz_sn'])->first();
if (empty($model)) {
$model = new BusinessAfterSaleOrder();
} else {
$model->updated_at = $now;
}
$model->shop_id = $shopId;
$model->refund_amount = $v['apply_extension']['refund_amount'];
$model->refund_shipping_amount = $v['apply_extension']['refund_shipping_amount'];
$model->description = $v['apply_extension']['description'] ?? '';
$model->reason = $v['apply_extension']['reason'] ?? '';
$model->sub_extensions = isset($v['apply_extension']['sub_extensions']) ? json_encode($v['apply_extension']['sub_extensions']) : null;
$model->return_goods_extension = isset($v['return_goods_extension']) ? json_encode($v['return_goods_extension']) : null;
$model->image_list = isset($v['apply_extension']['image_list']) ? json_encode($v['apply_extension']['image_list']) : null;
$model->apply_type = $v['apply_type'];
$model->order_sn = $v['order_sn'];
$model->after_sales_biz_sn = $v['after_sales_biz_sn'];
$model->after_sales_status = $v['after_sales_status'];
$model->after_sale_created_at = !empty($v['created_at']) ? Carbon::createFromTimestamp($v['created_at'] / 1000)->toDateTimeString() : "";
$model->after_sale_updated_at = !empty($v['updated_at']) ? Carbon::createFromTimestamp($v['created_at'] / 1000)->toDateTimeString() : "";
$model->save();
}
}
public function queryStatusAndSync($order)
{
[$type, $appendParams] = Order::getOrderInfo($order->order_sn);
$responseName = 'ktt_order_get_response';
$res = $this->doRequest($type, $appendParams);
if (!isset($res[$responseName]['order_info'])) {
return;
}
$queryOrder = $res[$responseName]['order_info'];
$needUpdate = false;
if ($order->shipping_status != $queryOrder['shipping_status']) {
$order->shipping_status = $queryOrder['shipping_status'];
$needUpdate = true;
}
if ($order->cancel_status != $queryOrder['cancel_status']) {
$order->cancel_status = $queryOrder['cancel_status'];
$needUpdate = true;
}
if ($order->after_sales_status != $queryOrder['after_sales_status']) {
//售后状态更新
$order->after_sales_status = $queryOrder['after_sales_status'];
$needUpdate = true;
}
if ($needUpdate) {
$goodsSkuNum = 0;
foreach ($queryOrder['sub_order_list'] as $item) {
$orderItem = BusinessOrderItem::firstOrNew(['shop_id' => $order->shop_id, 'business_order_id' => $order->id, 'goods_id' => $item['goods_id'], 'sku_id' => $item['sku_id']], $item);
if ($item['external_sku_id']) {
$goodsSku = GoodsSku::query()
->with('combinationGoods')
->where('external_sku_id', $item['external_sku_id'])
->first('external_sku_id');
$combinationNum = $goodsSku ? ($goodsSku->combinationGoods->count() ?: 1) : 1;
$goodsSkuNum += $combinationNum;
} else {
$goodsSkuNum++;
}
if (!empty($orderItem->id)) {
$orderItem->update($item);
}
}
$order->goods_sku_num = $goodsSkuNum;
$order->save();
}
}
public function getOrderInfo($orderSn)
{
[$type, $appendParams] = Order::getOrderInfo($orderSn);
@ -163,6 +265,7 @@ class KuaiTuanTuan extends BusinessClient
$publicParams = array_merge($publicParams, $appendParams);
$publicParams['sign'] = $this->getSign($publicParams);
$res = $this->formDataPostRequest($url, $publicParams);
Log::info("快团团请求", ["param" => $publicParams, "res" => $res]);
if (isset($res['error_response'])) {
// "error_msg":"业务服务错误","sub_msg":"该店铺下不存在该商品","sub_code":"11","error_code":50001
// "error_msg":"业务服务错误","sub_msg":"该SKU在快团团中设置的库存为无限库存不支持修改库存","sub_code":"13","error_code":50001
@ -271,7 +374,6 @@ class KuaiTuanTuan extends BusinessClient
public function createLogistic($orderSn, $waybillNo)
{
[$type, $appendParams] = Order::createOrderLogistic($orderSn, $waybillNo);
return $this->doRequest($type, $appendParams);
}

View File

@ -92,5 +92,21 @@ class Order
return [$type, $appendParams];
}
/**
* 快团团增量查售后单订单
*/
public static function downloadIncrementAfterSaleOrders($beginTime, $endTime, $page = 1)
{
$type = 'pdd.ktt.after.sales.increment.list';
$appendParams = [
'start_updated_at' => $beginTime, // 更新起始时间
'end_updated_at' => $endTime, // 更新结束时间
'page_number' => $page, // 页码
'page_size' => 100, // 数量
];
return [$type, $appendParams];
}
}

View File

@ -3,6 +3,8 @@
namespace App\Services\Business\MiaoXuan;
use App\Models\BusinessGoodsSku;
use App\Models\BusinessOrderItem;
use App\Models\GoodsSku;
use App\Services\Business\BusinessClient;
class MiaoXuan extends BusinessClient
@ -33,6 +35,54 @@ class MiaoXuan extends BusinessClient
{
}
public function downloadAfterSaleOrdersAndSave($beginTime, $endTime, $page = 1)
{
}
public function queryStatusAndSync($order)
{
$appendParams = ["order_sn" => $order->order_sn, "type" => "erpQuery"];
$url = 'http://shop.chutang66.com/miaoxuan/queryOrder';
$queryOrder = $this->formDataPostRequest($url, $appendParams);
$needUpdate = false;
if ($order->shipping_status != $queryOrder['shipping_status']) {
$order->shipping_status = $queryOrder['shipping_status'];
$needUpdate = true;
}
if ($order->cancel_status != $queryOrder['cancel_status']) {
$order->cancel_status = $queryOrder['cancel_status'];
$needUpdate = true;
}
if ($order->after_sales_status!= $queryOrder['after_sales_status']) {
//售后状态更新
$order->after_sales_status = $queryOrder['after_sales_status'];
$needUpdate = true;
}
if ($needUpdate) {
$goodsSkuNum = 0;
foreach ($queryOrder['sub_order_list'] as $item) {
$orderItem = BusinessOrderItem::firstOrNew(['shop_id' => $order->shop_id, 'business_order_id' => $order->id, 'goods_id' => $item['goods_id'], 'sku_id' => $item['sku_id']], $item);
if ($item['external_sku_id']) {
$goodsSku = GoodsSku::query()
->with('combinationGoods')
->where('external_sku_id', $item['external_sku_id'])
->first('external_sku_id');
$combinationNum = $goodsSku ? ($goodsSku->combinationGoods->count() ?: 1) : 1;
$goodsSkuNum += $combinationNum;
} else {
$goodsSkuNum++;
}
if (!empty($orderItem->id)) {
$orderItem->update($item);
}
}
$order->goods_sku_num = $goodsSkuNum;
$order->save();
}
}
public function batchIncrQuantity($businessGoodsSkus, $num, $incremental)
{
$batchAppendParams = [];

View File

@ -0,0 +1,35 @@
<?php
namespace App\Services\DeveloperConfig;
use App\Http\Enum\CacheKeyEnum;
use App\Http\Enum\DevConfigKeyEnum;
use App\Models\DeveloperConfig;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
class DeveloperConfigService
{
public static function getDefaultExpireDay()
{
$expireTime = Carbon::now()->addHour();
return Cache::remember(CacheKeyEnum::DEFAULT_EXPIRE_DAY, $expireTime, function () {
$developerConfig = DeveloperConfig::query()->where("key",
"=", DevConfigKeyEnum::SKU_EXPIRE_DAY)->first();
return $developerConfig['value'] ?? DevConfigKeyEnum::DEFAULT_EXPIRE_DAY;
});
}
public static function getSkuAdminRoleIds()
{
$expireTime = Carbon::now()->addHour();
return Cache::remember(CacheKeyEnum::SKU_ADMIN_ROLE_IDS, $expireTime, function () {
$developerConfig = DeveloperConfig::query()->where("key",
"=", DevConfigKeyEnum::SKU_ADMIN_ROLE_IDS)->first();
$roleIdsStr = $developerConfig['value'] ?? DevConfigKeyEnum::DEFAULT_SKU_ADMIN_ROLE_IDS;
return explode(",", $roleIdsStr) ?? [];
});
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace App\Services\Good;
use App\Events\BatchStockUpdateEvent;
use App\Models\BusinessOrderItem;
use App\Models\CombinationGood;
use App\Models\DailyStockRecord;
use App\Models\Goods;
use App\Models\GoodsSku;
use App\Models\GoodsType;
use App\Utils\DateTimeUtils;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Predis\Command\Redis\SUBSCRIBE;
class GoodService
{
public function saveDefaultGoodsByGoodType($typeId)
{
$goodsType = GoodsType::query()->with("parentType")->where('id', "=", $typeId)
->first()->toArray();
$params['type_id'] = $typeId;
$parentName = "";
$params['goods_code'] = static::getChCode($goodsType['id']);
if (!empty($goodsType['parent_type']['id'])) {
$params['goods_code'] .= "Z". static::getChCode($goodsType['parent_type']['id']);
$parentName = $goodsType['parent_type']['name'] ?? '';
}
$saveData = $params;
$saveData['title'] = $goodsType['name'] . " " .$parentName;
return Goods::query()->firstOrCreate($params, $saveData);
}
public function getTypeFormatName($typeId)
{
$goodsType = GoodsType::query()->with("parentType")->where('id', "=", $typeId)
->first()->toArray();
$parentName = "";
if (!empty($goodsType['parent_type']['name'])) {
$parentName = $goodsType['parent_type']['name'] ?? '';
}
return $goodsType['name'] . " " .$parentName;
}
public function getSkuName($typeId,$sku)
{
$goodsType = GoodsType::query()->with("parentType")->where('id', "=", $typeId)
->first()->toArray();
$skuName = $goodsType['name'];
if(!empty($sku['attribute'])){
$skuName.=" ".$sku['attribute'];
}
if (!empty($goodsType['parent_type']['name'])) {
$skuName.= " ".$goodsType['parent_type']['name'] ?? '';
}
return $skuName." ".$sku['title'];
}
public function getRandomCode()
{
$time = time();
return substr($time, -7) . static::randomString(3);
}
public static function randomString($len = 32)
{
$chars = [
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
"S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
"3", "4", "5", "6", "7", "8", "9"
];
// 将数组打乱
shuffle($chars);
$charsLen = count($chars) - 1;
$output = "";
for ($i = 0; $i < $len; $i++) {
$output .= $chars[mt_rand(0, $charsLen)];
}
return $output;
}
/**
* 获取非z的字符串
* @param $intValue
* @return string
*/
public function getChCode($intValue)
{
$ch = range('A', 'Y');
$result = '';
while ($intValue) {
$result = $ch[$intValue % 25] . $result;
$intValue = intval($intValue / 25);
}
return $result;
}
}

View File

@ -0,0 +1,226 @@
<?php
namespace App\Services\GoodSku;
use App\Events\BatchStockUpdateEvent;
use App\Http\Enum\BusinessOrderShippingStatus;
use App\Http\Enum\TargetTypeEnum;
use App\Models\BusinessOrderItem;
use App\Models\CombinationGood;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\Log as LogModel;
use App\Utils\DateTimeUtils;
use App\Utils\GeneratorUtils;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use phpDocumentor\Reflection\Types\Collection;
class GoodSkuService
{
/**
* $skusWithCombinationGoods 除了携带组合商品的字段 还拼接了盘点的具体库存值inventory
* @param array $skusWithCombinationGoods
* @return void
*/
public function inventory(array $skusWithCombinationGoods)
{
$skusWithCombinationGoods = $this->handleSkusWithCombinationGoods($skusWithCombinationGoods);
//传进来的sku可能包含组合商品 所以这里需要事先计算好数据
$inventoryKeyBySkuIdMap = collect($skusWithCombinationGoods)->where('is_combination', "=", 0)
->pluck("real_stock", "id")->toArray();
Log::info("库存原始操作map", $inventoryKeyBySkuIdMap);
//计算组合商品
foreach ($skusWithCombinationGoods as $sku) {
if (!empty($sku['is_combination'])) {
foreach ($sku['combination_goods'] as $combinationGoods) {
if (!isset($inventoryKeyBySkuIdMap[$combinationGoods["item_id"]])) {
//没有盘点到的sku需要在原先的sku库存
$inventoryKeyBySkuIdMap[$combinationGoods["item_id"]] = 0;
}
$inventoryKeyBySkuIdMap[$combinationGoods["item_id"]] += $combinationGoods['item_num'] * $sku['real_stock'];
}
}
}
$today = DateTimeUtils::getToday();
$dateTime = date('Y-m-d H:i:s');
$updateIds = [];
Log::info("库存盘点前完整信息", $skusWithCombinationGoods);
Log::info("需要操作的库存数据", $inventoryKeyBySkuIdMap);
$batchNumber = GeneratorUtils::generateBatchNumber("import");
DB::beginTransaction();
try {
foreach ($skusWithCombinationGoods as $sku) {
// 更新每日数据
DailyStockRecord::query()->updateOrCreate([
'sku_id' => $sku['id'],
'day' => $today,
], [
"inventory" => $sku['inventory'],
"inventory_time" => $dateTime,
"batch_number" => $batchNumber
]);
}
$costLogs = [];
//更新库存并记录数据
foreach ($inventoryKeyBySkuIdMap as $skuId => $realStock) {
//库存修改 盘点是直接覆盖
$skuData = GoodsSku::query()->where('id', $skuId)->first();
if (!empty($skuData)) {
$costLogs[] = static::addStockLog($skuData
, TargetTypeEnum::INVENTORY, ['stock' => $realStock]);
$skuData->stock = $realStock;
$skuData->save();
$updateIds[] = $skuId;
}
}
$log = new LogModel();
$log->batchInsert($costLogs);
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
Log::error("库存盘点异常", ["error" => $exception->getMessage()]);
}
// 批量更新
event(new BatchStockUpdateEvent($updateIds));
}
/**
* 库存盘点 需要扣减未发货未取消的订单数据
* 目前只查询了7日内的数据
* @param array $skusWithCombinationGoods
* @return \Illuminate\Support\Collection
*/
public function handleSkusWithCombinationGoods(array $skusWithCombinationGoods)
{
//查询sku当前未发货的数量 需要扣减
$externalSkuIds = collect($skusWithCombinationGoods)->pluck("external_sku_id")->toArray();
$skuIds = collect($skusWithCombinationGoods)->pluck("id")->toArray();
//关联的组合数据
$combinationGoods = CombinationGood::query()->with("goodsSku:id,external_sku_id")->whereIn('item_id', $skuIds)->get()->toArray();
$externalSkuIds = collect($combinationGoods)->pluck("goods_sku.external_sku_id")->merge(collect($externalSkuIds))->toArray();
Log::info("需要查询的externalSkuIds",[$externalSkuIds]);
//默认只查15天内未发货的数据
$startTime = Carbon::now()->subDays(15)->toDateTimeString();
$unshippedDataCollect = BusinessOrderItem::query()
->with([
'shop:id,name',
'goodsSku:id,external_sku_id,is_combination',
'goodsSku.combinationGoods:id,goods_sku_id,item_id,item_num'
])
->leftJoin("business_orders as b", "business_order_id", "=", "b.id")
->select("external_sku_id", DB::raw("SUM(goods_number) - SUM(already_cancel_number) as number"))
->whereIn("external_sku_id", $externalSkuIds)
->where("b.shipping_status", "=", BusinessOrderShippingStatus::UNSHIP)
->where("b.after_sales_status", "!=", 2)
->where("business_order_items.created_at", ">=", $startTime)->where("business_order_items.cancel_status", "=", 0)
->groupBy('external_sku_id')->get()->toArray();
Log::info("盘点未发货数据",[$unshippedDataCollect]);
$ids = [];
//重组订单
foreach ($unshippedDataCollect as $businessOrderItem) {
if (is_null($businessOrderItem['goods_sku'])) {
continue;
}
$id = $businessOrderItem['goods_sku']['id'];
if ($businessOrderItem['goods_sku']['is_combination']) {
foreach ($businessOrderItem['goods_sku']['combination_goods'] ?? [] as $combinationGoods) {
$ids[$combinationGoods['item_id']] = ($ids[$combinationGoods['item_id']] ?? 0)
+ ((int)$businessOrderItem['number']) * $combinationGoods['item_num'];
}
}
if (isset($ids[$id])) {
$ids[$id] += (int)$businessOrderItem['number'];
} else {
$ids[$id] = (int)$businessOrderItem['number'];
}
}
return collect($skusWithCombinationGoods)->map(function ($v) use ($ids) {
$v['real_stock'] = $v['inventory'] ?? null;
if (!empty($ids[$v['id']]) && isset($v['inventory'])) {
$v['real_stock'] = $v['inventory'] - $ids[$v['id']];
}
return $v;
})->toArray();
}
public static function computeSkuStock(array $goodsSkuItem, array $changeData, $targetType = TargetTypeEnum::LOSS)
{
$updateIds = [];
$updateParams = [];
//添加系统日志
$costLogs = [];
// 成本
if (empty($goodsSkuItem['is_combination'])) {
$updateParam = [
'stock' => $goodsSkuItem['stock'] + $changeData['num'],
'sale_stock' => max($goodsSkuItem['sale_stock'] + $changeData['num'], 0),
];
if ($targetType == TargetTypeEnum::PURCHASE) {
$updateParam['cost'] = $changeData['cost'];
}
if ($updateParam['sale_stock'] <= 0) {
$updateParam['status'] = GoodsSku::$STATUS_DOWN;
}
GoodsSku::query()->where('external_sku_id', "=", $goodsSkuItem['external_sku_id'])
->update($updateParam);
$updateIds[] = $goodsSkuItem['id'];
$updateParams[] = $updateParam;
$costLogs[] = static::addStockLog($goodsSkuItem, $targetType, $updateParam);
} else {
$combinationGood = CombinationGood::query()->with('goodsSkuItem:id,stock,sale_stock')
->where('goods_sku_id', $goodsSkuItem['id'])->get();
foreach ($combinationGood as $item) {
$updateParam = [
'stock' => $item['goodsSkuItem']['stock'] + $changeData['num'] * $item['item_num'],
'sale_stock' => max($item['goodsSkuItem']['sale_stock'] + $changeData['num'] * $item['item_num'], 0),
];
if ($updateParam['sale_stock'] <= 0) {
$updateParam['status'] = GoodsSku::$STATUS_DOWN;
}
GoodsSku::query()->where('id', $item['goodsSkuItem']['id'])->update($updateParam);
$updateIds[] = $item['goodsSkuItem']['id'];
$updateParams[] = $updateParam;
$costLogs[] = static::addStockLog($item['goodsSkuItem'], $targetType, $updateParam);
}
}
$log = new LogModel();
$log->batchInsert($costLogs);
Log::info("本次请求更新参数", $updateParams);
return $updateIds;
}
public static function addStockLog($goodsSkuItem, $targetType = TargetTypeEnum::LOSS, $updateParam)
{
$userId = Auth::id();
$costLog = [
'module' => 'goods',
'action' => "POST",
'target_type' => $targetType,
'target_id' => $goodsSkuItem['id'] ?? 0,
'user_id' => $userId ?? 999,
"target_field" => "stock"
];
$costLog['before_update'] = json_encode($goodsSkuItem);
$costLog['after_update'] = json_encode($updateParam);
return $costLog;
}
}

View File

@ -0,0 +1,410 @@
<?php
namespace App\Services\Statistic;
use App\Events\BatchStockUpdateEvent;
use App\Http\Enum\CacheKeyEnum;
use App\Http\Enum\StaticTypeEnum;
use App\Models\BusinessOrderItem;
use App\Models\CombinationGood;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\LossRecords;
use App\Utils\DateTimeUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
class SaleDataService
{
/**
* sku维度的统计数据
*/
public static function saleStatistics(Request $request)
{
if (StaticTypeEnum::TODAY == $request->type) {
//实时统计 sku维度
return static::skuSaleStatisticsByToday($request);
} else {
//历史数据查询
return static::skuSaleStatisticsByHistory($request);
}
}
/**
* sku维度 今日实时统计
*/
public static function skuSaleStatisticsByToday(Request $request)
{
[$startTime, $endTime] = static::getTimeRange($request);
$build = BusinessOrderItem::query();
if (!empty($request->sku_id)) {
$externalSkuId = GoodsSku::query()->where("id", "=", $request->sku_id)
->pluck("external_sku_id")->first();
if (!empty($externalSkuId)) {
$build->where("external_sku_id", "=", $externalSkuId);
} else {
return [];
}
}
$orderItems = $build
->leftJoin("business_orders as b", "business_order_id", "=", "b.id")
->select("business_order_items.external_sku_id"
, DB::raw("sum(CASE WHEN b.shipping_status>0 THEN goods_number-already_cancel_number ELSE 0 END) as shipping_num")
, DB::raw("sum(CASE WHEN b.shipping_status=0 THEN goods_number-already_cancel_number ELSE 0 END) as unshipping_num")
, DB::raw("sum(goods_number-already_cancel_number) as goods_total")
, DB::raw("ROUND(sum(goods_amount) / 100,2) as goods_total_amount"))
->whereBetween("business_order_items.created_at", [$startTime, $endTime])
->where("external_sku_id", "!=", "")
->where("business_order_items.cancel_status", "=", 0)
->groupBy('external_sku_id')
->orderBy("goods_total", "DESC")
->paginate($request->get('per_page'));
if (!empty($orderItems->items())) {
$externalSkuIds = collect($orderItems->items())->pluck("external_sku_id")->toArray();
$goodsSkus = GoodsSku::query()->whereIn("external_sku_id", $externalSkuIds)
->select("id", "title", "name", "stock", "sale_stock", "status", "external_sku_id")->get()
->pluck(null, "id")->toArray();
return static::addSaleDataToGoodsSku($goodsSkus, $orderItems);
}
return $orderItems;
}
public static function skuSaleStatisticsByHistory(Request $request)
{
[$startTime, $endTime] = static::getTimeRange($request);
$build = DailyStockRecord::query();
if (!empty($request->sku_id)) {
$build->where("sku_id", "=", $request->sku_id);
}
$dailyRecord = $build->select("sku_id", DB::raw("sum(order_goods_num) as goods_total")
, DB::raw("sum(order_total_amount) as goods_total_amount"))
->whereBetween("created_at", [$startTime, $endTime])
->groupBy("sku_id")
->having(DB::raw("sum(order_goods_num)"), ">", 0)
->orderBy("goods_total", "DESC")
->paginate($request->get('per_page'));
$skuIds = collect($dailyRecord->items())->pluck('sku_id')->toArray();
$goodsSkusMapKeyBySkuId = GoodsSku::query()->select("id", "title", "name", "stock", "sale_stock"
, "status", "external_sku_id")->whereIn("id", $skuIds)
->get()->pluck(null, "id")->toArray();
$dailyRecord->getCollection()->map(function ($v) use ($goodsSkusMapKeyBySkuId) {
if (!empty($goodsSkusMapKeyBySkuId[$v['sku_id']])) {
foreach ($goodsSkusMapKeyBySkuId[$v['sku_id']] as $key => $val) {
$v->$key = $val;
}
} else {
//出现异常的skuid
$v->title = "未知商品";
$v->name = "未知商品";
$v->stock = 0;
$v->sale_stock = 0;
$v->status = "下架";
$v->id = 0;
}
});
return $dailyRecord;
}
public static function addSaleDataToGoodsSku($goodsSku, $orderItems)
{
$skuIds = collect($goodsSku)->pluck("id")->toArray();
//查询8天内-昨天的数据
$startTime = Carbon::now()->subDays(8)->startOfDay()->toDateTimeString();
$endTime = Carbon::yesterday()->endOfDay()->toDateTimeString();
$dailyRecord = DailyStockRecord::query()->whereIn("sku_id", $skuIds)
->whereBetween("day", [$startTime, $endTime])
->get()->toArray();
$dailyRecordGroupBySkuId = collect($dailyRecord)->groupBy("sku_id")->toArray();
Log::info("dailyRecordGroupBySkuId", $dailyRecordGroupBySkuId);
$combineGoodsSkus = collect($goodsSku)->map(function ($v) use ($dailyRecordGroupBySkuId, $orderItems) {
$v['yesterday_avg_num'] = round(collect($dailyRecordGroupBySkuId[$v['id']] ?? [])->sortByDesc("day")
->take(1)->avg("order_goods_num") ?? 0, 2);
$v['three_day_avg_num'] = round(collect($dailyRecordGroupBySkuId[$v['id']] ?? [])->sortByDesc("day")
->take(3)->avg("order_goods_num") ?? 0, 2);
$v['seven_day_avg_num'] = round(collect($dailyRecordGroupBySkuId[$v['id']] ?? [])->sortByDesc("day")
->take(7)->avg("order_goods_num") ?? 0, 2);
return $v;
})->pluck(null, "external_sku_id")->toArray();
Log::info("combineGoodsSkus", $combineGoodsSkus);
$orderItems->getCollection()->map(function ($v) use ($combineGoodsSkus) {
if (!empty($combineGoodsSkus[$v['external_sku_id']])) {
foreach ($combineGoodsSkus[$v['external_sku_id']] as $key => $val) {
$v->$key = $val;
}
} else {
//出现异常售卖的编码
$v->title = "未知商品";
$v->name = "未知商品";
$v->stock = 0;
$v->sale_stock = 0;
$v->status = "下架";
$v->yesterday_avg_num = 0;
$v->three_day_avg_num = 0;
$v->id = 0;
$v->seven_day_avg_num = 0;
}
});
return $orderItems;
}
public static function spuSaleStatistics(Request $request)
{
//spu 基本就是全统计了
if (StaticTypeEnum::TODAY == $request->type) {
return static::spuSaleStatisticsByToday($request);
} else {
//统计历史数据 这里走缓存
return static::spuSaleStatisticsByHistoryCache($request);
}
}
public static function getTimeRange(Request $request)
{
if (!empty($request->input("start_time")) && !empty($request->input("start_time"))) {
$startTime = Carbon::parse($request->input("start_time"))->toDateTimeString();
$endTime = Carbon::parse($request->input("end_time"))->toDateTimeString();
} else {
$startTime = Carbon::parse($request->input("start_day"))->toDateTimeString();
$endTime = Carbon::parse($request->input("end_day"))->endOfDay()->toDateTimeString();
}
return [$startTime, $endTime];
}
public static function spuSaleStatisticsByToday(Request $request)
{
[$startTime, $endTime] = static::getTimeRange($request);
//实时统计 sku维度
$orderItems = BusinessOrderItem::query()
->leftJoin("business_orders as b", "business_order_id", "=", "b.id")
->select("business_order_items.external_sku_id"
, DB::raw("sum(CASE WHEN b.shipping_status>0 THEN goods_number-already_cancel_number ELSE 0 END) as shipping_num")
, DB::raw("sum(CASE WHEN b.shipping_status=0 THEN goods_number-already_cancel_number ELSE 0 END) as unshipping_num")
, DB::raw("sum(goods_number-already_cancel_number) as goods_total")
, DB::raw("ROUND(sum(goods_amount) / 100,2) as goods_total_amount"))
->where('b.confirm_at', '>=', Carbon::parse($startTime)->getPreciseTimestamp(3))
->where('b.confirm_at', '<=', Carbon::parse($endTime)->getPreciseTimestamp(3))
->where("external_sku_id", "!=", "")
->where("business_order_items.cancel_status", "=", 0)
->groupBy('external_sku_id')->get()->toArray();
$externalSkuIds = collect($orderItems)->pluck("external_sku_id")->toArray();
$goodsSkus = GoodsSku::query()
->with([
'combinationGoods:id,goods_sku_id,item_id,item_num',
'combinationGoods.goodsSkuItem:id,name,goods_id,title,stock,sale_stock,external_sku_id,updated_at,yesterday_num,reference_price,status',
])
->whereIn("external_sku_id", $externalSkuIds)->get()->pluck(null, "external_sku_id")
->toArray();
$skus = [];
//组合商品
foreach ($orderItems as $orderItem) {
$sku = $goodsSkus[$orderItem['external_sku_id']] ?? [];
if (!empty($sku['is_combination'])) {
foreach ($sku['combination_goods'] as $combinationGood) {
$skuItem = $goodsSkus[$combinationGood['goods_sku_item']['external_sku_id']] ?? [];
$skuItem['shipping_num'] = $skuItem['shipping_num'] ?? 0 + $orderItem['shipping_num'] * $combinationGood['item_num'];
$skuItem['unshipping_num'] = $skuItem['unshipping_num'] ?? 0 + $orderItem['unshipping_num'] * $combinationGood['item_num'];
$skuItem['goods_total'] = $skuItem['goods_total'] ?? 0 + $orderItem['goods_total'] * $combinationGood['item_num'];
$skuItem['goods_total_amount'] = $skuItem['goods_total_amount'] ?? 0 + $orderItem['goods_total_amount'] * $combinationGood['item_num'];
$skus[$combinationGood['goods_sku_item']['external_sku_id']] = $skuItem;
}
} else {
$skuItem = $skus[$orderItem['external_sku_id']] ?? [];
$skuItem['shipping_num'] = $skuItem['shipping_num'] ?? 0 + $orderItem['shipping_num'];
$skuItem['unshipping_num'] = $skuItem['unshipping_num'] ?? 0 + $orderItem['unshipping_num'];
$skuItem['goods_total'] = $skuItem['goods_total'] ?? 0 + $orderItem['goods_total'];
$skuItem['goods_total_amount'] = $skuItem['goods_total_amount'] ?? 0 + $orderItem['goods_total_amount'];
$skus[$orderItem['external_sku_id']] = $skuItem;
}
}
$goodsSkuWithTypes = GoodsSku::query()
->Join("goods", "goods_id", "=", "goods.id")
->Join("goods_types", "goods.type_id", "=", "goods_types.id")
->whereIn("external_sku_id", array_keys($skus))
->select("goods_skus.id", "goods_types.id as type_id", "goods_types.name", "external_sku_id", "stock", "sale_stock")
->get()->toArray();
return collect($goodsSkuWithTypes)->map(function ($v) use ($skus) {
if (!empty($skus[$v['external_sku_id']])) {
return array_merge($v, $skus[$v['external_sku_id']]);
}
})->filter()->values()->groupBy('type_id')->map(function ($v, $key) {
return [
"type_id" => $key,
"type_name" => $v[0]['name'] ?? '',
"stock" => $v->sum("stock"),
"sale_stock" => $v->sum("sale_stock"),
"shipping_num" => $v->sum("shipping_num"),
"unshipping_num" => $v->sum("unshipping_num"),
"goods_total" => $v->sum("goods_total"),
"goods_total_amount" => $v->sum("goods_total_amount"),
];
})->sortByDesc('goods_total')->values()->toArray();
}
/**
* 通过历史报表统计spu数据-缓存
* @param Request $request
* @return mixed
*/
public
static function spuSaleStatisticsByHistoryCache(Request $request)
{
[$startTime, $endTime] = static::getTimeRange($request);
$cacheKey = CacheKeyEnum::SPU_STATISTIC_BY_DATE . $request->input("start_day") . "_" . $request->input("end_day");
$expireTime = Carbon::now()->addHour();
return Cache::remember($cacheKey, $expireTime, function () use ($startTime, $endTime) {
return static::spuSaleStatisticsByHistory($startTime, $endTime);
});
}
/**
* 通过历史报表统计spu数据
* @param $startTime
* @param $endTime
* @return array
*/
public static function spuSaleStatisticsByHistory($startTime, $endTime)
{
$dailyAllRecord = DailyStockRecord::query()
->select("sku_id", DB::raw("sum(order_goods_num) as goods_total")
, DB::raw("sum(order_total_amount) as goods_total_amount"))
->whereBetween("day", [$startTime, $endTime])
->groupBy("sku_id")
->having(DB::raw("sum(order_goods_num)"), ">", 0)
->get()->toArray();
$skuIds = collect($dailyAllRecord)->pluck('sku_id')->toArray();
$goodsSkus = GoodsSku::query()
->with([
'combinationGoods:id,goods_sku_id,item_id,item_num'
])
->whereIn("id", $skuIds)->get()->pluck(null, "id")
->toArray();
$skus = [];
//组合商品需要分散到sku维度进行统计
foreach ($dailyAllRecord as $skuRecord) {
$sku = $goodsSkus[$skuRecord['sku_id']] ?? [];
if (!empty($sku['is_combination'])) {
foreach ($sku['combination_goods'] as $combinationGood) {
$skuItem = $goodsSkus[$combinationGood['item_id']] ?? [];
$skuItem['goods_total'] = $skuItem['goods_total'] ?? 0 + $skuRecord['goods_total'] * $combinationGood['item_num'];
$skuItem['goods_total_amount'] = $skuItem['goods_total_amount'] ?? 0 + $skuRecord['goods_total_amount'] * $combinationGood['item_num'];
$skus[$combinationGood['item_id']] = $skuItem;
}
} else {
$skuItem = $skus[$skuRecord['sku_id']] ?? [];
$skuItem['goods_total'] = $skuItem['goods_total'] ?? 0 + $skuRecord['goods_total'];
$skuItem['goods_total_amount'] = $skuItem['goods_total_amount'] ?? 0 + $skuRecord['goods_total_amount'];
$skus[$skuRecord['sku_id']] = $skuItem;
}
}
$goodsSkuWithTypes = GoodsSku::query()
->Join("goods", "goods_id", "=", "goods.id")
->Join("goods_types", "goods.type_id", "=", "goods_types.id")
->select("goods_skus.id", "goods_types.id as type_id", "goods_types.name", "external_sku_id", "stock", "sale_stock")
->whereIn("goods_skus.id", array_keys($skus))
->get()->toArray();
return collect($goodsSkuWithTypes)->map(function ($v) use ($skus) {
if (!empty($skus[$v['id']])) {
return array_merge($v, $skus[$v['id']]);
}
})->filter()->values()->groupBy('type_id')->map(function ($v, $key) {
return [
"type_id" => $key,
"type_name" => $v[0]['name'] ?? '',
"stock" => $v->sum("stock"),
"sale_stock" => $v->sum("sale_stock"),
"goods_total" => $v->sum("goods_total"),
"goods_total_amount" => $v->sum("goods_total_amount"),
];
})->sortByDesc('goods_total')->values()->toArray();
}
/**
* gmv 统计
* @param Request $request
* @return array
*/
public static function gmvStatistics(Request $request)
{
if (StaticTypeEnum::TODAY == $request->type) {
[$startTime, $endTime] = static::getTimeRange($request);
$build = BusinessOrderItem::query();
if (!empty($request->sku_id)) {
$externalSkuId = GoodsSku::query()->where("id", "=", $request->sku_id)
->pluck("external_sku_id")->first();
if (!empty($externalSkuId)) {
$build->where("external_sku_id", "=", $externalSkuId);
} else {
return [];
}
}
$orderItems = $build->select("business_order_items.external_sku_id"
, "goods_number", "already_cancel_number", "goods_amount", "created_at")
->whereBetween("business_order_items.created_at", [$startTime, $endTime])
->where("business_order_items.cancel_status", "=", 0)
->get()->toArray();
$interval = $request->input("interval", 30);
return collect($orderItems)->groupBy(function ($v) use ($startTime, $interval) {
$diff = Carbon::parse($v['created_at'])->diffInMinutes(Carbon::parse($startTime));
return (int)floor($diff / $interval);
})->map(function ($v, $key) use ($startTime, $interval) {
return [
"sort_key" => $key,
"interval" => $interval,
"time_start" => Carbon::parse($startTime)->addMinutes($key * $interval)->toTimeString(),
"time_end" => Carbon::parse($startTime)->addMinutes(($key + 1) * $interval)->toTimeString(),
"goods_total" => $v->sum("goods_number") - $v->sum("already_cancel_number"),
"goods_total_amount" => round($v->sum("goods_amount") / 100, 2),
];
})->sort()->values()->toArray();
} else {
//gmv 统计历史数据
[$startTime, $endTime] = static::getTimeRange($request);
$build = DailyStockRecord::query();
if (!empty($request->sku_id)) {
$build->where("sku_id", "=", $request->sku_id);
}
return $build->select("day", DB::raw("sum(order_total_amount) as goods_total_amount")
, DB::raw("sum(order_goods_num) as goods_total"))
->whereBetween("day", [$startTime, $endTime])
->groupBy("day")->orderBy("day")
->get()->toArray();
}
}
/**
* 报损统计
* @param Request $request
* @return array
*/
public static function lossRecordStatistics(Request $request)
{
$startTime = Carbon::parse($request->input("start_time"))->toDateTimeString();
$endTime = Carbon::parse($request->input("end_time"))->toDateTimeString();
$build = LossRecords::query();
if (!empty($request->sku_id)) {
$externalSkuId = GoodsSku::query()->where("id", "=", $request->sku_id)
->pluck("external_sku_id")->first();
if (!empty($externalSkuId)) {
$build->where("external_sku_id", "=", $externalSkuId);
} else {
return [];
}
}
return $build->select("date", DB::raw("sum(num) as total_num")
, DB::raw("sum(num*cost) as total_loss_amount"))
->whereBetween("date", [$startTime, $endTime])
->groupBy("date")->orderBy("date")
->get()->toArray();
}
}

View File

@ -0,0 +1,244 @@
<?php
namespace App\Services\WayBill\JingDong;
use App\Enum\Pickup\ConsignType;
use App\Models\BusinessOrder;
use App\Models\OrderItem;
use App\Models\Pickup;
use App\Models\Waybill;
use App\Services\Pickup\Exceptions\KdNiaoException;
use App\Utils\Env\EnvUtils;
use Carbon\Carbon;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Log;
use Lop\LopOpensdkPhp\Filters\ErrorResponseFilter;
use Lop\LopOpensdkPhp\Filters\IsvFilter;
use Lop\LopOpensdkPhp\Options;
use Lop\LopOpensdkPhp\Support\DefaultClient;
use Lop\LopOpensdkPhp\Support\GenericRequest;
use Psr\Http\Message\ResponseInterface;
class JingDongService
{
public $baseUrl;
public $appKey;
public $appSecret;
public $accessToken;
public $customerCode;
public static $CANCEL_TYPE = [
1 => '预约信息有误',
2 => '快递员无法取件',
3 => '上门太慢',
4 => '运费太贵',
7 => '联系不上快递员',
8 => '快递员要求取消',
11 => '其他(如疫情管控,无法寄 件)',
9 => '预约信息有误',
];
public function __construct()
{
$this->baseUrl = "https://api.jdl.com";
$this->appKey = "5ee218f6619e438db8755ee560c3eaa7";
$this->appSecret = "7ffb176f75014ebbb37061f051024bf3";
$this->accessToken = "4e411a1d178147cdb58b5579a0df378d";//每年都会过期 https://oauth.jdl.com/oauth/authorize?client_id=YOUR_APP_KEY&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code
//生产环境域名https://oauth.jdl.com 预发环境域名https://uat-oauth.jdl.com
$this->domain = "FreshMedicineDelivery";//对接方案的编码 生鲜快递
$this->customerCode = "028K4188368";//月结编码
/*if (!EnvUtils::checkIsProduce()) {
//沙箱环境
$this->baseUrl = "https://test-api.jdl.com";
$this->appKey = "62d07644754843cc882fca7c01476c4f";
$this->appSecret = "0c2c8b6b7c10481ea639f6daa09ac02e";
$this->accessToken = "78c246c0ab564e67add6296a9eaf04a1";;//每年都会过期 https://oauth.jdl.com/oauth/authorize?client_id=YOUR_APP_KEY&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code
$this->customerCode = "27K1234912";//月结编码
}*/
}
public function request($body, $path)
{
// 生产环境: https://api.jdl.com
$client = new DefaultClient($this->baseUrl);
// 系统参数应用的app_ley和app_secret可从【控制台-应用管理-概览】中查看access_token是用户授权时获取的令牌用户授权相关说明请查看https://cloud.jdl.com/#/devSupport/53392
$isvFilter = new IsvFilter($this->appKey, $this->appSecret, $this->accessToken);
$errorResponseFilter = new ErrorResponseFilter();
$request = new GenericRequest();
$request->setDomain($this->domain);//对接方案的编码,应用订购对接方案后可在订阅记录查看
$request->setPath($path);//api的path可在API文档查看
$request->setMethod("POST");//只支持POST
// 序列化后的JSON字符串
$request->setBody(json_encode([$body]));
// 为请求添加ISV模式过滤器自动根据算法计算开放平台鉴权及签名信息
$request->addFilter($isvFilter);
// 为请求添加错误响应解析过滤器,如果不添加需要手动解析错误响应
$request->addFilter($errorResponseFilter);
$options = new Options();
$options->setAlgorithm(Options::MD5_SALT);
Log::info("京东请求body:{$path}", [$body]);
$response = $client->execute($request, $options);
$response = json_decode($response->getBody(), true);
Log::info("京东返回请求response", [$response]);
if (!isset($response['code']) || ($response['code'] != 0 && $response['code'] != 1 && $response['code'] != 1000)) {
throw new \Exception($response['message'] ?? "code异常");
}
return $response;
}
private function buildCargoes($extend)
{
return [
"quantity" => $extend["quantity"] ?? 1,
"goodsName" => "鲜花",
"volume" => ($extend['volume'] ?? 0) > 0 ? $extend['volume'] : 100,
"weight" => $extend['weight'] ?? 1,
"packageQty" => 1
];
}
public function buildContact($params)
{
return [
'senderName' => $params['name'] ?? '',
'senderPhone' => $params['mobile'] ?? '',
'senderAddress' => substr($params['province_name'] . $params['city_name'] . $params['area_name'] . $params['address_detail'], 0, 350),
];
}
public function createOrder(Waybill $pickup)
{
$path = "/freshmedicinedelivery/delivery/create/order/v1";
$pickupData = $pickup->toArray();
$body = [
"orderId" => $pickupData['order_sn'],
"senderContactRequest" => [
'senderName' => $pickupData['sender_name'] ?? '',
'senderPhone' => $pickupData['sender_mobile'] ?? '',
'senderAddress' => substr($pickupData['sender_province'] . $pickupData['sender_city']
. $pickupData['sender_district'] . $pickupData['sender_detail'], 0, 350)
],
"customerCode" => $this->customerCode,
"remark" => $pickup['note'] ?? '',
"salePlatform" => '0030001',//其他平台
"cargoesRequest" => $this->buildCargoes($pickup),
"receiverContactRequest" => [
'receiverName' => $pickupData['recipient_name'] ?? '',
'receiverPhone' => $pickupData['recipient_mobile'] ?? '',
'receiverAddress' => substr($pickupData['recipient_province'] . $pickupData['recipient_city'] . $pickupData['recipient_district'] . $pickupData['recipient_detail'], 0, 350)
],
"channelOrderId" => $pickupData['order_sn'],
"promiseTimeType" => 26,//22医药冷链 26冷链专送,29医药专送
"goodsType" => 7,//1:普通2:生鲜常温5:鲜活6:控温7:冷藏8:冷冻9:深冷21:医药冷藏23:医药控温24:医药常温25:医药冷冻27:医药深冷
//"pickUpStartTime" => Carbon::today()->startOfDay()->addDays(2)->addHours(19)->toDateTimeString(),
//"pickUpEndTime" => Carbon::today()->startOfDay()->addDays(2)->addHours(21)->toDateTimeString(),
];
$response = $this->request($body, $path);
return $response['data'];
}
public function cancelOrder(Waybill $pickup)
{
$path = "/freshmedicinedelivery/delivery/cancel/waybill/v1";
$pickupData = $pickup->toArray();
$body = [
"waybillNo" => $pickupData['waybill_code'],
"customerCode" => $this->customerCode,
"interceptReason" => "订单信息有误",
];
$response = $this->request($body, $path);
return $response['data'];
}
public function pullData(Waybill $pickup)
{
//云打印
$this->domain = "jdcloudprint";
$path = "/PullDataService/pullData";
$pickupData = $pickup->toArray();
$body = [
"cpCode" => "JD",
"wayBillInfos" => [[
'orderNo' => $pickupData['order_sn'],
'jdWayBillCode' => $pickupData['waybill_code'],
]],
"parameters" => ["ewCustomerCode" => $this->customerCode],
"objectId" => $pickupData['object_id'],
];
$response = $this->request($body, $path);
return $response['prePrintDatas'];
}
public function queryOrder(Waybill $pickup)
{
$path = "/ecap/v1/orders/details/query";
$pickupData = $pickup->toArray();
$body = [
"waybillCode" => $pickupData['ship_sn'] ?? '',
"orderCode" => $pickupData['out_order_sn'] ?? "",
"customerCode" => $this->customerCode,
"orderOrigin" => 2,
];
$response = $this->request($body, $path);
return $response['data'];
}
public function subscribe(Waybill $pickup)
{
$path = "/jd/tracking/subscribe";
$pickupData = $pickup->toArray();
$body = [
"referenceNumber" => $pickupData['ship_sn'] ?? '',
"referenceType" => 20000,
"customerCode" => $this->customerCode,
];
$this->domain = "Tracking_JD";//对接方案的编码
$response = $this->request($body, $path);
return $response['data'];
}
public function queryFee(Waybill $pickup)
{
$path = "/ecap/v1/orders/actualfee/query";
$pickupData = $pickup->toArray();
$body = [
"waybillCode" => $pickupData['ship_sn'] ?? '',
"orderCode" => $pickupData['out_order_sn'] ?? "",
"customerCode" => $this->customerCode,
"orderOrigin" => 2,
];
$response = $this->request($body, $path);
return $response['data'];
}
public function queryTrace(Waybill $pickup)
{
$path = "/freshmedicinedelivery/delivery/query/waybill/gis/v1";
$pickupData = $pickup->toArray();
$body = [
"waybillNo" => $pickupData['waybill_code'] ?? '',
"customerCode" => $this->customerCode,
];
$response = $this->request($body, $path);
return $response['data'];
}
}

View File

@ -0,0 +1,281 @@
<?php
namespace App\Services\WayBill\JingDong;
use App\Events\CreateLogisticEvent;
use App\Http\Enum\BusinessOrderShippingStatus;
use App\Models\BusinessOrder;
use App\Models\GoodsSku;
use App\Models\ShopSender;
use App\Models\ShopShip;
use App\Models\Waybill;
use App\Services\Business\KuaiTuanTuan\FaceSheet;
class WayBillService
{
public $orders;
public $objectId;
public $adminUser;
// 标准模板
public $templateUrl = 'https://template-content.jd.com/template-oss?tempCode=jdkd76x130';
// 时效类型
public $timedDeliveryCode;
/**
* 新版京东打印-下单
* @return array
*/
public function getWayBillContents()
{
// 已下单过的订单不再下单
$contents = [];
$this->adminUser = auth('api')->user();
$jingDongService = new JingDongService();
foreach ($this->orders as $shopId => $order) {
// 订单取消的情况暂不处理
$shopSend = $this->getShopSend($shopId);
foreach ($order as $item) {
[$sender, $orderInfo, $senderConfig] = $this->prepareRequest($item, $shopSend);
$waybill = $this->saveWayBill($item, $shopSend, $senderConfig);
if (empty($waybill->waybill_code)) {
$resp = $jingDongService->createOrder($waybill);
if (isset($resp['waybillNo'])) {
$waybill->status = Waybill::$STATUS_CREATE_WAYBILL_CODE;
$waybill->waybill_code = $resp['waybillNo'];
$waybill->save();
//物流发货
event(new CreateLogisticEvent($waybill->shop_id, $waybill->order_sn, $waybill->waybill_code));
}
}
//返回面单待打印数据
if (empty($waybill->encryptedData)) {
// 延时0.5秒 防止订单查询不到
usleep(0.5 * 1000000);
$resp = $jingDongService->pullData($waybill);
if (!empty($resp[0]['perPrintData'])) {
$waybill->status = Waybill::$STATUS_CREATE_WAYBILL_ENCRYPTED_DATA;
$waybill->templateUrl = $this->templateUrl;
$waybill->encryptedData = $resp[0]['perPrintData'];
$waybill->save();
}
}
$contents[$item['id']] = $waybill;
}
}
return $contents;
}
public function getDocumentsAndOrderIds($contents)
{
// 打印单,根据商品排序
$items = [];
foreach ($contents as $docId => $content) {
foreach ($content['items'] as $item) {
if ($item['is_single']) {
$items[$item['external_sku_id']][] = $docId;
}
}
}
ksort($items);
$documents = $orderIds = $hasIds = [];
foreach ($items as $docIds) {
$docIds = array_unique($docIds);
$docIds = array_diff($docIds, $hasIds);
$hasIds = array_merge($hasIds, $docIds);
foreach ($docIds as $docId) {
$orderIds[] = $contents[$docId]['order_id'];
$documents[] = $contents[$docId];
}
}
return [$documents, $orderIds];
}
private function saveWayBill($order, $shopShip, $senderConfig)
{
$waybill = Waybill::query()->firstOrNew(
['order_sn' => $order['order_sn']]
);
$waybill->shop_id = $order['shop_id'];
$waybill->object_id = $this->objectId;
$waybill->sender_country = $senderConfig['country'];
$waybill->sender_province = $senderConfig['province'];
$waybill->sender_city = $senderConfig['city'];
$waybill->sender_district = $senderConfig['district'];
$waybill->sender_detail = $senderConfig['detail'];
$waybill->sender_name = $senderConfig['name'];
$waybill->sender_mobile = (int)$senderConfig['mobile'];
$waybill->recipient_province = $order['recipient_province'];
$waybill->recipient_city = $order['recipient_city'];
$waybill->recipient_district = $order['recipient_district'];
$waybill->recipient_detail = $order['recipient_detail'];
$waybill->recipient_name = $order['recipient_name'];
$waybill->recipient_mobile = $order['recipient_mobile'];
$waybill->user_id = $this->adminUser->id ?? 0;
$waybill->wp_code = $senderConfig['wp_code'];
$waybill->order_sn = $order['order_sn'];
$waybill->order_id = $order['id'];
$waybill->participate_no = $order['participate_no'];
$waybill->note = $order['note'];
$waybill->items = json_encode($order['items'], 256);
$waybill->save();
return $waybill;
}
private function getTimedDelivery($order)
{
$this->timedDeliveryCode = Waybill::$BUSINESS_EXPRESS_CODE;
$address = [
'辽宁省' => [],
'吉林省' => [],
'黑龙江省' => [],
'甘肃省' => [],
'海南省' => [],
'宁夏回族自治区' => [
'银川市', '石嘴山市', '吴忠市', '固原市'
],
'青海省' => [
'西宁市', '海东市', '海北藏族自治州', '黄南藏族自治州', '海南藏族自治州', '玉树藏族自治州'
],
];
if (isset($address[$order['recipient_province']])) {
if (empty($address[$order['recipient_province']])) {
$this->timedDeliveryCode = Waybill::$AIR_FREIGHT_CODE;
}
if ($address[$order['recipient_province']] && in_array($order['recipient_city'], $address[$order['recipient_province']], true)) {
$this->timedDeliveryCode = Waybill::$AIR_FREIGHT_CODE;
}
}
}
private function prepareRequest($order, $shopSend)
{
$this->setObjectId();
$items = [];
foreach ($order['items'] as $item) {
$items[] = [
'name' => $item['name'],
'count' => $item['count'],
];
}
if (empty($shopSend)) {
abort(404, '发货人信息未匹配');
}
$senderConfig = $shopSend;
$sender = [
'address' => [
'city' => $senderConfig['city'],
'country' => $senderConfig['country'],
'detail' => $senderConfig['detail'],
'district' => $senderConfig['district'],
'province' => $senderConfig['province'],
],
'name' => $senderConfig['name'],
'mobile' => $senderConfig['mobile'],
];
$orderInfo = [
'object_id' => $this->objectId,
'order_info' => [
'order_channels_type' => 'PDD',
'trade_order_list' => [$order['order_sn']],
],
'package_info' => [
'items' => $items,
],
'recipient' => [
'address' => [
'city' => $order['recipient_city'],
'detail' => $order['recipient_detail'],
'district' => $order['recipient_district'],
'province' => $order['recipient_province'],
],
'name' => $order['recipient_name'],
'mobile' => $order['recipient_mobile'],
],
'template_url' => $this->templateUrl,
];
return [$sender, $orderInfo, $senderConfig];
}
public function setObjectId()
{
$this->objectId = date('YmdHis') . str_pad(mt_rand(1, 9999999), 7, '0', STR_PAD_LEFT);
return $this;
}
public function setOrders($orders)
{
$orders = $orders->toArray();
// 订单拆分组合
foreach ($orders as $order) {
if ($order['shipping_status'] != BusinessOrderShippingStatus::UNSHIP) {
continue;
}
if ($order['cancel_status'] == 1) {
continue;
}
$info = [
'id' => $order['id'],
'shop_id' => $order['shop_id'],
'order_sn' => $order['order_sn'],
'recipient_province' => $order['receiver_address_province'],
'recipient_city' => $order['receiver_address_city'],
'recipient_district' => $order['receiver_address_district'],
'recipient_detail' => $order['receiver_address_detail'],
'recipient_name' => $order['receiver_name'],
'recipient_mobile' => $order['receiver_mobile'],
'participate_no' => $order['is_supplier'] ? $order['participate_no'] : $order['supply_participate_no'],
'note' => $order['business_note'] ? $order['business_note'] . ',' . $order['buyer_memo'] : $order['buyer_memo'],
'items' => []
];
$note = "";
foreach ($order['items'] as $item) {
$count = $item['goods_number'] - $item['already_cancel_number'];
$info['items'][] = [
'is_single' => true,
'should_print' => true,
'name' => $item['goods_name'],
'count' => $count,
'external_sku_id' => $item['external_sku_id'],
];
$note .= $item['goods_name'] . " " . $count . "件,";
}
$note .= "[备注:" . $info['note'] . "]";
$info['note'] = $note;
$this->orders[$order['shop_id']][] = $info;
}
return $this;
}
private function getShopSend($shopId)
{
return ShopSender::query()
->where('status', 1)->orderBy('sort')
->first();
}
private function getShopShip($shopId)
{
return ShopShip::query()
->select(['id', 'shop_id', 'access_token', 'owner_id'])
->where('shop_id', $shopId)
->with([
'senders' => function ($query) {
$query->where('status', 1)->orderBy('sort');
}
])
->first();
}
}

View File

@ -2,6 +2,8 @@
namespace App\Utils;
use Illuminate\Support\Facades\Log;
class DateTimeUtils
{
/**
@ -42,4 +44,28 @@ class DateTimeUtils
return (int)ceil($time);
}
public static function validateDate($date, $format = 'Y-m-d')
{
$d = \DateTime::createFromFormat($format, $date);
return $d && $d->format($format) === $date;
}
public static function excelUploadDateToString($excelData, $defaultTime, $format = "Y-m-d")
{
try {
$time = ($excelData - 25569) * 24 * 3600;
if ($format == "Y-m-d H:i:s") {
$time = $time - 8 * 3600;
}
return date($format, $time);
} catch (\Exception $exception) {
Log::error("时间转化出错", [$exception->getMessage()]);
}
return $defaultTime;
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Utils;
use App\Utils\NumberUtils;
use Yeepay\Yop\Sdk\Utils\Http\HttpUtils;
class GeneratorUtils
{
public static function generateBatchNumber($type = "add")
{
$code = "A";//批量添加
if ($type != 1) {
$code = "I";//导入
}
return $code.date("YmdHis") . rand(1000, 10000);
}
public static function generateCombinationGoodNumber($goods)
{
$code = "";
foreach ($goods as $v){
$code.=static::getChCode($v['item_id'])."Z".static::getChCode($v['item_num'])."Z";
}
rtrim($code,"Z");
return $code;
}
/**
* 获取非z的字符串
* @param $intValue
* @return string
*/
public static function getChCode($intValue)
{
$ch = range('A', 'Y');
$result = '';
while ($intValue) {
$result = $ch[$intValue % 25] . $result;
$intValue = intval($intValue / 25);
}
return $result;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Utils;
use App\Exceptions\ServiceException;
use Illuminate\Support\Facades\Redis;
class RedisLockUtils
{
private static $prefix = 'lock';
public static function getKey($keyType, $extend = '')
{
return collect([
self::$prefix,
$keyType,
$extend
])->filter()->implode(':');
}
public static function getLock($lockKey, $expireSeconds = 1, $throwException = false)
{
$lock = Redis::setnx($lockKey, 1);
if (!$lock) {
if ($throwException) {
throw new ServiceException('操作频繁');
}
return false;
}
Redis::expire($lockKey, $expireSeconds);
return true;
}
public static function deleteLock($lockKey)
{
return Redis::del($lockKey);
}
}

View File

@ -17,6 +17,7 @@
"laravel/framework": "^6.20.26",
"laravel/tinker": "^2.5",
"maatwebsite/excel": "^3.1",
"predis/predis": "^2.2",
"spatie/laravel-permission": "*"
},
"require-dev": {
@ -38,7 +39,8 @@
},
"autoload": {
"psr-4": {
"App\\": "app/"
"App\\": "app/",
"Lop\\LopOpensdkPhp\\" : "extend/lop-opensdk-php/src/"
},
"classmap": [
"database/seeds",

69
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "037b06c1b26399725a1d9c0687402942",
"content-hash": "964631bbee47f895975146a783331c50",
"packages": [
{
"name": "aliyuncs/oss-sdk-php",
@ -2587,6 +2587,73 @@
],
"time": "2022-07-30T15:51:26+00:00"
},
{
"name": "predis/predis",
"version": "v2.2.2",
"source": {
"type": "git",
"url": "https://github.com/predis/predis.git",
"reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/predis/predis/zipball/b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1",
"reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.3",
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^8.0 || ~9.4.4"
},
"suggest": {
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
},
"type": "library",
"autoload": {
"psr-4": {
"Predis\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Till Krüss",
"homepage": "https://till.im",
"role": "Maintainer"
}
],
"description": "A flexible and feature-complete Redis client for PHP.",
"homepage": "http://github.com/predis/predis",
"keywords": [
"nosql",
"predis",
"redis"
],
"support": {
"issues": "https://github.com/predis/predis/issues",
"source": "https://github.com/predis/predis/tree/v2.2.2"
},
"funding": [
{
"url": "https://github.com/sponsors/tillkruss",
"type": "github"
}
],
"time": "2023-09-13T16:42:03+00:00"
},
{
"name": "psr/container",
"version": "1.1.1",

View File

@ -52,6 +52,7 @@ return [
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
"permission" => 0777
],
'slack' => [

View File

@ -61,7 +61,8 @@ class CreateBusinessOrdersTable extends Migration
// 索引
$table->unique(['shop_id', 'order_sn']);
$table->index(['shop_id', 'participate_no']);
$table->index(['shop_id', 'confirm_at', 'after_sales_status', 'cancel_status', 'is_supplier']);
$table->index(['shop_id', 'confirm_at', 'after_sales_status', 'cancel_status', 'is_supplier']
,"shop_id_confirm_at_composite_index");
});
}

View File

@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePurchaseRecordsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('purchase_records', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('external_sku_id',60)->comment('唯一sku标识');
$table->string('batch_number',60)->nullable()->comment('批次号');
$table->date('date')->nullable()->comment('日期');;
$table->Integer('num')->default(0)->comment('采购数量');
$table->unsignedDecimal('cost')->default(0)->comment('成本');
$table->Integer('buyer_user_id')->default(0)->comment('购买人用户id');;
$table->string('buyer_name')->nullable()->comment('采购人');
$table->Integer('check_status')->default(0)->comment('盘点完近似状态 0未完成1已售卖完成');
$table->string('expire_time')->nullable()->comment('保质期时间');
$table->string('supplier_name')->nullable()->comment('供应商名称');
$table->Integer('supplier_id')->nullable()->comment('供应商id');
// 索引
$table->index('external_sku_id');
$table->index('created_at');
$table->index('batch_number');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('purchase_records');
}
}

View File

@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateLossRecordsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('loss_records', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('external_sku_id',60)->comment('唯一sku标识');
$table->string('batch_number',60)->nullable()->comment('批次号');
$table->date('date')->nullable()->comment('日期');
$table->Integer('num')->default(0)->comment('报损数量');
$table->unsignedDecimal('cost')->default(0)->comment('成本');
$table->Integer('buyer_user_id')->default(0)->comment('购买人用户id');;
$table->string('buyer_name')->nullable()->comment('采购人');
$table->string('reason')->nullable()->comment('报损原因');
$table->string('phenomenon')->nullable()->comment('报损现象');
// 索引
$table->index('external_sku_id');
$table->index('created_at');
$table->index('batch_number');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('loss_records');
}
}

Some files were not shown because too many files have changed in this diff Show More