367 Commits
0.1.19 ... 0.2

Author SHA1 Message Date
0f91b8a332 doc(calculate): 优化代码格式,删除部分无用打印语句 2023-08-15 15:20:59 +08:00
59a604bab0 fix(park): 修复创建报表中核定线损未显示问题 2023-08-15 15:13:10 +08:00
eaf45331f1 fix(calculate_tenement):修复计算相关赋值问题 2023-08-15 14:46:04 +08:00
2844db1a86 fix(#27): 修复用户电量电费详细为空 2023-08-15 14:13:25 +08:00
7d3fafeb04 fix(#27): 未完全修复成功 2023-08-15 11:05:41 +08:00
032d38181a fix(#26): 修复获取公摊表计下的摊薄表计失败[如果没有公摊关系,则无提示,无显示信息] 2023-08-14 17:26:58 +08:00
292a50029f Merge branch '0.2' of https://git.archgrid.xyz/free-lancers/electricity_bill_calc_service into 0.2 2023-08-14 16:34:16 +08:00
de176ce61d fix(#23): 修复获取园区公共电费概况有问题 2023-08-14 16:34:12 +08:00
8a383515d3 fix(#22):修复部分字段读数问题 2023-08-14 14:42:15 +08:00
37bd0da1f2 Merge branch '0.2' of https://git.archgrid.xyz/free-lancers/electricity_bill_calc_service into 0.2 2023-08-14 13:49:48 +08:00
bfa7ac1508 fix(#24):修复获取到指定核算报表中的分页公摊表计的核算信息问题 2023-08-14 13:48:58 +08:00
3f36153968 fix(#22):修复无法获取报表的总览信息错误 2023-08-14 13:24:25 +08:00
845bd75348 fix(#10): 修复创建报表错误 2023-08-14 10:05:09 +08:00
559c2d439d Merge branch '0.2' of https://git.archgrid.xyz/free-lancers/electricity_bill_calc_service into 0.2 2023-08-11 15:31:37 +08:00
d6829fce27 fix(#10):修复空指针 2023-08-11 15:31:24 +08:00
d1e8c63ec9 fix(park): 修复删除园区返回信息错误 2023-08-11 15:19:06 +08:00
e229f3976d fix(#20): 修复在删除充值记录的时候出现问题 2023-08-11 14:52:15 +08:00
683907b363 fix(#10): 计算相关读取数据部分debug完成 2023-08-11 13:13:45 +08:00
c1b84d1fbb Merge branch '0.2' of https://git.archgrid.xyz/free-lancers/electricity_bill_calc_service into 0.2 2023-08-11 11:00:32 +08:00
2021d67d03 fix(#10):修复空指针 2023-08-11 10:59:53 +08:00
b988ffcb75 fix(pooled): 增加缺少字段loss 2023-08-11 10:57:45 +08:00
361b302407 Merge branch '0.2' of https://git.archgrid.xyz/free-lancers/electricity_bill_calc_service into 0.2 2023-08-11 10:37:40 +08:00
d8f470e660 fix(#10): 修复部分计算错误,遗留问题在pooled中的deteminePublicMeterConsumpyions里时间为空错误 2023-08-11 10:37:17 +08:00
c472eb4196 fix(#17):修复有绑定表计的商户迁出时无法迁出的错误 2023-08-11 09:02:57 +08:00
98866a8de5 fix(#16):修复商户入驻时间为指定日期,并非当前日期 2023-08-10 16:33:41 +08:00
c89a5f20ad fix(doc): 注释优化 2023-08-10 16:13:12 +08:00
01e944cb5a fix(meter): 修改结构体MeterCreationForm中的字段名称,和此结构体的调用处 2023-08-10 16:12:13 +08:00
c2d43bd98e fix(#15): 修复: 更换表计的时候,在代码中接收了参数未作任何处理 2023-08-10 15:27:59 +08:00
a543b33276 Merge branch '0.2' of https://git.archgrid.xyz/free-lancers/electricity_bill_calc_service into 0.2 2023-08-10 15:03:49 +08:00
6289257ac1 fix(#13):修复历史电费核算时未选择园区时显示园区中的数据,选中某一园区时显示不同园区的数据 2023-08-10 14:54:12 +08:00
5d997c14ee Merge branch '0.2' of https://git.archgrid.xyz/free-lancers/electricity_bill_calc_service into 0.2 2023-08-10 10:49:47 +08:00
b50eabbca6 fix(meter): 修复:更换电表时的修改逻辑 2023-08-10 10:49:40 +08:00
a34aa6ad07 fix(#12):修复抄表记录与表记管理处无论选择任何表记类型只会显示全部错误 2023-08-10 09:54:57 +08:00
3d918eea85 fix(tenement): 解决向园区中指定商户下绑定一个新的表计时时间异常问题 2023-08-10 09:32:38 +08:00
7f46f2f36b fix(calculate): 修复创建报表时的状态错误 2023-08-09 16:24:16 +08:00
ed10996a06 Merge branch '0.2' of https://git.archgrid.xyz/free-lancers/electricity_bill_calc_service into 0.2 2023-08-09 15:44:10 +08:00
2aa3729e03 fix(#12):修复抄表记录与表记管理处无论选择任何表记类型只会显示全部错误 2023-08-09 15:42:37 +08:00
7d59121c2f fix(report): 调试,历史电费核算部分 2023-08-09 15:35:02 +08:00
b2efb972dc fix(report): 调试,历史电费核算部分 2023-08-09 15:28:17 +08:00
5f750dd0e0 Merge branch '0.2' of https://git.archgrid.xyz/free-lancers/electricity_bill_calc_service into 0.7 2023-08-09 10:51:57 +08:00
dd6becc994 fix(#10):修复创建报表错误,但查出列出园区bug 2023-08-09 10:48:33 +08:00
b59e24333e fix(report): 修复核算报表中园区获取错误 2023-08-09 09:18:51 +08:00
00cf35ec2a fix(#10):修复创建报表错误,但查出列出园区bug 2023-08-08 17:41:16 +08:00
ed5a3f6c0a fix(#5):修复为前端下列表记提供的接口 2023-08-08 15:21:12 +08:00
a593666542 fix(authorized_loss_rate): 修复获取系统中定义的基准核定线损率错误 2023-08-08 14:43:45 +08:00
8a4be5328e fix(#8): 合并分支处理冲突时候遗留问题,并且注释中的问题未处理。 2023-08-08 14:02:20 +08:00
0135b81080 doc(settings): 设置基准线损率为7% 2023-08-08 13:57:05 +08:00
fb328514d1 fix(config): 优化redis的连接,同一调取配置文件内的 2023-08-08 13:51:00 +08:00
ad1b816795 fix(#5):修复抄表管理页面显示的表计信息多余数据库中的数据 2023-08-08 13:47:02 +08:00
fe7f8bd692 Merge branch '0.2' of https://git.archgrid.xyz/free-lancers/electricity_bill_calc_service into 0.2 2023-08-08 13:34:29 +08:00
f55cc07b1c doc(gitignore): 2023-08-08 13:34:23 +08:00
a3d4dbfca5 fix:#6 解决表号为空时候的sql错误 2023-08-08 13:24:58 +08:00
2bab62512a doc(gitignore):禁止goland个性化配置上传 2023-08-08 10:33:48 +08:00
e3c34c96e2 enhance(sync):同步管理路由添加 2023-08-08 10:05:37 +08:00
a34fee8339 fix(meterDetail): 增加缺少字段display_ratio 2023-08-08 08:54:19 +08:00
69357f77dd fix(report):修复更新指定的核算任务与初始化一个新的核算任务 2023-08-07 17:33:38 +08:00
27a499b8cc fix(report): 修复启动指定核算任务的计算功能 2023-08-07 17:05:29 +08:00
568deaf972 enhance(report): 2023-08-07 17:00:46 +08:00
f995b89ea9 test(config):合并分时提交的配置文件 2023-08-07 15:32:10 +08:00
cd56f2b444 test(user): 用于合并分支 2023-08-07 15:30:50 +08:00
8163fd99ee Merge pull request 'enhance(calculate): 完善计算部分' (#3) from Deka_123/electricity_bill_calc_service:newBranch into 0.2
Reviewed-on: free-lancers/electricity_bill_calc_service#3
2023-08-07 15:24:21 +08:00
0e365cdb6b enhance(calculate): 完善计算部分 2023-08-07 15:15:11 +08:00
020e76b901 合并分支 2023-08-04 17:11:10 +08:00
79d55505a7 Merge pull request 'ZiHangQinBranch' (#2) from ZiHangQin/electricity_bill_calc_service:ZiHangQinBranch into 0.2
Reviewed-on: free-lancers/electricity_bill_calc_service#2
2023-08-04 15:01:24 +08:00
af359f4429 enhance(calculate): 增加方法:计算商户的合计电费信息,并归总与商户相关关联的表计记录 2023-08-04 14:38:03 +08:00
ce4c483bcb [计算相关]计算已经启用的商铺面积和(完成) 2023-08-04 11:09:04 +08:00
94e19f4c5a Merge pull request 'ZiHangQinBranch' (#1) from ZiHangQin/electricity_bill_calc_service:ZiHangQinBranch into 0.2
Reviewed-on: free-lancers/electricity_bill_calc_service#1
2023-08-04 10:27:19 +08:00
c916301f6b 优化注释 2023-08-04 10:24:09 +08:00
5710a640e8 [计算相关]fix 计算线损以及调整线损参数错误。new 计算所有已经启用的商铺面积总和,仅计算所有未迁出的商户的所有表计对应的商铺面积。(完成) 2023-08-04 10:05:27 +08:00
6b3d3dd93c [计算相关]计算线损以及调整线损(完成) 2023-08-04 09:39:59 +08:00
8fc463bd9d [计算相关]计算所有表计的总电量(完成) 2023-08-03 17:30:00 +08:00
f688f50ecb [计算相关]获取所有的物业表计,然后对所有的物业表计电量进行计算。(完成) 2023-08-03 16:59:58 +08:00
f254ec1f3a [天神模式]删除符合条件表计完成 2023-07-31 10:15:14 +08:00
9b899be33d [天神模式]删除符合条件表计公摊关系完成 2023-07-31 09:50:41 +08:00
c36bfff05a 删除指定的企业用户完成 2023-07-31 09:41:45 +08:00
b84c51b18e 修正router中的方法错误 2023-07-28 17:33:36 +08:00
1dd5f1049d [天神模式]删除符合条件的商户绑定的表计关系 2023-07-28 17:29:41 +08:00
18d48c7fea [天神模式]删除符合条件的报表完成 2023-07-28 16:46:52 +08:00
8ab89bca34 [天神模式]删除指定的园区完成 2023-07-28 16:25:49 +08:00
b64929c10a 给[天神模式]删除指定商户功能添加权限认证 2023-07-27 14:15:34 +08:00
1099a7c335 [天神模式]删除指定商户完成 2023-07-27 14:01:45 +08:00
5866882c2d 统计当前系统中的报表 2023-07-26 15:11:16 +08:00
9ad3415cdb 完成获取当前系统中待审核的内容数量功能 2023-07-26 13:43:30 +08:00
39e404451e 完成获取系统基准线损率功能 2023-07-26 10:03:01 +08:00
251c44049a 修改审核撤回报表请求细节,完成撤回电费核算功能 2023-07-26 08:52:24 +08:00
b3032638fc 增加审核撤回报表用户鉴权 2023-07-25 15:34:30 +08:00
d8a29e7d17 修改分页检索核算报表一些细节,完成审核撤回报表功能 2023-07-25 15:31:35 +08:00
6fece99e00 带分页的待审核的核算撤回申请列表完成 2023-07-25 10:45:43 +08:00
ab44ff5cc4 a 2023-07-25 10:09:53 +08:00
61edef5c92 新增:完善withdraw的返回值 2023-07-20 16:13:19 +08:00
648fc0f370 new:新增withdraw请求,该暂无真实数据 2023-07-18 16:07:56 +08:00
徐涛
7f2ec68197 fix(invoice):改正发票列表获取时,空列表返回null的问题。 2023-06-27 17:04:21 +08:00
徐涛
9dc846c044 fix(invoice):修正对于发票的检索。 2023-06-27 17:01:14 +08:00
徐涛
b7eaaffc3a enhance(types):增加区间类型中关于空区间的判断。 2023-06-27 17:00:44 +08:00
徐涛
1db60a0e4f fix(topup):修复商户充值中的自动复制对应关系。 2023-06-27 16:48:04 +08:00
徐涛
06f86e3cd4 fix(tenement):修正商户下拉列表检索功能。 2023-06-27 16:36:10 +08:00
徐涛
ccc6cac4df fix(topup):修正商户充值部分的查询SQL语句。 2023-06-27 16:24:28 +08:00
徐涛
3ab505e446 fix(meter):修正公摊表计绑定时,对于表计编号的解析。 2023-06-27 16:21:09 +08:00
徐涛
26a951f970 fix(serial):修正雪花ID生成算法中不能保存最后序列号的问题。 2023-06-27 15:37:40 +08:00
徐涛
0a9d5cd121 fix(tenement):修正表计在抄表时进行的数据合理性检查。 2023-06-27 15:28:11 +08:00
徐涛
4df3efacd8 fix(tenement):修正列举商户时的迁入迁出条件筛选。 2023-06-27 15:11:46 +08:00
徐涛
0c389e440a fix(tenement):修正商户中带有JSONB字段的插入语句。 2023-06-27 15:05:04 +08:00
徐涛
a1a72b7204 fix(meter):修复批量导入表计时的SQL语句和数据处理行为。 2023-06-27 14:22:43 +08:00
徐涛
a626869f14 fix(meter):修正对于表计创建和修改表单中的内容的捕获。 2023-06-27 13:18:00 +08:00
徐涛
b4ce754c0d fix(report):修正一系列报表查询中出现的无法获取数据的问题。 2023-06-27 10:58:11 +08:00
徐涛
7806f07766 fix(report):修正报表中获取分摊表计的功能,补充应获取但未获取的字段。 2023-06-26 21:52:17 +08:00
徐涛
877b8304c3 fix(report):确定ConsumptionUnit对应的ConsumptionDisplay数据结构的自动复制模式。 2023-06-26 16:54:26 +08:00
徐涛
b224f51613 fix(meter):在按照编号列表获取表计详细的时候,对于表计编号列表是空的防御措施。 2023-06-26 16:22:53 +08:00
徐涛
ac36c158c0 fix(meter):修正未绑定表计的查询。 2023-06-26 16:03:57 +08:00
徐涛
037e6258d1 fix(meter):修复表计查询中的错误映射关系。 2023-06-26 15:47:25 +08:00
徐涛
aec1655f1c fix(meter):修正数据库字段映射。 2023-06-26 15:15:41 +08:00
徐涛
0246eaba27 fix(tenement):修正商户查询结果的扫描以及错误返回null的问题。 2023-06-26 14:03:37 +08:00
徐涛
e6d9435c14 fix(types):现在日期和时间的指针形式解析不在对空白字符串报错了。 2023-06-26 13:29:34 +08:00
徐涛
062cc6a9ea fix(tenement):修正商户部分处理器的挂载。 2023-06-26 13:27:22 +08:00
徐涛
2792959d1e feat(report):基本完成报表查看部分内容的迁移。 2023-06-26 13:08:34 +08:00
徐涛
fa03bf5dbd enhance(report):基本完成报表查询部分的综合服务部分。 2023-06-20 23:06:43 +08:00
徐涛
fbe4036389 enhance(report):基本完成报表部分的数据库查询操作。 2023-06-20 16:50:34 +08:00
徐涛
b2e4fb809f build(container):编译用容器镜像改用国内镜像。 2023-06-20 13:19:36 +08:00
徐涛
0d73665313 refactor(cache):缓存系统降级,停用全部数据缓存。 2023-06-19 20:56:31 +08:00
徐涛
316553d81a enhance(report):构建报表数据库相关功能文件。 2023-06-19 17:30:17 +08:00
徐涛
2b5272dad9 feat(topup):基本完成商户充值部分接口,待测。 2023-06-19 15:49:16 +08:00
徐涛
234e811324 feat(topup):基本完成商户充值记录的数据库操作部分。 2023-06-19 14:46:21 +08:00
徐涛
541932d62e feat(invoice):基本完成商户发票记录功能,待测。 2023-06-19 13:20:30 +08:00
徐涛
e88477a710 feat(invoice):基本完成发票相关功能的服务操作函数。 2023-06-17 10:44:24 +08:00
徐涛
11bd661e79 enahnce(error):增强未成功完成操作的错误描述。 2023-06-17 10:43:51 +08:00
徐涛
986562c2c2 enahnce(error):将错误类型中的提示信息更改为中文。 2023-06-17 09:37:09 +08:00
徐涛
e5dadf06be feat(error):增加数据不足的错误类型。 2023-06-17 09:30:48 +08:00
徐涛
db52a140b1 feat(invoice):基本完成发票相关数据库操作。 2023-06-16 13:49:00 +08:00
徐涛
ce2bb160e1 enhance(enum):增加公共枚举内容的定义。 2023-06-16 13:48:34 +08:00
徐涛
24f2c26d86 enhance(cache):放开缓存管理中对于构造缓存键的方法。 2023-06-16 10:43:06 +08:00
徐涛
fb388c53c7 enhance(types):日期区间范围增加常用方法的易用性。 2023-06-16 08:40:15 +08:00
徐涛
b244fd5823 enhance(types):增加若干常用方法。 2023-06-16 08:36:11 +08:00
徐涛
2c303cfba7 feat(types):增加日期时间的范围区间类型。 2023-06-16 08:25:01 +08:00
徐涛
2d6bff5828 enhance(tenement):基本完成商户相关借口,待测。 2023-06-15 13:50:50 +08:00
徐涛
577ac9d1dc refactor(park):调整判断园区归属的逻和使用方法。 2023-06-15 09:31:00 +08:00
徐涛
7531a6a5a1 refactor(meter):同意对于园区归属的判断处理句式。 2023-06-15 05:58:00 +08:00
徐涛
be270597f5 refactor(park):园区部分的归属判断改为采用通用函数完成, 2023-06-15 05:48:38 +08:00
徐涛
77587b8157 enhance(types):日期时间类型增加从字符串解析成为日期时间指针类型的函数。 2023-06-14 21:22:10 +08:00
徐涛
d97db0cf50 enhance(tenement):基本完成商户与其他功能联合操作的服务。 2023-06-14 17:26:09 +08:00
徐涛
2558a83024 enhance(tenement):基本完成商户部分功能的数据库交互。 2023-06-14 14:26:18 +08:00
徐涛
7da5bd1112 enhance(meter):基本完成表计抄表记录上传解析,待测。 2023-06-13 21:01:14 +08:00
徐涛
d4fbf86800 build(deps):升级部分依赖库的版本。 2023-06-13 20:43:24 +08:00
徐涛
c9b7dc3aec enhance(meter):调整输出表计抄表模板中数值的格式。 2023-06-13 15:54:06 +08:00
徐涛
424ba2e839 fix(meter):修正写入 Excel 的内容格式。 2023-06-13 14:47:22 +08:00
徐涛
0cffc1b6d1 fix(meter):修补表计抄表记录的模板导出。 2023-06-13 14:43:08 +08:00
徐涛
ef325aede5 fix(meter):修正表计信息的检索。 2023-06-13 14:25:54 +08:00
徐涛
e2a61d58ac enhance(meter):部署抄表模板下载功能。 2023-06-13 14:21:00 +08:00
徐涛
542efdac86 enhance(meter):调整输出日志语种。 2023-06-13 13:28:13 +08:00
徐涛
7c6f211931 enhance(meter):调整输出的表计档案模板中的单元格内容格式。 2023-06-13 13:25:54 +08:00
徐涛
23e9e2ec4d enhance(meter):恢复对于表计类型的解析错误提示,并在表计类型解析出现错误的时候停下来。 2023-06-13 11:11:10 +08:00
徐涛
6f0edc54bf enhance(meter):调整对于表计类型的解析。 2023-06-13 10:59:49 +08:00
徐涛
7bac667c2a refactor(meter):表计档案模板的下载改为使用Excel 生成的方法实现。 2023-06-13 10:23:47 +08:00
徐涛
46ae943653 enhance(log):调整日志系统配置。 2023-06-12 17:09:13 +08:00
徐涛
2339e4c725 enhance(meter):完成大部分表计相关的接口。 2023-06-11 22:31:32 +08:00
徐涛
e366888608 enhance(cache):增加快速处理可空值作为Redis缓存键的处理。 2023-06-11 18:48:48 +08:00
徐涛
cadb2db8c7 enhance(log):改进对于空白指针类型的日志输出。 2023-06-11 17:50:40 +08:00
徐涛
7476278c52 enhance(tools):增加一个用于将空字符串转换为空白指针的功能函数。 2023-06-11 17:43:34 +08:00
徐涛
bfa9da4a03 enhance(log):精简了在日志中输出访问请求的内容。 2023-06-11 17:36:38 +08:00
徐涛
c2a56c8253 enahnce(security):改进对于令牌的获取。 2023-06-11 17:35:50 +08:00
徐涛
5d938da53e enhance(types):修正日期时间解析尝试模板。 2023-06-10 11:37:52 +08:00
徐涛
686890b5d8 enhance(types):增加不同日期时间格式的解析。 2023-06-10 11:26:10 +08:00
徐涛
50418f2e30 enhance(excel):调整抄表记录 Excel 的列识别关键字。 2023-06-10 07:45:22 +08:00
徐涛
0020776218 enhance(excel):增加对于表计档案和抄表记录两个 Excel 文件的解析器。 2023-06-10 06:37:34 +08:00
徐涛
48753eb3f0 enhance(excel):增加解析日期与时间类型内容的功能。 2023-06-10 06:28:04 +08:00
徐涛
ab2bcb3cf6 enhance(types):喂日期时间类型增加有字符串解析的功能。 2023-06-10 06:27:31 +08:00
徐涛
005f9020a1 fix(park):修正查询指定用户园区功能。 2023-06-09 06:06:04 +08:00
徐涛
2d17bd5f0d fix(park):修正园区信息检索。 2023-06-08 22:23:56 +08:00
徐涛
2f17853dc0 fix(park):修改更新指定园区可用性时的提示语。 2023-06-08 22:15:30 +08:00
徐涛
ea1c1d7829 fix(park):修正园区详细信息的查询。 2023-06-08 22:14:27 +08:00
徐涛
c302b32367 fix(park):修改园区信息编辑时给出的提示。 2023-06-08 22:08:10 +08:00
徐涛
7f43965f1c fix(charge):基本完成用户充值功能部分的测试。 2023-06-08 21:59:01 +08:00
徐涛
0c725e99a4 fix(charge):修复用户充值记录创建的时候对于数值类型内容解析的问题。 2023-06-08 21:47:04 +08:00
徐涛
33ccd7e0b6 fix(charge):修复用户充值部分的日期相关查询。 2023-06-08 21:40:54 +08:00
徐涛
40abbcfb8c fix(charge):改进用户充值部分的查询错误。 2023-06-08 21:35:34 +08:00
徐涛
62560e4eeb fix(user):补充清理用户部分的缓存。 2023-06-08 21:08:36 +08:00
徐涛
28b1478e9a enhance(meter):基本完成表计大部分功能的放置。 2023-06-08 20:55:56 +08:00
徐涛
6bf4009338 refactor(serial):唯一序列号生成器改进。 2023-06-07 21:25:28 +08:00
徐涛
bfb59a3626 refactor(park):补充重构删除原time模块以后的问题。 2023-06-05 22:16:14 +08:00
徐涛
8aa3a054b0 refactor(time):彻底重构time类型。 2023-06-05 22:09:42 +08:00
徐涛
1fd5e7b9aa enhance(park):将判断园区归属函数提升到控制器全局。 2023-06-05 21:53:57 +08:00
徐涛
85f4d04a7f refactor(types):将日期时间类型提取到公共的类型定义包中。 2023-06-05 21:53:05 +08:00
徐涛
c22e7e7dc0 feat(park):基本完成园区功能接口的迁移。 2023-06-03 22:48:33 +08:00
徐涛
98f3bdec0a feat(charge):基本完成用户充值管理部分接口。 2023-06-03 11:26:36 +08:00
徐涛
f8ef6aba98 enhance(user):更改用户检索中like的定义方式。 2023-06-02 16:27:43 +08:00
徐涛
097e25f070 feat(user):完成用户部分所有接口的迁移。 2023-06-02 15:51:08 +08:00
徐涛
cd723e98e3 feat(user):完成获取用户服务有效期的定义。 2023-06-02 10:18:45 +08:00
徐涛
919883f521 enhance(user):为用户模型增加Json键定义。 2023-06-02 10:18:16 +08:00
徐涛
04dc9c51aa fix(user):根据行政区划部分的测试结论,修正用户部分的模型扫描。 2023-06-02 06:26:22 +08:00
徐涛
ee55507e05 enhance(region):追加行政区划内容的调整。 2023-06-02 06:18:57 +08:00
徐涛
e5b5322e0d fix(region):基本确定行政区划部分功能,明确模型扫描功能的使用。 2023-06-02 06:18:34 +08:00
徐涛
bca0fd777d enhance(log):将日志改为中文。 2023-06-01 18:53:45 +08:00
徐涛
6efe16e2fe enhance(user):基本完成用户系列的Repository功能迁移。 2023-06-01 13:56:06 +08:00
徐涛
afc0114181 enhance(user):调整用户查询的排序。 2023-06-01 12:55:28 +08:00
徐涛
9aa32d99b9 feat(user):通过完成用户检索功能,继续确定项目的基本代码结构。 2023-06-01 12:04:03 +08:00
徐涛
71f39c8c2f enhance(cache):更正缓存函数名称,增加分页检索缓存功能。 2023-06-01 10:05:29 +08:00
徐涛
73737c3753 feat(user):完成用户登出功能。 2023-06-01 06:13:43 +08:00
徐涛
61fef8d0fa enhance(user):打通用户登录功能,调整程序基本结构。 2023-06-01 06:01:29 +08:00
徐涛
523e6215f4 enhance(user):完成可运行的程序基本结构以及用户基本查询功能。 2023-06-01 05:37:41 +08:00
徐涛
47cd27c968 fix(db):完成数据库连接配置。 2023-05-31 22:08:08 +08:00
徐涛
0d5457fca3 enhance(migration):删除全部数据库迁移代码。 2023-05-31 16:26:16 +08:00
徐涛
28609df9ec enhance(user):使用Goqu SQL构建库重写查询构成。 2023-05-31 14:47:50 +08:00
徐涛
31ec847ab2 enhance(model):改进用户会话中使用的类型。 2023-05-31 09:41:52 +08:00
徐涛
83b0cd89f4 enhance(config):配置雪花ID生成算法增加主机编号配置。 2023-05-31 09:40:57 +08:00
徐涛
1ccd19d78b enhance(utils):移动时间工具函数包的位置。 2023-05-31 09:40:28 +08:00
徐涛
2b30481419 feat(serial):增加改进雪花算法。 2023-05-31 09:38:39 +08:00
徐涛
ac94c578d6 refactor(changes):暂时删除全部内容,并完成基本数据库连接的创建。 2023-05-30 14:55:39 +08:00
徐涛
12ec8d26bf fix(charge):修正用户计费无法搜索的问题。 2022-09-29 16:14:41 +08:00
徐涛
e67b9afa68 fix(user):更正用户分页检索接口的权限。 2022-09-29 10:43:07 +08:00
徐涛
201af1cc25 fix(charge):修复记录列表中没有列出转供电企业的名称的问题。 2022-09-29 09:44:35 +08:00
徐涛
183092b670 enhance(user):清除已经不再使用的调试语句。 2022-09-29 09:43:27 +08:00
徐涛
8caf069b8a fix(charge):修复记录用户延期时用户列表无法列出用户名称的问题。 2022-09-29 09:42:11 +08:00
徐涛
11cc6f0de1 enhance(json):调整JSON编解码器的配置。 2022-09-29 06:32:50 +08:00
徐涛
00e2664007 fix(meter):调整路由定义顺序。 2022-09-28 21:22:14 +08:00
徐涛
746a9bcaa9 refactor(dep):整理迁移到fiber以后的依赖。 2022-09-28 20:50:49 +08:00
徐涛
7a8e8014ad refactor(god):天神模式控制器基本完成向fiber的迁移。 2022-09-28 20:35:46 +08:00
徐涛
c028056458 refactor(fee):附加费控制器基本完成向fiber的迁移。 2022-09-28 20:29:15 +08:00
徐涛
5c1b26c77f refactor(meter):终端表计控制器基本切换到fiber框架。 2022-09-28 20:14:02 +08:00
徐涛
122bc228bf refactor(park):园区部分的控制器基本完成向fiber框架的迁移。 2022-09-28 17:26:48 +08:00
徐涛
ba9c87d2b1 refactor(controller):完成部分控制器向fiber框架的迁移。 2022-09-28 17:13:26 +08:00
徐涛
e13193de6d refactor(charge):基本完成用户续费记录接口到fiber框架的切换。 2022-09-28 16:02:24 +08:00
徐涛
4b08952916 refactor(app):基本完成基础服务框架的功能构建。 2022-09-28 16:01:16 +08:00
徐涛
df9bf83bb8 fix(deprecates):继续清理已经删除的表计摊薄字段。 2022-09-23 15:53:56 +08:00
徐涛
0169419707 enhance(enduser):精简终端表计统计所使用的查询语句。 2022-09-23 10:26:36 +08:00
徐涛
2ba64227d0 enhance(fee):放开费用检索的权限。 2022-09-22 21:03:28 +08:00
徐涛
c56e1128ff fix(login):修正监管方登录返回的代码。 2022-09-22 20:57:33 +08:00
徐涛
7e861ba4e6 enhance(cache):修改附加费关联缓存的设计。 2022-09-22 20:26:51 +08:00
徐涛
712d704004 fix(enduser):调整更新终端用户调整电费统计部分的空指针问题。 2022-09-22 17:04:49 +08:00
徐涛
502a8bbcee fix(enduser):修正当调整电费也是空值时出现的空指针问题。 2022-09-22 16:16:14 +08:00
徐涛
c306a749aa fix(enduser):改正一个查询语句中的大小写问题。 2022-09-22 16:12:28 +08:00
徐涛
1385f0bbcb fix(enduser):修正调整电费统计中失误计入未发布报表的问题。 2022-09-22 15:01:58 +08:00
徐涛
0e7333b104 fix(park):修正两处不能保存内容的错误。 2022-09-22 14:40:23 +08:00
徐涛
de6e24dcd3 fix(enduser):修正当户址与户名为空的时候出现的空指针问题。 2022-09-22 14:14:34 +08:00
徐涛
f3457c9ab2 fix(fee):修正错误的文字。 2022-09-22 13:25:14 +08:00
徐涛
022788bb44 enhance(enduser):终端用户统计部分增加终端用户表计类型。 2022-09-22 10:19:25 +08:00
徐涛
f4ee7cf8a4 enhance(enduser):基本完成终端用户表计在一个时间阶段内的统计功能。 2022-09-22 09:56:33 +08:00
徐涛
f8f8a0ced1 enhance(fee):基本完成物业附加费部分的运算。 2022-09-21 22:46:33 +08:00
徐涛
38ec847f55 fix(publicity):更新公示报表中错误的json字段命名。 2022-09-21 16:54:33 +08:00
徐涛
d47a052d39 fix(migrate):修正数据库迁移脚本中的错误。 2022-09-21 16:49:30 +08:00
徐涛
9f68d3adea enhance(publicity):调整公示报表数据结构与计算。 2022-09-21 16:44:01 +08:00
徐涛
c716de21aa enhance(model):调整物业附加费、公示报表部分的数据结构模型,以及数据库迁移脚本。 2022-09-21 15:36:03 +08:00
徐涛
fa61f83c6a refactor(db):切换底层数据库驱动。 2022-09-21 08:47:24 +08:00
徐涛
f66782c87b enhance(utils):增加对切片进行分区的工具函数。 2022-09-20 17:12:20 +08:00
徐涛
875b013c2b fix(migrate);修正数据库迁移时默认数据迁移方向。 2022-09-20 16:22:46 +08:00
徐涛
8ec217534e enhance(calculate):基本完成摊薄总计部分的计算改动。 2022-09-20 15:44:25 +08:00
徐涛
2c182496fa enhance(model):调整数据结构使其方便迁移。 2022-09-20 15:09:00 +08:00
徐涛
c7569fbc2c enhance(model):修改报表及公示的数据结构,使其能够支持公共区域部分的计算。 2022-09-20 15:05:24 +08:00
徐涛
6740ab3f48 enhance(migrate):调整报表总计的数据库结构。
重要:下一次升级数据库,需要清除已经不再使用的报表总计列。
2022-09-20 14:56:03 +08:00
徐涛
aa3a4dc44e fix(enduser):修正抄表记录的初始化与上传。现在可以正常上传了。 2022-09-20 14:50:14 +08:00
徐涛
d7f3e0f096 fix(date):空日期改为2000年。 2022-09-20 14:24:43 +08:00
徐涛
efc0a605b7 fix(model):调整Decimal类型,防止bun框架将其认定为JSONB类型。 2022-09-20 12:31:55 +08:00
徐涛
b5c2455af7 enhance(meter):改进终端表计批量导入性能。 2022-09-20 11:03:38 +08:00
徐涛
a13193cfa4 enhance(publicity):向前端返回的终端用户数据增加公共设施与摊薄标记。 2022-09-20 06:39:57 +08:00
徐涛
794054b831 enhance(config):调整数据库连接池配置。 2022-09-19 22:26:27 +08:00
徐涛
aa5c43e51a fix(god):基本完成天神模式中的查询调整。 2022-09-19 20:55:32 +08:00
徐涛
8a070d4396 refactor(pg):更换数据库驱动为pgx。 2022-09-19 17:26:04 +08:00
徐涛
4620271fa5 fix(report):基本完成公示报表检索部分的查询。 2022-09-19 14:56:12 +08:00
徐涛
7f585c0452 fix(report):修正精简组装报表查询语句。 2022-09-19 14:41:49 +08:00
徐涛
d98e782f12 fix(withdraw):修正撤回管理中嵌套查询的问题。 2022-09-19 14:40:05 +08:00
徐涛
cafed0c7f0 doc(readme):更新数据嵌套的说明。 2022-09-19 14:38:48 +08:00
徐涛
8bd9c2f46f doc(readme):增加编写要点内容。 2022-09-19 14:29:43 +08:00
徐涛
0355300908 fix(report):基本完成报表部分的查询调整。 2022-09-19 14:27:25 +08:00
徐涛
3742a445b7 fix(meter):修正终端表计档案管理中无效的判断。 2022-09-19 12:51:05 +08:00
徐涛
01953132ca fix(meter):终端用户表计档案完成查询调整。 2022-09-19 12:19:31 +08:00
徐涛
ed83fbc77c fix(fee):完成配电维护费部分的查询语句调整。 2022-09-19 11:54:23 +08:00
徐涛
308c9e959c fix(park):修正一处应用错误。 2022-09-19 11:27:37 +08:00
徐涛
1e04922cee fix(park):完成园区部分的数据库迁移调整。 2022-09-19 11:24:18 +08:00
徐涛
a9f93d5239 enhance(model):调整自定义的日期类型,以及相关的查询。 2022-09-19 11:06:10 +08:00
徐涛
d885538500 build(debug):增加允许在VSCode中使用jsoniter进行编译的功能。 2022-09-19 08:52:25 +08:00
徐涛
f13ba3fca0 enhance(model):增加一个专门用于保存日期的自定义类型。 2022-09-19 08:51:55 +08:00
徐涛
720bdd54fe fix(user):修正转供电企业快速查询功能。 2022-09-18 22:24:09 +08:00
徐涛
b638bc5f75 fix(charge):修正日期保存时的时区问题。 2022-09-18 22:17:00 +08:00
徐涛
3f8ced5453 doc(readme):更新项目编写说明。 2022-09-18 22:15:40 +08:00
徐涛
ecd2238c9e fix(user):修正指定用户详细信息的获取。 2022-09-18 11:45:03 +08:00
徐涛
2218e27838 fix(user):修正对于用户名称是否为空的判断。 2022-09-18 11:42:34 +08:00
徐涛
710d285733 fix(user):更新用户名称在修改的时候,拼音缩写不跟随修改的问题。 2022-09-18 11:40:40 +08:00
徐涛
7b8ee5ddbd fix(model):补充所有数据模型中用于记录操作时间的Hook。 2022-09-18 11:31:32 +08:00
徐涛
e40ba55825 fix(user):修正检索用户时语句中的引用问题。 2022-09-18 10:50:49 +08:00
徐涛
2aa6939186 fix(stat):修正统计部分的关联设计。 2022-09-18 10:26:43 +08:00
徐涛
1c0ebc9559 fix(region):修正行政区划中的查询错误。 2022-09-18 10:15:53 +08:00
徐涛
0d062560da fix(model):修正模型定义中的继承部分。 2022-09-18 10:11:23 +08:00
徐涛
9bbb58ae55 fix(migrate):修正迁移文件中的错误。 2022-09-18 10:03:32 +08:00
徐涛
d0d6c9f721 refactor(main):基本完成主进程及启动任务的迁移,清理项目依赖。 2022-09-18 09:59:49 +08:00
徐涛
3f60376061 refactor(user):用户控制器基本完成迁移。 2022-09-18 08:18:31 +08:00
徐涛
cea90c5b29 fix(query):修正查询时所使用的变量类型。 2022-09-18 07:07:26 +08:00
徐涛
ffaccc4c88 fix(service):修正一些使用错误。 2022-09-17 21:35:50 +08:00
徐涛
60d2db6cdd refactor(calculate):报表计算服务基本完成迁移。 2022-09-17 21:31:37 +08:00
徐涛
805911f72b refactor(stat):统计服务基本完成迁移。 2022-09-17 18:45:02 +08:00
徐涛
df08c31278 refactor(god):天神模式基本完成迁移。 2022-09-17 18:26:23 +08:00
徐涛
fc3f931362 refactor(enduser):终端用户抄表控制基本完成迁移。 2022-09-17 07:05:17 +08:00
徐涛
e250ef6792 refactor(withdraw):报表撤回系列功能基本完成迁移。 2022-09-16 22:17:19 +08:00
徐涛
d778c4d3f3 refactor(report):报表的服务功能基本完成迁移。 2022-09-16 17:27:10 +08:00
徐涛
7ce9abe1de enhance(model):增加两个用于快速转换数据结构的方法。 2022-09-16 17:26:41 +08:00
徐涛
dba74ecd49 fix(fee):修正bun中Model方法传递的内容。 2022-09-16 13:09:07 +08:00
徐涛
398e67a7bd enhance(fee):为园区固定费用服务增加调试用的日志记录器。 2022-09-16 10:38:08 +08:00
徐涛
ab7ce6d0c6 refactor(fee):园区固定费用基本完成迁移。 2022-09-16 10:36:14 +08:00
徐涛
e2767501fb refactor(meter):园区终端用户表计基本完成迁移。 2022-09-16 10:07:15 +08:00
徐涛
3d20ceb35a refactor(park):园区部分基本完成迁移。 2022-09-16 09:26:48 +08:00
徐涛
c6c1423364 refactor(charge):基本完成用户计费部分的迁移。 2022-09-16 08:51:10 +08:00
徐涛
cb2908435a refactor(context):改进超时上下文的生成。 2022-09-16 05:38:44 +08:00
徐涛
92e8d312dd enhance(migrate):用户费用记录改为自增长序列类型。 2022-09-16 05:28:37 +08:00
徐涛
ce7b69923d refactor(user):基本完成用户服务层的迁移。 2022-09-15 21:03:05 +08:00
徐涛
f8025c5dea fix(region):纠正一个可能错误的Where子句用法。 2022-09-15 16:29:39 +08:00
徐涛
8687b462ff refactor(region):基本完成行政区划部分服务的迁移。 2022-09-15 16:22:24 +08:00
徐涛
b262042244 feat(global):全局增加一个用于生成带有超时控制的上下文生成器。 2022-09-15 16:14:30 +08:00
徐涛
ae6e6490b2 refactor(model):基本完成对于数据实体模型的迁移。 2022-09-15 15:58:01 +08:00
徐涛
9380879ab5 enhance(migrate):增加基于bun的迁移脚本。 2022-09-15 13:48:46 +08:00
徐涛
4254d020b9 refactor(db):切换数据库驱动为pq,数据控制库为bun,更改Redis连接的名称。 2022-09-15 10:11:27 +08:00
徐涛
769882dce5 fix(dilute):修正配电维护费项目ID的名称,以使其符合API文档的要求。 2022-09-15 09:42:56 +08:00
徐涛
a3a00d162a chore(project):增加版本库文件屏蔽,加入运行配置。 2022-09-15 09:05:38 +08:00
徐涛
a8430e012f enhance(enduser):导出抄表记录模板中携带有已经保存在系统中的抄表数据。 2022-09-13 16:00:41 +08:00
徐涛
85b9890168 fix(enduser):修正峰谷终端用户数据保存时因为文字错误不能保存的问题。 2022-09-09 15:40:35 +08:00
徐涛
e3f6af886b feat(god):增加重新同步终端用户档案的功能。 2022-09-09 15:13:43 +08:00
徐涛
ac70adf92a fix(calculate):报表的计算会根据园区的不同选择不同的计算方式。 2022-09-09 12:28:40 +08:00
徐涛
e1463de00d build(dep):整理依赖。 2022-09-09 10:56:54 +08:00
徐涛
ca5d306b88 enhance(log):优化日志记录器的命名策略。 2022-09-09 10:55:26 +08:00
徐涛
0bd6c27ab9 enhance(log):为默认的日志记录器提供默认的名称。 2022-09-09 10:47:25 +08:00
徐涛
5c0c77202c enhance(log):Web框架所使用的日志中间件改用第三方定义的中间件。 2022-09-09 09:31:51 +08:00
徐涛
54b680f757 enhance(log):日志文件与控制台日志的配置分离。 2022-09-09 09:21:19 +08:00
徐涛
146ef4b7ef enhance(log):改进日志的滚动保存配置。 2022-09-09 08:49:53 +08:00
徐涛
e619d27559 enhance(enduser):为导出的用户抄表记录文件增加文件名内容。 2022-09-08 20:34:01 +08:00
徐涛
dce83d7e49 fix(excel):修复NullDecimal类型内容的导入。 2022-09-08 19:39:10 +08:00
徐涛
6d28740a51 enhance(docker):容器化配置增加日志保存目录卷。 2022-09-08 16:16:02 +08:00
徐涛
8d190a3478 fix(log):清理系统中无用的调试日志输出。 2022-09-08 16:13:45 +08:00
徐涛
46494dd46e feat(log):日志系统切换至zap。 2022-09-08 16:10:08 +08:00
徐涛
d38b6ab064 feat(log):完全使用Zerolog接管项目中的日志记录。 2022-09-08 14:33:54 +08:00
徐涛
4f11249b94 feat(log):加入zerolog和lumberjack的支持,实现日志功能的初步封装。 2022-09-08 13:15:51 +08:00
徐涛
c433652326 fix(park):修复不能正确记录园区峰谷用户表计类型的问题。 2022-09-08 10:59:09 +08:00
徐涛
62a9fec43f enhance(god):清除报表中不同部分的时候同时重置报表的进度状态。 2022-09-07 14:04:52 +08:00
徐涛
466d21e8b4 fix(god):修复并调整SQL语句。 2022-09-07 13:15:19 +08:00
徐涛
ab92f22c85 fix(god):修复删除报表中SQL语句的错误。 2022-09-06 19:03:07 +08:00
徐涛
2e8bbf2b99 fix(god):补充遗漏的抄表记录字段。 2022-09-06 18:52:25 +08:00
徐涛
c3324128d0 fix(god):修复清空报表中维护费方法。 2022-09-06 18:40:18 +08:00
徐涛
7be45d3ffc feat(god):完成天神模式的控制器,整体功能待测。 2022-09-06 17:29:33 +08:00
徐涛
86de5fd3ad enhance(god):附加清理用户计费信息缓存。 2022-09-06 16:51:09 +08:00
徐涛
b0c4984b21 enhance(god):增加几个漏掉删除的数据表。 2022-09-06 16:50:09 +08:00
徐涛
97bad80784 enhance(god):增加清理关联缓存。 2022-09-06 16:46:14 +08:00
徐涛
0ec7f5fba1 Merge branch 'master' into god-mode 2022-09-06 16:08:45 +08:00
徐涛
0d2b1431b6 fix(cache):修复缓存关联关系被不正常清除的问题。 2022-09-06 15:53:24 +08:00
徐涛
8aa4e38760 fix(park):修复园区不能搜索的问题。 2022-09-06 15:28:27 +08:00
徐涛
954285e426 fix(cache):清除已经不用的调试日志。 2022-09-06 15:19:32 +08:00
徐涛
a4a9938675 enhance(cache):精简缓存关系清理的逻辑。 2022-09-06 15:12:32 +08:00
徐涛
8a21a2f469 fix(cache):修正缓存无法保存纯数字内容的问题。 2022-09-06 15:06:34 +08:00
徐涛
a1e9167cdf enhance(cache):增加缓存关联关系中无效键清理方法。 2022-09-06 13:56:30 +08:00
徐涛
2a07db75c7 enhance(cache):删除已经不再使用的缓存方法。 2022-09-06 13:17:59 +08:00
徐涛
60280d0e06 enhance(cache):实体的存在性检查改为使用普通Key存储。 2022-09-06 12:43:04 +08:00
徐涛
f0c22db31f fix(cache):修正缓存关联记录中记录查询数量的键类型。 2022-09-06 12:37:20 +08:00
徐涛
2ea8443409 enhance(cache):改用普通Key记录查询数量。 2022-09-06 12:35:53 +08:00
徐涛
bd1033ac47 enhance(cache):为缓存增加60秒内的随机秒数,以避免缓存雪崩。 2022-09-06 12:28:06 +08:00
徐涛
ae056f612a fix(calculate):修正园区总电量在保存后电度电费不显示的问题。 2022-09-06 11:24:39 +08:00
徐涛
a512773f11 fix(park):修复创建和修改园区的时候园区住户数量不能被保存的问题。 2022-09-06 11:13:34 +08:00
徐涛
901cbf23bb feat(god):基本完成天神模式所使用的数据服务。 2022-09-06 10:45:20 +08:00
徐涛
c4ab500235 fix(calculate):修正终端用户的损失分摊的计算。 2022-09-05 17:57:40 +08:00
徐涛
5332fc9b4f fix(enduser):修正抄表时,平段部分的计算。 2022-09-03 22:03:58 +08:00
徐涛
37971f6875 fix(report):修复配电物业费部分的计算。 2022-09-03 21:39:02 +08:00
徐涛
008ebcee79 fix(report):更改两处计算错误。 2022-09-03 15:49:59 +08:00
徐涛
41c4dcea2e fix(report):补充计算功能传递的数据项。 2022-09-03 09:36:05 +08:00
徐涛
13368eace9 fix(enduser):修正一处逻辑错误。 2022-09-02 19:56:30 +08:00
徐涛
b162844159 enhance(cache):为常用的缓存加入生命期设置。 2022-09-02 19:50:40 +08:00
徐涛
c9f8235339 enhance(enduser):终端用户抄表时可根据当前数据库中记录的数量决定是否读取上传表格中的上期表底数。 2022-09-02 19:43:26 +08:00
徐涛
e1d55e4fc7 enhance(excel):调整Excel分析,以避免因为单元格没有填写导致的panic。 2022-09-02 09:52:45 +08:00
徐涛
17bde54c7c fix(user):更正表示用户登录错误的错误码。 2022-09-02 09:52:02 +08:00
徐涛
7abf35ca97 fix(enduser):修正终端用户表计抄表过程中的错误。 2022-08-30 11:42:15 +08:00
徐涛
e150a22174 fix(report):修正保存报表概览信息的时候无法获取到报表ID的问题。 2022-08-30 11:35:23 +08:00
徐涛
e9a122fcda fix(report):修正对于园区的最新报表期数的检测。 2022-08-30 10:37:11 +08:00
徐涛
b09b3f4f2d fix(fee):修正未能正确获得要更新的维护费ID的问题。 2022-08-30 10:27:36 +08:00
徐涛
50c9195797 fix(fee):修正对于维护费归属的判断语句。 2022-08-30 10:15:18 +08:00
徐涛
bdddcec922 fix(meter):增加表计的默认排序。 2022-08-30 10:14:40 +08:00
157 changed files with 16949 additions and 6026 deletions

3
.gitignore vendored
View File

@@ -161,6 +161,7 @@ atlassian-ide-plugin.xml
# Cursive Clojure plugin # Cursive Clojure plugin
.idea/replstate.xml .idea/replstate.xml
.idea/electricity_bill_calc_service.iml
# SonarLint plugin # SonarLint plugin
.idea/sonarlint/ .idea/sonarlint/
@@ -179,3 +180,5 @@ fabric.properties
# Block sensitive configuration files # Block sensitive configuration files
settings.local.yaml settings.local.yaml
log/
__debug_bin

9
.idea/0.2.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

28
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,28 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
</code_scheme>
</component>

11
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="PostgreSQL - postgres@39.105.39.8" uuid="996b1b9f-5c40-4bd6-8c3c-0af67aaaa15d">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://39.105.39.8:9432/postgres</jdbc-url>
</data-source>
</component>
</project>

9
.idea/electricity_bill_calc_service.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="Go" enabled="true" />
</module>

6
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/electricity_bill_calc_service.iml" filepath="$PROJECT_DIR$/.idea/electricity_bill_calc_service.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

19
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Service",
"type": "go",
"request": "launch",
"mode": "auto",
"buildFlags": "-tags=jsoniter",
"cwd": "${workspaceFolder}",
"program": "${workspaceRoot}/main.go",
"args": [
]
}
]
}

14
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"sqltools.connections": [
{
"previewLimit": 50,
"server": "39.105.39.8",
"port": 9432,
"driver": "PostgreSQL",
"name": "Electricity@Archgrid",
"database": "electricity",
"username": "electricity",
"password": "nLgxPO5s8gK2tR0OL0Q"
}
]
}

View File

@@ -1,4 +1,4 @@
FROM golang:1.19-alpine AS builder FROM dockerproxy.com/library/golang:1.20-alpine AS builder
ENV GO111MODULE=on ENV GO111MODULE=on
ENV GOPROXY="https://goproxy.io" ENV GOPROXY="https://goproxy.io"
@@ -9,7 +9,7 @@ RUN go mod download && go mod verify
ADD . /app ADD . /app
RUN CGO_ENABLED=0 GOOS=linux go build -tags=jsoniter -v -o server . RUN CGO_ENABLED=0 GOOS=linux go build -tags=jsoniter -v -o server .
FROM alpine:latest AS production FROM dockerproxy.com/library/alpine:latest AS production
RUN echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.4/main/" > /etc/apk/repositories RUN echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.4/main/" > /etc/apk/repositories
RUN apk add --no-cache tzdata RUN apk add --no-cache tzdata
RUN apk update \ RUN apk update \
@@ -21,6 +21,7 @@ RUN apk update \
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
RUN mkdir /app RUN mkdir /app
WORKDIR /app WORKDIR /app
VOLUME ["/app/log"]
COPY --from=builder /app/server . COPY --from=builder /app/server .
COPY settings.yaml . COPY settings.yaml .
COPY regions.csv . COPY regions.csv .

View File

@@ -11,3 +11,18 @@
项目详细设计方案见[详细设计方案](https://kdocs.cn/l/cawe22YUV3bJ),该设计方案未经许可,禁止私自修改。 项目详细设计方案见[详细设计方案](https://kdocs.cn/l/cawe22YUV3bJ),该设计方案未经许可,禁止私自修改。
项目任务分配与状态概览表见[任务概况](https://kdocs.cn/l/camrXvBMlCNs)。 项目任务分配与状态概览表见[任务概况](https://kdocs.cn/l/camrXvBMlCNs)。
## 项目代码编写要点
### 数据库访问
项目所式的数据库框架采用的是Bun该框架采用贴近SQL语句的数据库方式但是在使用过程中需注意以下几点。
1. 要执行一个语句,必须提供一个`context.Context`类型的上下文,推荐采用`context.WithTimeout()`生成。上下文中携带的超时时间应该能覆盖超时时间所需要运行的全部语句。
1. 语句中用来指定操作目标数据表的`Model()`方法其接受的是一个目标变量的地址如果传入的是一个slice那么就一定需要使用`&`获取其地址,如果是使用`&struct{}`或者`new()`初始化的,则可以直接作为参数传入。
1. 数据库中的时间字段都是带有时区的但是bun中都是使用UTC时间的对于数据库中`timestamptz`类型的字段,可以正常的按照数据库配置的时区保存时间,但是对于没有携带时区的`date`类型,就不能直接向其中传入`time.Time`类型的参数了,必须手工将其转化为字符串形式。
1. 使用Relation关联获取其他数据表内容的时候`Relation()`提及的数据表中配置的`alias`名称将不起作用语句中的数据别名实际上是被关联字段名称的snake_case形式。
1. 需要进行嵌套Relation选择的时候嵌套的Relation可以采用`Relation("A.B")`的形式来指示使用数据模型A中的Relation B。
1. 如果需要只从Relation关联数据表中选择一部分字段不能直接在主查询语句中使用`Column()`方法,必须在`Relation()`方法的第二个参数中声明。
1. 如果需要对Relation关联表中的字段设置`Where`条件子句,那么就必须使用`relation_name__column_name`的双下划线字段选择形式,直接在`Relation()`方法中设定`Where`子句行不通。
1. 定义数据模型的时候数据字段尽可能不要提供默认值如果确实需要默认值要首先考虑在数据库中定义默认值约束其次选择使用Hook来赋予默认值。直接定义在struct tag中的默认值可能会造成bun形成查询语句时意外的空值。

183
_1.gitignore Normal file
View File

@@ -0,0 +1,183 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# Block sensitive configuration files
settings.local.yaml
log/
__debug_bin

Binary file not shown.

72
cache/abstract.go vendored
View File

@@ -3,6 +3,7 @@ package cache
import ( import (
"electricity_bill_calc/global" "electricity_bill_calc/global"
"fmt" "fmt"
"math/rand"
"strings" "strings"
"time" "time"
@@ -23,24 +24,25 @@ const (
func Cache[T interface{}](key string, value *T, expires time.Duration) error { func Cache[T interface{}](key string, value *T, expires time.Duration) error {
var err error var err error
if expires > 0 { if expires > 0 {
setCmd := global.RedisConn.B().Set(). realExpires := expires + time.Duration(rand.Int63n(60))*time.Second
setCmd := global.Rd.B().Set().
Key(key).Value(rueidis.JSON(value)). Key(key).Value(rueidis.JSON(value)).
ExSeconds(int64(expires.Seconds())). ExSeconds(int64(realExpires.Seconds())).
Build() Build()
err = global.RedisConn.Do(global.Ctx, setCmd).Error() err = global.Rd.Do(global.Ctx, setCmd).Error()
} else { } else {
setCmd := global.RedisConn.B().Set(). setCmd := global.Rd.B().Set().
Key(key).Value(rueidis.JSON(value)). Key(key).Value(rueidis.JSON(value)).
Build() Build()
err = global.RedisConn.Do(global.Ctx, setCmd).Error() err = global.Rd.Do(global.Ctx, setCmd).Error()
} }
return err return err
} }
// 从Redis缓存中获取一个数据 // 从Redis缓存中获取一个数据
func Retreive[T interface{}](key string) (*T, error) { func Retrieve[T interface{}](key string) (*T, error) {
getCmd := global.RedisConn.B().Get().Key(key).Build() getCmd := global.Rd.B().Get().Key(key).Build()
result := global.RedisConn.Do(global.Ctx, getCmd) result := global.Rd.Do(global.Ctx, getCmd)
if result.Error() != nil { if result.Error() != nil {
if rueidis.IsRedisNil(result.Error()) { if rueidis.IsRedisNil(result.Error()) {
return nil, nil return nil, nil
@@ -58,8 +60,8 @@ func Retreive[T interface{}](key string) (*T, error) {
// 检查Redis缓存中是否存在指定键的记录 // 检查Redis缓存中是否存在指定键的记录
func Exists(key string) (bool, error) { func Exists(key string) (bool, error) {
existsCmd := global.RedisConn.B().Exists().Key(key).Build() existsCmd := global.Rd.B().Exists().Key(key).Build()
result := global.RedisConn.Do(global.Ctx, existsCmd) result := global.Rd.Do(global.Ctx, existsCmd)
if result.Error() != nil { if result.Error() != nil {
return false, result.Error() return false, result.Error()
} }
@@ -70,8 +72,8 @@ func Exists(key string) (bool, error) {
// 从Redis缓存中删除指定键 // 从Redis缓存中删除指定键
// ! 如果指定键已不存在那么本函数一样会返回false // ! 如果指定键已不存在那么本函数一样会返回false
func Delete(key string) (bool, error) { func Delete(key string) (bool, error) {
deleteCmd := global.RedisConn.B().Del().Key(key).Build() deleteCmd := global.Rd.B().Del().Key(key).Build()
result := global.RedisConn.Do(global.Ctx, deleteCmd) result := global.Rd.Do(global.Ctx, deleteCmd)
if result.Error() != nil { if result.Error() != nil {
return false, result.Error() return false, result.Error()
} }
@@ -79,23 +81,23 @@ func Delete(key string) (bool, error) {
return count > 0, err return count > 0, err
} }
func dissembleScan(result rueidis.RedisResult) (int64, []string, error) { func dissembleScan(result rueidis.RedisResult) (uint64, []string, error) {
var ( var (
err error err error
cursor int64 cursor uint64
keys = make([]string, 0) keys = make([]string, 0)
) )
results, err := result.ToArray() results, err := result.ToArray()
if err != nil { if err != nil {
return -1, keys, err return 0, keys, err
} }
cursor, err = results[0].AsInt64() cursor, err = results[0].AsUint64()
if err != nil { if err != nil {
return -1, keys, err return 0, keys, err
} }
keys, err = results[1].AsStrSlice() keys, err = results[1].AsStrSlice()
if err != nil { if err != nil {
return -1, keys, err return 0, keys, err
} }
return cursor, keys, err return cursor, keys, err
} }
@@ -104,13 +106,13 @@ func dissembleScan(result rueidis.RedisResult) (int64, []string, error) {
func DeleteAll(pattern string) error { func DeleteAll(pattern string) error {
var ( var (
err error err error
cursor int64 cursor uint64
keys = make([]string, 0) keys = make([]string, 0)
sKeys []string sKeys []string
) )
for { for {
scanCmd := global.RedisConn.B().Scan().Cursor(cursor).Match(pattern).Count(20).Build() scanCmd := global.Rd.B().Scan().Cursor(cursor).Match(pattern).Count(20).Build()
results := global.RedisConn.Do(global.Ctx, scanCmd) results := global.Rd.Do(global.Ctx, scanCmd)
cursor, sKeys, err = dissembleScan(results) cursor, sKeys, err = dissembleScan(results)
if err != nil { if err != nil {
return err return err
@@ -121,8 +123,8 @@ func DeleteAll(pattern string) error {
} }
} }
delCmd := global.RedisConn.B().Del().Key(keys...).Build() delCmd := global.Rd.B().Del().Key(keys...).Build()
err = global.RedisConn.Do(global.Ctx, delCmd).Error() err = global.Rd.Do(global.Ctx, delCmd).Error()
return err return err
} }
@@ -135,3 +137,27 @@ func CacheKey(category string, ids ...string) string {
} }
return b.String() return b.String()
} }
type ToString[T any] interface {
ToString() string
*T
}
// 用于生成一个内容可以为空的Redis缓存键这个键的类型必须实现了`ToString`接口。
func NullableConditionKey[P any, T ToString[P]](value T, defaultStr ...string) string {
defaultStr = append(defaultStr, "UNDEF")
if value == nil {
return defaultStr[0]
} else {
return value.ToString()
}
}
// 用于生成一个内容可以为空的字符串指针类型的Redis缓存键。
func NullableStringKey(value *string, defaultStr ...string) string {
defaultStr = append(defaultStr, "UNDEF")
if value == nil {
return defaultStr[0]
}
return *value
}

57
cache/count.go vendored
View File

@@ -1,49 +1,58 @@
package cache package cache
import ( import (
"electricity_bill_calc/global" "fmt"
"strconv"
"strings" "strings"
"time"
) )
func assembleCountKey(entityName string) string { type _CountRecord struct {
var keys = make([]string, 0) Count int64
keys = append(keys, strings.ToUpper(entityName))
return CacheKey(TAG_COUNT, keys...)
} }
func assembleCountIdentification(additional ...string) string { func AssembleCountKey(entityName string, additional ...string) string {
return strings.Join(additional, ":") var keys = make([]string, 0)
keys = append(keys, strings.ToUpper(entityName))
keys = append(keys, additional...)
var b strings.Builder
b.WriteString(TAG_COUNT)
for _, s := range keys {
fmt.Fprintf(&b, ":%s", s)
}
return b.String()
} }
// 向缓存中缓存模型名称明确的包含指定条件的实体记录数量 // 向缓存中缓存模型名称明确的包含指定条件的实体记录数量
func CacheCount(relationNames []string, entityName string, count int64, conditions ...string) error { func CacheCount(relationNames []string, entityName string, count int64, conditions ...string) error {
countKey := assembleCountKey(entityName) countKey := AssembleCountKey(entityName, conditions...)
identification := assembleCountIdentification(conditions...) cacheInstance := &_CountRecord{Count: count}
cmd := global.RedisConn.B().Hset().Key(countKey).FieldValue().FieldValue(identification, strconv.FormatInt(count, 10)).Build() err := Cache(countKey, cacheInstance, 5*time.Minute)
result := global.RedisConn.Do(global.Ctx, cmd)
for _, relationName := range relationNames { for _, relationName := range relationNames {
CacheRelation(relationName, STORE_TYPE_HASH, countKey, identification) CacheRelation(relationName, STORE_TYPE_KEY, countKey)
} }
return result.Error() return err
} }
// 从缓存中获取模型名称明确的,包含指定条件的实体记录数量 // 从缓存中获取模型名称明确的,包含指定条件的实体记录数量
func RetreiveCount(entityName string, condtions ...string) (int64, error) { func RetrieveCount(entityName string, condtions ...string) (int64, error) {
countKey := assembleCountKey(entityName) countKey := AssembleCountKey(entityName, condtions...)
identification := assembleCountIdentification(condtions...) exist, err := Exists(countKey)
cmd := global.RedisConn.B().Hget().Key(countKey).Field(identification).Build()
result, err := global.RedisConn.Do(global.Ctx, cmd).AsInt64()
if err != nil { if err != nil {
return -1, err return -1, err
} }
return result, nil if !exist {
return -1, nil
}
instance, err := Retrieve[_CountRecord](countKey)
if instance != nil && err == nil {
return instance.Count, nil
} else {
return -1, err
}
} }
// 删除指定模型名称的数量缓存 // 删除指定模型名称的数量缓存
func AbolishCountEntity(entityName string) error { func AbolishCountEntity(entityName string) error {
countKey := assembleCountKey(entityName) pattern := fmt.Sprintf("%s:%s:*", TAG_COUNT, strings.ToUpper(entityName))
cmd := global.RedisConn.B().Del().Key(countKey).Build() return DeleteAll(pattern)
err := global.RedisConn.Do(global.Ctx, cmd).Error()
return err
} }

15
cache/entity.go vendored
View File

@@ -3,9 +3,10 @@ package cache
import ( import (
"fmt" "fmt"
"strings" "strings"
"time"
) )
func assembleEntityKey(entityName, id string) string { func AssembleEntityKey(entityName, id string) string {
var keys = make([]string, 0) var keys = make([]string, 0)
keys = append(keys, strings.ToUpper(entityName), id) keys = append(keys, strings.ToUpper(entityName), id)
var b strings.Builder var b strings.Builder
@@ -18,8 +19,8 @@ func assembleEntityKey(entityName, id string) string {
// 缓存模型名称明确的使用ID进行检索的实体内容。 // 缓存模型名称明确的使用ID进行检索的实体内容。
func CacheEntity[T any](instance T, relationNames []string, entityName, id string) error { func CacheEntity[T any](instance T, relationNames []string, entityName, id string) error {
entityKey := assembleEntityKey(entityName, id) entityKey := AssembleEntityKey(entityName, id)
err := Cache(entityKey, &instance, -1) err := Cache(entityKey, &instance, 5*time.Minute)
for _, relationName := range relationNames { for _, relationName := range relationNames {
CacheRelation(relationName, STORE_TYPE_KEY, entityKey) CacheRelation(relationName, STORE_TYPE_KEY, entityKey)
} }
@@ -27,15 +28,15 @@ func CacheEntity[T any](instance T, relationNames []string, entityName, id strin
} }
// 从缓存中取出模型名称明确的使用ID进行检索的实体内容。 // 从缓存中取出模型名称明确的使用ID进行检索的实体内容。
func RetreiveEntity[T any](entityName, id string) (*T, error) { func RetrieveEntity[T any](entityName, id string) (*T, error) {
entityKey := assembleEntityKey(entityName, id) entityKey := AssembleEntityKey(entityName, id)
instance, err := Retreive[T](entityKey) instance, err := Retrieve[T](entityKey)
return instance, err return instance, err
} }
// 精确的从缓存中删除指定模型名称、指定ID的实体内容。 // 精确的从缓存中删除指定模型名称、指定ID的实体内容。
func AbolishSpecificEntity(entityName, id string) (bool, error) { func AbolishSpecificEntity(entityName, id string) (bool, error) {
entityKey := assembleEntityKey(entityName, id) entityKey := AssembleEntityKey(entityName, id)
return Delete(entityKey) return Delete(entityKey)
} }

63
cache/exists.go vendored
View File

@@ -1,72 +1,49 @@
package cache package cache
import ( import (
"electricity_bill_calc/global"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/samber/lo"
) )
func assembleExistsKey(entityName string) string { func AssembleExistsKey(entityName string, additional ...string) string {
var keys = make([]string, 0) var keys = make([]string, 0)
keys = append(keys, strings.ToUpper(entityName)) keys = append(keys, strings.ToUpper(entityName))
return CacheKey(TAG_EXISTS, keys...) keys = append(keys, additional...)
} var b strings.Builder
b.WriteString(TAG_EXISTS)
func assembleExistsIdentification(additional ...string) string { for _, s := range keys {
return strings.Join(additional, ":") fmt.Fprintf(&b, ":%s", s)
}
return b.String()
} }
// 缓存模型名称明确的、包含指定ID以及一些附加条件的记录 // 缓存模型名称明确的、包含指定ID以及一些附加条件的记录
func CacheExists(relationNames []string, entityName string, conditions ...string) error { func CacheExists(relationNames []string, entityName string, conditions ...string) error {
existskey := assembleExistsKey(entityName) existskey := AssembleExistsKey(entityName, conditions...)
identification := assembleExistsIdentification(conditions...) err := Cache(existskey, lo.ToPtr(true), 5*time.Minute)
cmd := global.RedisConn.B().Sadd().Key(existskey).Member(identification).Build()
err := global.RedisConn.Do(global.Ctx, cmd).Error()
for _, relationName := range relationNames { for _, relationName := range relationNames {
CacheRelation(relationName, STORE_TYPE_SET, existskey, identification) CacheRelation(relationName, STORE_TYPE_KEY, existskey)
} }
return err return err
} }
// 从缓存中获取模型名称明确、包含指定ID以及一些附加条件的实体是否存在的标记函数在返回false时不保证数据库中相关记录也不存在 // 从缓存中获取模型名称明确、包含指定ID以及一些附加条件的实体是否存在的标记函数在返回false时不保证数据库中相关记录也不存在
func CheckExists(entityName string, condtions ...string) (bool, error) { func CheckExists(entityName string, condtions ...string) (bool, error) {
existsKey := assembleExistsKey(entityName) existsKey := AssembleExistsKey(entityName, condtions...)
identification := assembleExistsIdentification(condtions...) return Exists(existsKey)
cmd := global.RedisConn.B().Sismember().Key(existsKey).Member(identification).Build()
result, err := global.RedisConn.Do(global.Ctx, cmd).AsBool()
return result, err
} }
// 从缓存中删除模型名称明确、包含指定ID的全部实体存在标记 // 从缓存中删除模型名称明确、包含指定ID的全部实体存在标记
func AbolishExists(entityName, id string) error { func AbolishExists(entityName, id string) error {
existsKey := assembleExistsKey(entityName) pattern := fmt.Sprintf("%s:%s:%s:*", TAG_EXISTS, strings.ToUpper(entityName), id)
pattern := fmt.Sprintf("%s*", id) return DeleteAll(pattern)
var (
err error
cursor int64
elems = make([]string, 0)
sElem []string
)
for {
cmd := global.RedisConn.B().Sscan().Key(existsKey).Cursor(cursor).Match(pattern).Count(20).Build()
result := global.RedisConn.Do(global.Ctx, cmd)
cursor, sElem, err = dissembleScan(result)
if err != nil {
return err
}
elems = append(elems, sElem...)
if cursor == 0 {
break
}
}
cmd := global.RedisConn.B().Srem().Key(existsKey).Member(elems...).Build()
err = global.RedisConn.Do(global.Ctx, cmd).Error()
return err
} }
// 从缓存中删除指定模型名称的全部存在标记 // 从缓存中删除指定模型名称的全部存在标记
func AbolishExistsEntity(entityName string) error { func AbolishExistsEntity(entityName string) error {
existskey := assembleExistsKey(entityName) pattern := fmt.Sprintf("%s:%s:*", TAG_EXISTS, strings.ToUpper(entityName))
_, err := Delete(existskey) return DeleteAll(pattern)
return err
} }

77
cache/relation.go vendored
View File

@@ -2,6 +2,7 @@ package cache
import ( import (
"electricity_bill_calc/global" "electricity_bill_calc/global"
"fmt"
"strings" "strings"
"github.com/rueian/rueidis" "github.com/rueian/rueidis"
@@ -14,13 +15,13 @@ const (
STORE_TYPE_HASH = "HASH" STORE_TYPE_HASH = "HASH"
) )
func assembleRelationKey(relationName string) string { func AssembleRelationKey(relationName string) string {
var keys = make([]string, 0) var keys = make([]string, 0)
keys = append(keys, strings.ToUpper(relationName)) keys = append(keys, strings.ToUpper(relationName))
return CacheKey(TAG_RELATION, keys...) return CacheKey(TAG_RELATION, keys...)
} }
func assembleRelationIdentity(storeType, key string, field ...string) string { func AssembleRelationIdentity(storeType, key string, field ...string) string {
var identity = make([]string, 0) var identity = make([]string, 0)
identity = append(identity, storeType, key) identity = append(identity, storeType, key)
identity = append(identity, field...) identity = append(identity, field...)
@@ -29,18 +30,18 @@ func assembleRelationIdentity(storeType, key string, field ...string) string {
// 向缓存中保存与指定关联名称相关联的键的名称以及键的类型和子字段的组成。 // 向缓存中保存与指定关联名称相关联的键的名称以及键的类型和子字段的组成。
func CacheRelation(relationName, storeType, key string, field ...string) error { func CacheRelation(relationName, storeType, key string, field ...string) error {
relationKey := assembleRelationKey(relationName) relationKey := AssembleRelationKey(relationName)
relationIdentity := assembleRelationIdentity(storeType, key, field...) relationIdentity := AssembleRelationIdentity(storeType, key, field...)
cmd := global.RedisConn.B().Sadd().Key(relationKey).Member(relationIdentity).Build() cmd := global.Rd.B().Sadd().Key(relationKey).Member(relationIdentity).Build()
result := global.RedisConn.Do(global.Ctx, cmd) result := global.Rd.Do(global.Ctx, cmd)
return result.Error() return result.Error()
} }
// 从缓存中清理指定的关联键 // 从缓存中清理指定的关联键
func AbolishRelation(relationName string) error { func AbolishRelation(relationName string) error {
relationKey := assembleRelationKey(relationName) relationKey := AssembleRelationKey(relationName)
cmd := global.RedisConn.B().Smembers().Key(relationKey).Build() cmd := global.Rd.B().Smembers().Key(relationKey).Build()
relationItems, err := global.RedisConn.Do(global.Ctx, cmd).AsStrSlice() relationItems, err := global.Rd.Do(global.Ctx, cmd).AsStrSlice()
if err != nil { if err != nil {
return err return err
} }
@@ -49,17 +50,67 @@ func AbolishRelation(relationName string) error {
separated := strings.Split(item, ";") separated := strings.Split(item, ";")
switch separated[0] { switch separated[0] {
case STORE_TYPE_KEY: case STORE_TYPE_KEY:
cmd := global.RedisConn.B().Del().Key(separated[1]).Build() cmd := global.Rd.B().Del().Key(separated[1]).Build()
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
case STORE_TYPE_HASH: case STORE_TYPE_HASH:
cmd := global.RedisConn.B().Hdel().Key(separated[1]).Field(separated[2:]...).Build() cmd := global.Rd.B().Hdel().Key(separated[1]).Field(separated[2:]...).Build()
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
case STORE_TYPE_SET: case STORE_TYPE_SET:
cmd := global.RedisConn.B().Srem().Key(separated[1]).Member(separated[2:]...).Build() cmd := global.Rd.B().Srem().Key(separated[1]).Member(separated[2:]...).Build()
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
} }
} }
errs := global.RedisConn.DoMulti(global.Ctx, cmds...) errs := global.Rd.DoMulti(global.Ctx, cmds...)
firstErr, has := lo.Find(errs, func(elem rueidis.RedisResult) bool {
return elem.Error() != nil
})
if has {
return firstErr.Error()
} else {
return nil
}
}
func ClearOrphanRelationItems() error {
var (
err error
cursor uint64
keys = make([]string, 0)
sKeys []string
)
for {
scanCmd := global.Rd.B().Scan().Cursor(cursor).Match(fmt.Sprintf("%s:*", TAG_RELATION)).Count(20).Build()
results := global.Rd.Do(global.Ctx, scanCmd)
cursor, sKeys, err = dissembleScan(results)
if err != nil {
return err
}
keys = append(keys, sKeys...)
if cursor == 0 {
break
}
}
var cmds = make(rueidis.Commands, 0)
for _, key := range keys {
relationItemsCmd := global.Rd.B().Smembers().Key(key).Build()
results := global.Rd.Do(global.Ctx, relationItemsCmd)
relationItems, err := results.AsStrSlice()
if err != nil {
return err
}
for _, item := range relationItems {
separated := strings.Split(item, ";")
exist, err := Exists(separated[1])
if err != nil {
return err
}
if !exist {
cmd := global.Rd.B().Srem().Key(key).Member(item).Build()
cmds = append(cmds, cmd)
}
}
}
errs := global.Rd.DoMulti(global.Ctx, cmds...)
firstErr, has := lo.Find(errs, func(elem rueidis.RedisResult) bool { firstErr, has := lo.Find(errs, func(elem rueidis.RedisResult) bool {
return elem.Error() != nil return elem.Error() != nil
}) })

33
cache/repository.go vendored
View File

@@ -1,33 +0,0 @@
package cache
import (
"electricity_bill_calc/config"
)
func CacheData[T interface{}](instance T, category string, key ...string) error {
var keys = make([]string, 0)
keys = append(keys, category)
keys = append(keys, key...)
cacheKey := CacheKey("cache", keys...)
if exists, _ := Exists(cacheKey); exists {
Delete(cacheKey)
}
return Cache(cacheKey, &instance, config.ServiceSettings.CacheLifeTime)
}
func RetreiveData[T interface{}](category string, key ...string) (*T, error) {
var keys = make([]string, 0)
keys = append(keys, category)
keys = append(keys, key...)
return Retreive[T](CacheKey("cache", keys...))
}
func AbolishCacheData(category string, key ...string) {
var keys = make([]string, 0)
keys = append(keys, category)
keys = append(keys, key...)
cacheKey := CacheKey("cache", keys...)
if exists, _ := Exists(cacheKey); exists {
Delete(cacheKey)
}
}

65
cache/search.go vendored
View File

@@ -1,11 +1,17 @@
package cache package cache
import ( import (
"electricity_bill_calc/logger"
"fmt" "fmt"
"strings" "strings"
"time"
"go.uber.org/zap"
) )
func assembleSearchKey(entityName string, additional ...string) string { var log = logger.Named("Cache")
func AssembleSearchKey(entityName string, additional ...string) string {
var keys = make([]string, 0) var keys = make([]string, 0)
keys = append(keys, strings.ToUpper(entityName)) keys = append(keys, strings.ToUpper(entityName))
keys = append(keys, additional...) keys = append(keys, additional...)
@@ -19,8 +25,8 @@ func assembleSearchKey(entityName string, additional ...string) string {
// 缓存模型名称明确的使用或者包含非ID检索条件的实体内容。 // 缓存模型名称明确的使用或者包含非ID检索条件的实体内容。
func CacheSearch[T any](instance T, relationNames []string, entityName string, conditions ...string) error { func CacheSearch[T any](instance T, relationNames []string, entityName string, conditions ...string) error {
searchKey := assembleSearchKey(entityName, conditions...) searchKey := AssembleSearchKey(entityName, conditions...)
err := Cache(searchKey, &instance, -1) err := Cache(searchKey, &instance, 5*time.Minute)
for _, relationName := range relationNames { for _, relationName := range relationNames {
CacheRelation(relationName, STORE_TYPE_KEY, searchKey) CacheRelation(relationName, STORE_TYPE_KEY, searchKey)
} }
@@ -28,9 +34,9 @@ func CacheSearch[T any](instance T, relationNames []string, entityName string, c
} }
// 从缓存中取得模型名称明确的使用或者包含非ID检索条件的实体内容。 // 从缓存中取得模型名称明确的使用或者包含非ID检索条件的实体内容。
func RetreiveSearch[T any](entityName string, conditions ...string) (*T, error) { func RetrieveSearch[T any](entityName string, conditions ...string) (*T, error) {
searchKey := assembleSearchKey(entityName, conditions...) searchKey := AssembleSearchKey(entityName, conditions...)
instance, err := Retreive[T](searchKey) instance, err := Retrieve[T](searchKey)
return instance, err return instance, err
} }
@@ -39,3 +45,50 @@ func AbolishSearch(entityName string) error {
pattern := fmt.Sprintf("%s:%s:*", TAG_SEARCH, strings.ToUpper(entityName)) pattern := fmt.Sprintf("%s:%s:*", TAG_SEARCH, strings.ToUpper(entityName))
return DeleteAll(pattern) return DeleteAll(pattern)
} }
// 向缓存中保存指定模型名称的分页检索结果,会同时采用`CacheCount`中的方法保存检索结果的总数量。
func CachePagedSearch[T any](instance T, total int64, relationNames []string, entityName string, conditions ...string) error {
searchKey := AssembleSearchKey(entityName, conditions...)
countKey := AssembleCountKey(entityName, conditions...)
err := Cache(searchKey, &instance, 5*time.Minute)
if err != nil {
return err
}
cacheInstance := &_CountRecord{Count: total}
err = Cache(countKey, cacheInstance, 5*time.Minute)
for _, relationName := range relationNames {
CacheRelation(relationName, STORE_TYPE_KEY, searchKey)
CacheRelation(relationName, STORE_TYPE_KEY, countKey)
}
return err
}
// 从缓存中获取指定模型名称的分页检索结果,会同时采用`RetrieveCount`中的方法获取检索结果的总数量。
func RetrievePagedSearch[T any](entityName string, conditions ...string) (*T, int64, error) {
searchKey := AssembleSearchKey(entityName, conditions...)
countKey := AssembleCountKey(entityName, conditions...)
instance, err := Retrieve[T](searchKey)
if err != nil {
return nil, -1, err
}
count, err := Retrieve[_CountRecord](countKey)
if err != nil {
return nil, -1, err
}
if instance == nil || count == nil {
log.Warn("检索结果或者检索总数为空。", zap.String("searchKey", searchKey), zap.String("countKey", countKey))
return nil, -1, nil
}
return instance, count.Count, nil
}
// 从缓存中删除指定模型名称的分页检索结果,会同时采用`AbolishCountEntity`中的方法删除检索结果的总数量。
func AbolishPagedSearch(entityName string) error {
pattern := fmt.Sprintf("%s:%s:*", TAG_SEARCH, strings.ToUpper(entityName))
err := DeleteAll(pattern)
if err != nil {
return err
}
pattern = fmt.Sprintf("%s:%s:*", TAG_COUNT, strings.ToUpper(entityName))
return DeleteAll(pattern)
}

4
cache/session.go vendored
View File

@@ -21,9 +21,9 @@ func CacheSession(session *model.Session) error {
return Cache(key, session, config.ServiceSettings.MaxSessionLife) return Cache(key, session, config.ServiceSettings.MaxSessionLife)
} }
func RetreiveSession(token string) (*model.Session, error) { func RetrieveSession(token string) (*model.Session, error) {
key := SessionKey(token) key := SessionKey(token)
return Retreive[model.Session](key) return Retrieve[model.Session](key)
} }
func HasSession(token string) (bool, error) { func HasSession(token string) (bool, error) {

View File

@@ -31,8 +31,14 @@ type RedisSetting struct {
type ServiceSetting struct { type ServiceSetting struct {
MaxSessionLife time.Duration MaxSessionLife time.Duration
ItemsPageSize int ItemsPageSize uint
CacheLifeTime time.Duration CacheLifeTime time.Duration
HostSerial int64
}
// 读取基准线损率
type BaseLossSetting struct {
Base string
} }
// 定义全局变量 // 定义全局变量
@@ -41,6 +47,7 @@ var (
DatabaseSettings *DatabaseSetting DatabaseSettings *DatabaseSetting
RedisSettings *RedisSetting RedisSettings *RedisSetting
ServiceSettings *ServiceSetting ServiceSettings *ServiceSetting
BaseLoss *BaseLossSetting
) )
// 读取配置到全局变量 // 读取配置到全局变量
@@ -68,5 +75,10 @@ func SetupSetting() error {
if err != nil { if err != nil {
return err return err
} }
err = s.ReadSection("BaselineLineLossRatio", &BaseLoss)
if err != nil {
return err
}
return nil return nil
} }

View File

@@ -3,13 +3,17 @@ package controller
import ( import (
"electricity_bill_calc/exceptions" "electricity_bill_calc/exceptions"
"electricity_bill_calc/model" "electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"net/http"
"github.com/gin-gonic/gin" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
func _retreiveSession(c *gin.Context) (*model.Session, error) { func _retreiveSession(c *fiber.Ctx) (*model.Session, error) {
session, exists := c.Get("session") session := c.Locals("session")
if !exists { if session == nil {
return nil, exceptions.NewUnauthorizedError("用户会话不存在") return nil, exceptions.NewUnauthorizedError("用户会话不存在")
} }
userSession, ok := session.(*model.Session) userSession, ok := session.(*model.Session)
@@ -18,3 +22,26 @@ func _retreiveSession(c *gin.Context) (*model.Session, error) {
} }
return userSession, nil return userSession, nil
} }
// 检查当前用户是否拥有指定园区,在判断完成之后直接产生响应
func checkParkBelongs(parkId string, logger *zap.Logger, c *fiber.Ctx, result *response.Result) (bool, error) {
session := c.Locals("session")
if session == nil {
logger.Error("用户会话不存在。")
return false, result.Unauthorized("用户会话不存在。")
}
userSession, ok := session.(*model.Session)
if !ok {
return false, result.Unauthorized("用户会话格式不正确,需要重新登录")
}
ok, err := repository.ParkRepository.IsParkBelongs(parkId, userSession.Uid)
switch {
case err != nil:
logger.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", userSession.Uid), zap.Error(err))
return false, result.Error(http.StatusInternalServerError, err.Error())
case err == nil && !ok:
logger.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", userSession.Uid))
return false, result.Forbidden("您无权访问该园区。")
}
return true, nil
}

View File

@@ -1,94 +1,97 @@
package controller package controller
import ( import (
"electricity_bill_calc/logger"
"electricity_bill_calc/model" "electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/response" "electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service" "electricity_bill_calc/service"
"electricity_bill_calc/types"
"net/http" "net/http"
"strconv"
"time"
"github.com/gin-gonic/gin" "github.com/gofiber/fiber/v2"
"github.com/shopspring/decimal" "go.uber.org/zap"
) )
func InitializeChargesController(router *gin.Engine) { var chargeLog = logger.Named("Handler", "Charge")
router.GET("/charges", security.OPSAuthorize, listAllCharges)
router.POST("/charge", security.OPSAuthorize, recordNewCharge) func InitializeChargeHandlers(router *fiber.App) {
router.PUT("/charge/:uid/:seq", security.OPSAuthorize, modifyChargeState) router.Get("/charge", searchCharges)
router.Post("/charge", createNewUserChargeRecord)
router.Put("/charge/:uid/:seq", modifyUserChargeState)
} }
func listAllCharges(c *gin.Context) { // 检索用户的充值记录列表
func searchCharges(c *fiber.Ctx) error {
chargeLog.Info("检索用户的充值记录列表。")
result := response.NewResult(c) result := response.NewResult(c)
requestPage, err := strconv.Atoi(c.DefaultQuery("page", "1")) keyword := c.Query("keyword", "")
page := c.QueryInt("page", 1)
beginTime := types.ParseDateWithDefault(c.Query("begin"), types.NewEmptyDate())
endTime := types.ParseDateWithDefault(c.Query("end"), types.MaxDate())
charges, total, err := repository.ChargeRepository.FindCharges(uint(page), &beginTime, &endTime, &keyword)
if err != nil { if err != nil {
result.NotAccept("查询参数[page]格式不正确。") chargeLog.Error("检索用户的充值记录列表失败。", zap.Error(err))
return return result.Error(http.StatusInternalServerError, err.Error())
} }
requestKeyword := c.DefaultQuery("keyword", "") return result.Success(
requestBeginDate := c.DefaultQuery("begin", "") "已经获取到符合条件的计费记录。",
requestEndDate := c.DefaultQuery("end", "") response.NewPagedResponse(page, total).ToMap(),
charges, total, err := service.ChargeService.ListPagedChargeRecord(requestKeyword, requestBeginDate, requestEndDate, requestPage) fiber.Map{"records": charges},
if err != nil {
result.NotFound(err.Error())
return
}
result.Json(
http.StatusOK, "已获取到符合条件的计费记录。",
response.NewPagedResponse(requestPage, total).ToMap(),
gin.H{"records": charges},
) )
} }
type _NewChargeFormData struct { // 创建一条新的用户充值记录
UserId string `json:"userId" form:"userId"` func createNewUserChargeRecord(c *fiber.Ctx) error {
Fee decimal.NullDecimal `json:"fee" form:"fee"` chargeLog.Info("创建一条新的用户充值记录。")
Discount decimal.NullDecimal `json:"discount" form:"discount"`
Amount decimal.NullDecimal `json:"amount" form:"amount"`
ChargeTo time.Time `json:"chargeTo" form:"chargeTo" time_format:"simple_date" time_location:"shanghai"`
}
func recordNewCharge(c *gin.Context) {
result := response.NewResult(c) result := response.NewResult(c)
formData := new(_NewChargeFormData) createionForm := new(model.ChargeRecordCreationForm)
c.BindJSON(formData) if err := c.BodyParser(createionForm); err != nil {
currentTime := time.Now() chargeLog.Error("无法解析创建充值记录的请求数据。", zap.Error(err))
newRecord := &model.UserCharge{ return result.Error(http.StatusBadRequest, err.Error())
UserId: formData.UserId,
Fee: formData.Fee,
Discount: formData.Discount,
Amount: formData.Amount,
Settled: true,
SettledAt: &currentTime,
ChargeTo: formData.ChargeTo,
} }
err := service.ChargeService.CreateChargeRecord(newRecord, true) fee, _ := createionForm.Fee.Decimal.Float64()
discount, _ := createionForm.Discount.Decimal.Float64()
amount, _ := createionForm.Amount.Decimal.Float64()
ok, err := service.ChargeService.RecordUserCharge(
createionForm.UserId,
&fee,
&discount,
&amount,
createionForm.ChargeTo,
true,
)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) chargeLog.Error("创建用户充值记录失败。", zap.Error(err))
return return result.Error(http.StatusInternalServerError, err.Error())
}
if !ok {
chargeLog.Error("创建用户充值记录失败。")
return result.NotAccept("创建用户充值记录失败。")
} else {
return result.Success("创建用户充值记录成功, 指定用户的服务已延期。")
} }
result.Created("指定用户的服务已延期。")
} }
type _StateChangeFormData struct { // 改变用户充值记录的状态
Cancelled bool `json:"cancelled"` func modifyUserChargeState(c *fiber.Ctx) error {
} chargeLog.Info("改变用户充值记录的状态。")
func modifyChargeState(c *gin.Context) {
result := response.NewResult(c) result := response.NewResult(c)
formData := new(_StateChangeFormData) uid := c.Params("uid")
c.BindJSON(formData) seq, err := c.ParamsInt("seq")
requestUserID := c.Param("uid")
requestChargeSeq, err := strconv.Atoi(c.Param("seq"))
if err != nil { if err != nil {
result.Error(http.StatusNotAcceptable, "参数[记录流水号]解析错误。") chargeLog.Error("无法解析请求参数。", zap.Error(err))
return return result.Error(http.StatusBadRequest, err.Error())
} }
err = service.ChargeService.CancelCharge(int64(requestChargeSeq), requestUserID) ok, err := service.ChargeService.CancelUserCharge(uid, int64(seq))
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) chargeLog.Error("取消用户充值记录失败。", zap.Error(err))
return return result.Error(http.StatusInternalServerError, err.Error())
}
if !ok {
chargeLog.Error("取消用户充值记录失败。")
return result.NotAccept("取消用户充值记录失败。")
} else {
return result.Success("取消用户充值记录成功。")
} }
result.Updated("指定用户服务延期记录状态已经更新。")
} }

View File

@@ -1,215 +0,0 @@
package controller
import (
"electricity_bill_calc/excel"
"electricity_bill_calc/global"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
"github.com/shopspring/decimal"
)
func InitializeEndUserController(router *gin.Engine) {
router.GET("/report/:rid/submeter", security.EnterpriseAuthorize, fetchEndUserInReport)
router.GET("/report/:rid/meter/template", security.EnterpriseAuthorize, downloadEndUserRegisterTemplate)
router.POST("/report/:rid/meter/batch", security.EnterpriseAuthorize, uploadEndUserRegisterTemplate)
router.PUT("/report/:rid/submeter/:pid/:mid", security.EnterpriseAuthorize, modifyEndUserRegisterRecord)
}
func fetchEndUserInReport(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
keyword := c.DefaultQuery("keyword", "")
requestPage, err := strconv.Atoi(c.DefaultQuery("page", "1"))
if err != nil {
result.NotAccept("查询参数[page]格式不正确。")
return
}
endUsers, totalItem, err := service.EndUserService.SearchEndUserRecord(requestReportId, keyword, requestPage)
if err != nil {
result.NotFound(err.Error())
return
}
result.Json(
http.StatusOK,
"已获取到符合条件的终端用户集合",
response.NewPagedResponse(requestPage, totalItem).ToMap(),
gin.H{"meters": endUsers},
)
}
func downloadEndUserRegisterTemplate(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
users, err := service.EndUserService.AllEndUserRecord(requestReportId)
if err != nil {
result.NotFound(err.Error())
return
}
meterType, err := service.ReportService.RetreiveParkEndUserMeterType(requestReportId)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
if meterType == -1 {
result.NotFound("未能确定用户表计类型。")
return
}
c.Status(http.StatusOK)
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", "attachment; filename=抄表记录.xlsx")
gen := lo.Ternary[excel.ExcelTemplateGenerator](
meterType == 0,
excel.NewMeterNonPVExcelTemplateGenerator(),
excel.NewMeterPVExcelTemplateGenerator(),
)
defer gen.Close()
gen.WriteMeterData(users)
gen.WriteTo(c.Writer)
}
func uploadEndUserRegisterTemplate(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
meterType, err := service.ReportService.RetreiveParkEndUserMeterType(requestReportId)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
if meterType == -1 {
result.NotFound("未能确定用户表计类型。")
return
}
uploadedFile, err := c.FormFile("data")
if err != nil {
result.NotAccept("没有接收到上传的档案文件。")
return
}
archiveFile, err := uploadedFile.Open()
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
if meterType == 0 {
errs := service.EndUserService.BatchImportNonPVRegister(requestReportId, archiveFile)
if errs.Len() > 0 {
result.Json(http.StatusInternalServerError, "上传抄表文件存在解析错误", gin.H{"errors": errs.Errs})
return
}
} else {
errs := service.EndUserService.BatchImportPVRegister(requestReportId, archiveFile)
if errs.Len() > 0 {
result.Json(http.StatusInternalServerError, "上传抄表文件存在解析错误", gin.H{"errors": errs.Errs})
return
}
}
result.Json(http.StatusOK, "已经成功完成抄表记录的导入。", gin.H{"errors": make([]error, 0)})
}
type ModifyEndUserRegisterFormData struct {
CurrentPeriodOverall decimal.NullDecimal `json:"currentPeriodOverall" form:"currentPeriodOverall"`
CurrentPeriodCritical decimal.NullDecimal `json:"currentPeriodCritical" form:"currentPeriodCritical"`
CurrentPeriodPeak decimal.NullDecimal `json:"currentPeriodPeak" form:"currentPeriodPeak"`
CurrentPeriodValley decimal.NullDecimal `json:"currentPeriodValley" form:"currentPeriodValley"`
AdjustOverall decimal.NullDecimal `json:"adjustOverall" form:"adjustOverall"`
AdjustCritical decimal.NullDecimal `json:"adjustCritical" form:"adjustCritical"`
AdjustPeak decimal.NullDecimal `json:"adjustPeak" form:"adjustPeak"`
AdjustValley decimal.NullDecimal `json:"adjustValley" form:"adjustValley"`
}
func modifyEndUserRegisterRecord(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
meterType, err := service.ReportService.RetreiveParkEndUserMeterType(requestReportId)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
if meterType == -1 {
result.NotFound("未能确定用户表计类型。")
return
}
requestParkId := c.Param("pid")
requestMeterId := c.Param("mid")
formData := new(ModifyEndUserRegisterFormData)
c.BindJSON(formData)
meter, err := service.EndUserService.FetchSpecificEndUserRecord(requestReportId, requestParkId, requestMeterId)
if err != nil {
result.NotFound(err.Error())
return
}
if formData.CurrentPeriodOverall.Valid {
meter.CurrentPeriodOverall = formData.CurrentPeriodOverall.Decimal
}
if formData.CurrentPeriodCritical.Valid {
meter.CurrentPeriodCritical = formData.CurrentPeriodCritical.Decimal
}
if formData.CurrentPeriodPeak.Valid {
meter.CurrentPeriodPeak = formData.CurrentPeriodPeak.Decimal
}
if formData.CurrentPeriodValley.Valid {
meter.CurrentPeriodValley = formData.CurrentPeriodValley.Decimal
}
if formData.AdjustOverall.Valid {
meter.AdjustOverall = formData.AdjustOverall.Decimal
}
if formData.AdjustCritical.Valid {
meter.AdjustCritical = formData.AdjustCritical.Decimal
}
if formData.AdjustPeak.Valid {
meter.AdjustPeak = formData.AdjustPeak.Decimal
}
if formData.AdjustValley.Valid {
meter.AdjustValley = formData.AdjustValley.Decimal
}
valid, err := meter.Validate()
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
if !valid {
result.NotAccept("抄表数据合法性验证失败。")
return
}
tx := global.DBConn.NewSession()
if err = tx.Begin(); err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
defer tx.Close()
err = service.EndUserService.UpdateEndUserRegisterRecord(tx, *meter)
if err != nil {
tx.Rollback()
result.Error(http.StatusInternalServerError, err.Error())
return
}
err = tx.Commit()
if err != nil {
tx.Rollback()
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Success("指定终端用户抄表记录已经更新。")
}

24
controller/foundation.go Normal file
View File

@@ -0,0 +1,24 @@
package controller
import (
"electricity_bill_calc/config"
"electricity_bill_calc/logger"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"github.com/gofiber/fiber/v2"
)
var foundLog = logger.Named("Handler", "Foundation")
func InitializeFoundationHandlers(router *fiber.App) {
router.Get("/norm/authorized/loss/rate", security.MustAuthenticated, getNormAuthorizedLossRate)
}
func getNormAuthorizedLossRate(c *fiber.Ctx) error {
foundLog.Info("获取系统中定义的基准核定线损率")
result := response.NewResult(c)
BaseLoss := config.BaseLoss
return result.Success("已经获取到系统设置的基准核定线损率。",
fiber.Map{"normAuthorizedLossRate": BaseLoss.Base},
)
}

132
controller/god_mode.go Normal file
View File

@@ -0,0 +1,132 @@
package controller
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"errors"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"net/http"
)
var GmLog = logger.Named("Handler", "GM")
func InitializeGmController(router *fiber.App) {
router.Delete("/gm/tenement", security.SingularityAuthorize, deleteTenement)
router.Delete("/gm/park", security.SingularityAuthorize, deletePark)
router.Delete("/gm/report", security.SingularityAuthorize, deleteReports)
router.Delete("/gm/tenement/meter", security.SingularityAuthorize, deleteTenementMeterRelations)
router.Delete("/gm/enterprise", security.SingularityAuthorize, deleteEnterprise)
router.Delete("/gm/meter/pooling", security.SingularityAuthorize, deleteMeterPoolingRelations)
router.Delete("gm/meter", security.SingularityAuthorize, deleteMeters)
}
//用于将参数转化为切片
func getQueryValues(c *fiber.Ctx, paramName string) []string {
values := c.Request().URI().QueryArgs().PeekMulti(paramName)
result := make([]string, len(values))
for i, v := range values {
result[i] = string(v)
}
return result
}
func deleteTenement(c *fiber.Ctx) error {
park := c.Query("park", "")
tenements := getQueryValues(c, "tenements")
result := response.NewResult(c)
GmLog.Info("[天神模式]删除指定园区中的商户", zap.String("park", park), zap.Strings("tenements", tenements))
err := service.GMService.DeleteTenements(park, tenements)
if err != nil {
GmLog.Error("[天神模式]删除指定园区中的商户失败", zap.Error(err))
return result.Error(500, err.Error())
}
return result.Success("指定商户已经删除。")
}
func deletePark(c *fiber.Ctx) error {
parks := getQueryValues(c, "parks")
result := response.NewResult(c)
GmLog.Info("[天神模式]删除指定园区", zap.Strings("parks", parks))
if len(parks) < 0 {
GmLog.Info("[天神模式]用户未指派园区参数或者未指定需要删除的园区。")
return result.Error(http.StatusBadRequest, error.Error(errors.New("必须至少指定一个需要删除的园区!")))
}
err := service.GMService.DeleteParks(parks)
if err != nil {
GmLog.Error("[天神模式]删除指定园区失败", zap.Error(err))
return result.Error(500, err.Error())
}
return result.Success("指定园区已经删除。")
}
func deleteReports(c *fiber.Ctx) error {
pid := c.Query("park")
reports := getQueryValues(c, "reports")
result := response.NewResult(c)
GmLog.Info("[天神模式]删除符合条件的报表。", zap.Strings("reports", reports))
err := service.GMService.DeleteReports(pid, reports)
if err != nil {
GmLog.Error("[天神模式]删除指定园区中的报表失败。", zap.Error(err))
return result.Error(500, err.Error())
}
return result.Success("指定报表已经删除。")
}
func deleteEnterprise(c *fiber.Ctx) error {
uid := c.Query("uid")
result := response.NewResult(c)
GmLog.Info("[天神模式]删除指定企业用户", zap.String("uid", uid))
err := service.GMService.DeleteEnterprises(uid)
if err != nil {
GmLog.Error("[天神模式]删除指定企业用户失败", zap.Error(err))
return result.Error(500, "删除指定企业用户失败。")
}
return result.Success("指定企业用户已经删除。")
}
func deleteTenementMeterRelations(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Query("park")
tId := getQueryValues(c, "tenements")
metersId := getQueryValues(c, "meters")
GmLog.Info("删除指定园区中的商户与表计的关联关系", zap.String("park id", parkId))
if err := service.GMService.DeleteTenementMeterRelations(parkId, tId, metersId); err != nil {
meterLog.Error("无法删除指定园区中的商户与表计的关联关系", zap.Error(err))
return result.NotAccept(err.Error())
}
return result.Success("删除成功")
}
func deleteMeterPoolingRelations(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Query("park")
mId := getQueryValues(c, "meters")
GmLog.Info("[天神模式]删除指定园区中的表计公摊关系", zap.String("park id", parkId))
if err := service.GMService.DeleteMeterPooling(parkId, mId); err != nil {
meterLog.Error("[天神模式]删除指定园区中的表计公摊关系失败", zap.Error(err))
return result.Error(500, "删除指定园区中的表计公摊关系失败。")
}
return result.Success("指定表计公摊关系已经删除。")
}
func deleteMeters(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Query("park")
mId := getQueryValues(c, "meters")
GmLog.Info("[天神模式]删除指定园区中的表计", zap.String("park id", parkId))
if err := service.GMService.DeleteMeters(parkId, mId); err != nil {
meterLog.Error("[天神模式]删除指定园区中的表计失败", zap.Error(err))
return result.Error(500, "删除指定园区中的表计失败。")
}
return result.Success("指定表计已经删除。")
}

227
controller/invoice.go Normal file
View File

@@ -0,0 +1,227 @@
package controller
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"electricity_bill_calc/tools"
"electricity_bill_calc/types"
"electricity_bill_calc/vo"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
"go.uber.org/zap"
)
var invoiceLog = logger.Named("Controller", "Invoice")
func InitializeInvoiceHandler(router *fiber.App) {
router.Get("/invoice", security.MustAuthenticated, listInvoices)
router.Post("/invoice", security.EnterpriseAuthorize, createNewInvoiceRecord)
router.Post("/invoice/precalculate", security.EnterpriseAuthorize, testCalculateInvoice)
router.Get("/invoice/:code", security.EnterpriseAuthorize, getInvoiceDetail)
router.Delete("/invoice/:code", security.EnterpriseAuthorize, deleteInvoiceRecord)
router.Get("/uninvoiced/tenemennt/:tid/report", security.EnterpriseAuthorize, getUninvoicedTenementReports)
}
// 列出指定园区中的符合条件的发票记录
func listInvoices(c *fiber.Ctx) error {
invoiceLog.Info("列出指定园区中的符合条件的发票记录")
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
invoiceLog.Error("列出指定园区中的符合条件的发票记录失败,不能获取到有效的用户会话。", zap.Error(err))
return result.Unauthorized("未能获取到有效的用户会话。")
}
park := tools.EmptyToNil(c.Query("park"))
if session.Type == model.USER_TYPE_ENT && park != nil && len(*park) > 0 {
pass, err := checkParkBelongs(*park, invoiceLog, c, &result)
if err != nil || !pass {
return err
}
}
startDate, err := types.ParseDatep(c.Query("start_date"))
if err != nil {
invoiceLog.Error("列出指定园区中的符合条件的发票记录失败,开始日期参数解析错误。", zap.Error(err))
return result.BadRequest("开始日期参数解析错误。")
}
endDate, err := types.ParseDatep(c.Query("end_date"))
if err != nil {
invoiceLog.Error("列出指定园区中的符合条件的发票记录失败,结束日期参数解析错误。", zap.Error(err))
return result.BadRequest("结束日期参数解析错误。")
}
keyword := tools.EmptyToNil(c.Query("keyword"))
page := c.QueryInt("page", 1)
invoices, total, err := repository.InvoiceRepository.ListInvoice(park, startDate, endDate, keyword, uint(page))
if err != nil {
invoiceLog.Error("列出指定园区中的符合条件的发票记录失败,检索符合条件的发票记录出现错误。", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "检索符合条件的发票记录出现错误。")
}
invoiceResponse := make([]*vo.InvoiceResponse, 0)
copier.Copy(&invoiceResponse, &invoices)
return result.Success(
"已经获取到符合条件的发票列表。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{
"invoices": invoiceResponse,
},
)
}
// 获取指定发票的详细信息
func getInvoiceDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
invoiceNo := tools.EmptyToNil(c.Params("code"))
invoiceLog.Info("获取指定发票的详细信息", zap.Stringp("InvoiceNo", invoiceNo))
if invoiceNo == nil {
invoiceLog.Error("获取指定发票的详细信息失败,未指定发票编号。")
return result.BadRequest("未指定发票编号。")
}
session, err := _retreiveSession(c)
if err != nil {
invoiceLog.Error("获取指定发票的详细信息失败,不能获取到有效的用户会话。", zap.Error(err))
return result.Unauthorized("未能获取到有效的用户会话。")
}
pass, err := repository.InvoiceRepository.IsBelongsTo(*invoiceNo, session.Uid)
if err != nil {
invoiceLog.Error("获取指定发票的详细信息失败,检查发票所属权时出现错误。", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "检查发票所属权时出现错误。")
}
if !pass {
invoiceLog.Error("获取指定发票的详细信息失败,发票不属于当前用户。")
return result.Forbidden("不能访问不属于自己的发票。")
}
invoice, err := repository.InvoiceRepository.GetInvoiceDetail(*invoiceNo)
if err != nil {
invoiceLog.Error("获取指定发票的详细信息失败,检索发票信息时出现错误。", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "检索发票信息时出现错误。")
}
if invoice == nil {
invoiceLog.Error("获取指定发票的详细信息失败,指定发票不存在。")
return result.NotFound("指定发票不存在。")
}
var invoiceResponse vo.ExtendedInvoiceResponse
copier.Copy(&invoiceResponse, &invoice)
return result.Success(
"已经获取到指定发票的详细信息。",
fiber.Map{
"invoice": invoiceResponse,
},
)
}
// 获取指定商户下所有尚未开票的核算项目
func getUninvoicedTenementReports(c *fiber.Ctx) error {
result := response.NewResult(c)
tenement := tools.EmptyToNil(c.Params("tid"))
invoiceLog.Info("获取指定商户下所有尚未开票的核算项目", zap.Stringp("Tenement", tenement))
if tenement == nil {
invoiceLog.Error("获取指定商户下所有尚未开票的核算项目失败,未指定商户。")
return result.BadRequest("未指定商户。")
}
session, err := _retreiveSession(c)
if err != nil {
invoiceLog.Error("获取指定商户下所有尚未开票的核算项目失败,不能获取到有效的用户会话。", zap.Error(err))
return result.Unauthorized("未能获取到有效的用户会话。")
}
pass, err := repository.TenementRepository.IsTenementBelongs(*tenement, session.Uid)
if err != nil {
invoiceLog.Error("获取指定商户下所有尚未开票的核算项目失败,检查商户所属权时出现错误。", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "检查商户所属权时出现错误。")
}
if !pass {
invoiceLog.Error("获取指定商户下所有尚未开票的核算项目失败,商户不属于当前用户。")
return result.Forbidden("不能访问不属于自己的商户。")
}
reports, err := repository.InvoiceRepository.ListUninvoicedTenementCharges(*tenement)
if err != nil {
invoiceLog.Error("获取指定商户下所有尚未开票的核算项目失败,检索核算项目时出现错误。", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "检索核算项目时出现错误。")
}
return result.Success(
"已经获取到指定商户下所有尚未开票的核算项目。",
fiber.Map{
"records": reports,
},
)
}
// 试计算指定发票的票面信息
func testCalculateInvoice(c *fiber.Ctx) error {
result := response.NewResult(c)
var form vo.InvoiceCreationForm
if err := c.BodyParser(&form); err != nil {
invoiceLog.Error("试计算指定发票的票面信息失败,请求表单数据解析错误。", zap.Error(err))
return result.BadRequest("请求表单数据解析错误。")
}
invoiceLog.Info("试计算指定发票的票面信息", zap.String("Park", form.Park), zap.String("Tenement", form.Tenement))
if pass, err := checkParkBelongs(form.Park, invoiceLog, c, &result); err != nil || !pass {
return err
}
total, cargos, err := service.InvoiceService.TestCalculateInvoice(form.Park, form.Tenement, form.TaxMethod, form.TaxRate, form.Covers)
if err != nil {
invoiceLog.Error("试计算指定发票的票面信息失败,试计算发票时出现错误。", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "试计算发票时出现错误。")
}
return result.Success(
"已经计算出指定发票的票面信息。",
fiber.Map{
"total": total,
"cargos": cargos,
},
)
}
// 创建一个新的发票记录
func createNewInvoiceRecord(c *fiber.Ctx) error {
result := response.NewResult(c)
var form vo.ExtendedInvoiceCreationForm
if err := c.BodyParser(&form); err != nil {
invoiceLog.Error("创建一个新的发票记录失败,请求表单数据解析错误。", zap.Error(err))
return result.BadRequest("请求表单数据解析错误。")
}
invoiceLog.Info("创建一个新的发票记录", zap.String("Park", form.Park), zap.String("Tenement", form.Tenement))
if pass, err := checkParkBelongs(form.Park, invoiceLog, c, &result); err != nil || !pass {
return err
}
err := service.InvoiceService.SaveInvoice(form.Park, form.Tenement, form.InvoiceNo, form.InvoiceType, form.TaxMethod, form.TaxRate, form.Covers)
if err != nil {
invoiceLog.Error("创建一个新的发票记录失败,保存发票时出现错误。", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "保存发票时出现错误。")
}
return result.Created("已经创建了一个新的发票记录。")
}
// 删除指定的发票记录
func deleteInvoiceRecord(c *fiber.Ctx) error {
result := response.NewResult(c)
invoiceNo := tools.EmptyToNil(c.Params("code"))
invoiceLog.Info("删除指定的发票记录", zap.Stringp("InvoiceNo", invoiceNo))
if invoiceNo == nil {
invoiceLog.Error("删除指定的发票记录失败,未指定发票编号。")
return result.BadRequest("未指定发票编号。")
}
session, err := _retreiveSession(c)
if err != nil {
invoiceLog.Error("删除指定的发票记录失败,不能获取到有效的用户会话。", zap.Error(err))
return result.Unauthorized("未能获取到有效的用户会话。")
}
pass, err := repository.InvoiceRepository.IsBelongsTo(*invoiceNo, session.Uid)
if err != nil {
invoiceLog.Error("删除指定的发票记录失败,检查发票所属权时出现错误。", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "检查发票所属权时出现错误。")
}
if !pass {
invoiceLog.Error("删除指定的发票记录失败,发票不属于当前用户。")
return result.Forbidden("不能删除不属于自己的发票。")
}
err = service.InvoiceService.DeleteInvoice(*invoiceNo)
if err != nil {
invoiceLog.Error("删除指定的发票记录失败,删除发票时出现错误。", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "删除发票时出现错误。")
}
return result.Success("已经删除了指定的发票记录。")
}

View File

@@ -1,153 +0,0 @@
package controller
import (
"electricity_bill_calc/model"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"net/http"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"github.com/shopspring/decimal"
)
func InitializeMaintenanceFeeController(router *gin.Engine) {
router.GET("/maintenance/fee", security.EnterpriseAuthorize, listMaintenanceFees)
router.POST("/maintenance/fee", security.EnterpriseAuthorize, createMaintenanceFeeRecord)
router.PUT("/maintenance/fee/:mid", security.EnterpriseAuthorize, modifyMaintenanceFeeRecord)
router.PUT("/maintenance/fee/:mid/enabled", security.EnterpriseAuthorize, changeMaintenanceFeeState)
router.DELETE("/maintenance/fee/:mid", security.EnterpriseAuthorize, deleteMaintenanceFee)
}
func ensureMaintenanceFeeBelongs(c *gin.Context, result *response.Result, requestMaintenanceFeeId string) bool {
userSession, err := _retreiveSession(c)
if err != nil {
result.Unauthorized(err.Error())
return false
}
sure, err := service.MaintenanceFeeService.EnsureFeeBelongs(userSession.Uid, requestMaintenanceFeeId)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return false
}
if !sure {
result.Unauthorized("所操作维护费记录不属于当前用户。")
return false
}
return true
}
func listMaintenanceFees(c *gin.Context) {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
if err != nil {
result.Unauthorized(err.Error())
return
}
requestPark := c.DefaultQuery("park", "")
if len(requestPark) > 0 {
if !ensureParkBelongs(c, result, requestPark) {
return
}
fees, err := service.MaintenanceFeeService.ListMaintenanceFees([]string{requestPark})
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Json(http.StatusOK, "已获取指定园区下的维护费记录", gin.H{"fees": fees})
} else {
parkIds, err := service.ParkService.AllParkIds(userSession.Uid)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
fees, err := service.MaintenanceFeeService.ListMaintenanceFees(parkIds)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Json(http.StatusOK, "已获取指定用户下的所有维护费记录。", gin.H{"fees": fees})
}
}
type _FeeCreationFormData struct {
ParkId string `json:"parkId" form:"parkId"`
Name string `json:"name" form:"name"`
Fee decimal.Decimal `json:"fee" form:"fee"`
Memo *string `json:"memo" form:"memo"`
}
func createMaintenanceFeeRecord(c *gin.Context) {
result := response.NewResult(c)
formData := new(_FeeCreationFormData)
c.BindJSON(formData)
if !ensureParkBelongs(c, result, formData.ParkId) {
return
}
newMaintenanceFee := &model.MaintenanceFee{}
copier.Copy(newMaintenanceFee, formData)
err := service.MaintenanceFeeService.CreateMaintenanceFeeRecord(*newMaintenanceFee)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Created("新维护费记录已经创建。")
}
type _FeeModificationFormData struct {
Fee decimal.Decimal `json:"fee" form:"fee"`
Memo *string `json:"memo" form:"memo"`
}
func modifyMaintenanceFeeRecord(c *gin.Context) {
result := response.NewResult(c)
requestFee := c.Param("mid")
formData := new(_FeeModificationFormData)
c.BindJSON(formData)
if !ensureMaintenanceFeeBelongs(c, result, requestFee) {
return
}
newFeeState := new(model.MaintenanceFee)
copier.Copy(newFeeState, formData)
err := service.MaintenanceFeeService.ModifyMaintenanceFee(*newFeeState)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Updated("指定维护费条目已更新。")
}
type _FeeStateFormData struct {
Enabled bool `json:"enabled" form:"enabled"`
}
func changeMaintenanceFeeState(c *gin.Context) {
result := response.NewResult(c)
requestFee := c.Param("mid")
formData := new(_FeeStateFormData)
c.BindJSON(formData)
if !ensureMaintenanceFeeBelongs(c, result, requestFee) {
return
}
err := service.MaintenanceFeeService.ChangeMaintenanceFeeState(requestFee, formData.Enabled)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Updated("指定维护费条目状态已更新。")
}
func deleteMaintenanceFee(c *gin.Context) {
result := response.NewResult(c)
requestFee := c.Param("mid")
if !ensureMaintenanceFeeBelongs(c, result, requestFee) {
return
}
err := service.MaintenanceFeeService.DeleteMaintenanceFee(requestFee)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Deleted("指定维护费条目已删除。")
}

538
controller/meter.go Normal file
View File

@@ -0,0 +1,538 @@
package controller
import (
"electricity_bill_calc/excel"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"electricity_bill_calc/tools"
"electricity_bill_calc/types"
"electricity_bill_calc/vo"
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
"github.com/samber/lo"
"go.uber.org/zap"
"net/http"
)
var meterLog = logger.Named("Handler", "Meter")
func InitializeMeterHandlers(router *fiber.App) {
router.Get("/meter/choice", security.EnterpriseAuthorize, listUnboundMeters)
router.Get("/meter/choice/tenement", security.EnterpriseAuthorize, listUnboundTenementMeters)
router.Get("/meter/:pid", security.EnterpriseAuthorize, searchMetersWithinPark)
router.Post("/meter/:pid", security.EnterpriseAuthorize, createNewMeterManually)
router.Get("/meter/:pid/template", security.EnterpriseAuthorize, downloadMeterArchiveTemplate)
router.Post("/meter/:pid/batch", security.EnterpriseAuthorize, uploadMeterArchive)
router.Get("/meter/:pid/pooled", security.EnterpriseAuthorize, listPooledMeters)
router.Get("/meter/:pid/:code", security.EnterpriseAuthorize, retrieveSpecificMeterDetail)
router.Put("/meter/:pid/:code", security.EnterpriseAuthorize, updateMeterManually)
router.Patch("/meter/:pid/:code", security.EnterpriseAuthorize, replaceMeter)
router.Get("/meter/:pid/:code/binding", security.EnterpriseAuthorize, listAssociatedMeters)
router.Post("/meter/:pid/:code/binding", security.EnterpriseAuthorize, bindAssociatedMeters)
router.Delete("/meter/:pid/:code/binding/:slave", security.EnterpriseAuthorize, unbindAssociatedMeters)
router.Get("/reading/:pid", security.EnterpriseAuthorize, queryMeterReadings)
router.Put("/reading/:pid/:code/:reading", security.EnterpriseAuthorize, updateMeterReading)
router.Get("/reading/:pid/template", security.EnterpriseAuthorize, downloadMeterReadingsTemplate)
router.Post("/reading/:pid/batch", security.EnterpriseAuthorize, uploadMeterReadings)
router.Post("/reading/:pid/:code", security.EnterpriseAuthorize, recordMeterReading)
router.Get("/reading/:pid/choice", security.EnterpriseAuthorize, listReadableMeters)
}
// 查询指定园区下的表计信息
func searchMetersWithinPark(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterLog.Info("查询指定园区下的表计信息", zap.String("park id", parkId))
result := response.NewResult(c)
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
keyword := c.Query("keyword")
page := c.QueryInt("page", 1)
mtype := c.QueryInt("type", 3)
meters, total, err := repository.MeterRepository.MetersIn(parkId, uint(page), &keyword, mtype)
if err != nil {
meterLog.Error("无法查询指定园区下的表计信息,无法获取表计列表", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success(
"已经取得符合条件的0.4kV表计列表。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"meters": meters},
)
}
// 查询指定园区中指定表计的详细信息
func retrieveSpecificMeterDetail(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterId := c.Params("code")
meterLog.Info("查询指定园区中指定表计的详细信息", zap.String("park id", parkId), zap.String("meter id", meterId))
result := response.NewResult(c)
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
meter, err := repository.MeterRepository.FetchMeterDetail(parkId, meterId)
if err != nil {
meterLog.Error("无法查询指定园区中指定表计的详细信息,无法获取表计信息", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if meter == nil {
meterLog.Warn("无法查询指定园区中指定表计的详细信息,表计不存在")
return result.NotFound("指定的表计不存在。")
}
return result.Success("指定表计信息已经找到。", fiber.Map{"meter": meter})
}
// 手动添加一条0.4kV表计记录
func createNewMeterManually(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterLog.Info("手动添加一条0.4kV表计记录", zap.String("park id", parkId))
result := response.NewResult(c)
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
var creationForm vo.MeterCreationForm
if err := c.BodyParser(&creationForm); err != nil {
meterLog.Error("无法手动添加一条0.4kV表计记录,无法解析表计创建表单", zap.Error(err))
return result.NotAccept(err.Error())
}
if err := service.MeterService.CreateMeterRecord(parkId, &creationForm); err != nil {
meterLog.Error("无法手动添加一条0.4kV表计记录,无法创建表计记录", zap.Error(err))
return result.NotAccept(err.Error())
}
return result.Created("新0.4kV表计已经添加完成。")
}
// 手动更新一条新的0.4kV表计记录
func updateMeterManually(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterId := c.Params("code")
meterLog.Info("手动更新一条新的0.4kV表计记录", zap.String("park id", parkId), zap.String("meter id", meterId))
result := response.NewResult(c)
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
var updateForm vo.MeterModificationForm
if err := c.BodyParser(&updateForm); err != nil {
meterLog.Error("无法手动更新一条新的0.4kV表计记录,无法解析表计更新表单", zap.Error(err))
return result.NotAccept(err.Error())
}
if err := service.MeterService.UpdateMeterRecord(parkId, meterId, &updateForm); err != nil {
meterLog.Error("无法手动更新一条新的0.4kV表计记录,无法更新表计记录", zap.Error(err))
return result.NotAccept(err.Error())
}
return result.Updated("0.4kV表计已经更新完成。")
}
// 下载指定的园区表计登记模板
func downloadMeterArchiveTemplate(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterLog.Info("下载指定的园区表计登记模板", zap.String("park id", parkId))
result := response.NewResult(c)
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
parkDetail, err := repository.ParkRepository.RetrieveParkDetail(parkId)
if err != nil {
meterLog.Error("无法下载指定的园区表计登记模板,无法获取园区信息", zap.Error(err))
return result.NotFound(err.Error())
}
buildings, err := repository.ParkRepository.RetrieveParkBuildings(parkId)
if err != nil {
meterLog.Error("无法下载指定的园区表计登记模板,无法获取园区建筑列表", zap.Error(err))
return result.NotFound(fmt.Sprintf("无法获取园区建筑列表,%s", err.Error()))
}
if err != nil {
meterLog.Error("无法下载指定的园区表计登记模板,无法生成表计登记模板", zap.Error(err))
return result.NotFound(fmt.Sprintf("无法生成表计登记模板,%s", err.Error()))
}
templateGenerator := excel.NewMeterArchiveExcelTemplateGenerator()
defer templateGenerator.Close()
err = templateGenerator.WriteTemplateData(buildings)
if err != nil {
meterLog.Error("无法下载指定的园区表计登记模板,无法生成表计登记模板", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, fmt.Sprintf("无法生成表计登记模板,%s", err.Error()))
}
c.Status(fiber.StatusOK)
c.Set(fiber.HeaderContentType, fiber.MIMEOctetStream)
c.Set("Content-Transfer-Encoding", "binary")
c.Set(fiber.HeaderContentDisposition, fmt.Sprintf("attachment; filename=%s-表计登记模板.xlsx", parkDetail.Name))
templateGenerator.WriteTo(c.Response().BodyWriter())
return nil
}
// 从Excel文件中导入表计档案
func uploadMeterArchive(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
uploadFile, err := c.FormFile("data")
if err != nil {
meterLog.Error("无法从Excel文件中导入表计档案无法获取上传的文件", zap.Error(err))
return result.NotAccept(fmt.Sprintf("没有接收到上传的文件,%s", err.Error()))
}
errs, err := service.MeterService.BatchImportMeters(parkId, uploadFile)
if err != nil {
meterLog.Error("无法从Excel文件中导入表计档案无法导入表计档案", zap.Error(err))
return result.Json(fiber.StatusNotAcceptable, "上传的表计档案存在错误。", fiber.Map{"errors": errs})
}
return result.Success("表计档案已经导入完成。", fiber.Map{"errors": errs})
}
// 更换系统中的表计
func replaceMeter(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterId := c.Params("code")
meterLog.Info("更换系统中的表计", zap.String("park id", parkId), zap.String("meter id", meterId))
result := response.NewResult(c)
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
var replacementForm vo.MeterReplacingForm
if err := c.BodyParser(&replacementForm); err != nil {
meterLog.Error("无法更换系统中的表计,无法解析表计更换表单", zap.Error(err))
return result.NotAccept(err.Error())
}
err := service.MeterService.ReplaceMeter(parkId, meterId, &replacementForm.OldReading, replacementForm.NewMeter.Code, replacementForm.NewMeter.Ratio, &replacementForm.NewMeter.Reading)
if err != nil {
meterLog.Error("更换表计出错1111", zap.Error(err))
return result.Error(500, err.Error())
}
return result.Success("更换成功")
}
// 列出指定公摊表计下的所有关联表计
func listAssociatedMeters(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
meterId := c.Params("code")
meterLog.Info("列出指定公摊表计下的所有关联表计", zap.String("park id", parkId), zap.String("meter id", meterId))
meters, err := service.MeterService.ListPooledMeterRelations(parkId, meterId)
if err != nil {
meterLog.Error("无法列出指定公摊表计下的所有关联表计,无法获取关联表计列表", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("已经取得指定公摊表计下的所有关联表计列表。", fiber.Map{"meters": meters})
}
// 向指定表计绑定关联表计
func bindAssociatedMeters(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
meterId := c.Params("code")
meterLog.Info("向指定表计绑定关联表计", zap.String("park id", parkId), zap.String("meter id", meterId))
var meters = make([]string, 0)
if err := c.BodyParser(&meters); err != nil {
meterLog.Error("无法向指定表计绑定关联表计,无法解析关联表计列表", zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err := service.MeterService.BindMeter(parkId, meterId, meters)
if err != nil {
meterLog.Error("无法向指定表计绑定关联表计,无法绑定关联表计", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if !ok {
meterLog.Warn("无法向指定表计绑定关联表计,表计关联失败。")
return result.NotAccept("表计关联失败。")
}
return result.Created("已经向指定表计绑定关联表计。")
}
// 解除指定园区下两个表计之间的关联关系
func unbindAssociatedMeters(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
masterMeter := c.Params("code")
slaveMeter := c.Params("slave")
if len(masterMeter) == 0 || len(slaveMeter) == 0 {
meterLog.Warn("无法解除指定园区下两个表计之间的关联关系,表计编号为空。")
return result.NotAccept("存在未给定要操作的表计编号。")
}
ok, err := service.MeterService.UnbindMeter(parkId, masterMeter, []string{slaveMeter})
if err != nil {
meterLog.Error("无法解除指定园区下两个表计之间的关联关系,无法解除关联关系", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if !ok {
meterLog.Warn("无法解除指定园区下两个表计之间的关联关系,表计关联解除失败。")
return result.NotAccept("表计关联解除失败。")
}
return result.Created("已经解除指定园区下两个表计之间的关联关系。")
}
// 分页列出园区中的公摊表计
func listPooledMeters(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
page := c.QueryInt("page", 1)
keyword := c.Query("keyword")
meters, total, err := service.MeterService.SearchPooledMetersDetail(parkId, uint(page), &keyword)
if err != nil {
meterLog.Error("无法分页列出园区中的公摊表计,无法获取公摊表计列表", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success(
"已经取得符合条件的公摊表计列表。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"meters": meters},
)
}
// 列出指定园区中尚未绑定公摊表计的表计
func listUnboundMeters(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
meterLog.Error("无法列出指定园区中尚未绑定公摊表计的表计,无法获取当前用户会话", zap.Error(err))
return result.Unauthorized(err.Error())
}
parkId := tools.EmptyToNil(c.Query("park"))
if pass, err := checkParkBelongs(*parkId, meterLog, c, &result); !pass {
return err
}
keyword := c.Query("keyword")
limit := uint(c.QueryInt("limit", 6))
meters, err := repository.MeterRepository.ListUnboundMeters(session.Uid, parkId, &keyword, &limit)
if err != nil {
meterLog.Error("无法列出指定园区中尚未绑定公摊表计的表计,无法获取表计列表", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
var simplifiedMeters = make([]*vo.SimplifiedMeterQueryResponse, 0)
copier.Copy(&simplifiedMeters, &meters)
return result.Success("已经取得符合条件的表计列表。", fiber.Map{"meters": simplifiedMeters})
}
// 列出指定园区中尚未绑定商户的表计
func listUnboundTenementMeters(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
meterLog.Error("无法列出指定园区中尚未绑定商户的表计,无法获取当前用户会话", zap.Error(err))
return result.Unauthorized(err.Error())
}
parkId := c.Query("park")
if len(parkId) == 0 {
meterLog.Error("无法列出指定园区中尚未绑定商户的表计未指定要访问的园区ID")
return result.NotAccept("未指定要访问的园区。")
}
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
keyword := c.Query("keyword")
limit := uint(c.QueryInt("limit", 6))
meters, err := repository.MeterRepository.ListUnboundTenementMeters(session.Uid, &parkId, &keyword, &limit)
if err != nil {
meterLog.Error("无法列出指定园区中尚未绑定商户的表计,无法获取表计列表", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
var simplifiedMeters = make([]*vo.SimplifiedMeterQueryResponse, 0)
copier.Copy(&simplifiedMeters, &meters)
return result.Success("已经取得符合条件的表计列表。", fiber.Map{"meters": simplifiedMeters})
}
// 查询指定园区中的表计读数
func queryMeterReadings(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
mtype := c.QueryInt("type", 3)
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
keyword := tools.EmptyToNil(c.Query("keyword"))
page := c.QueryInt("page", 1)
building := tools.EmptyToNil(c.Query("building"))
start := c.Query("start_date")
var startDate *types.Date = nil
if len(start) > 0 {
if parsedDate, err := types.ParseDate(start); err != nil {
meterLog.Error("查询指定园区中的表计读数,无法解析开始日期", zap.Error(err))
} else {
startDate = &parsedDate
}
}
end := c.Query("end_date")
var endDate *types.Date = nil
if len(end) > 0 {
if parsedDate, err := types.ParseDate(end); err != nil {
meterLog.Error("查询指定园区中的表计读数,无法解析结束日期", zap.Error(err))
} else {
endDate = &parsedDate
}
}
readings, total, err := service.MeterService.SearchMeterReadings(parkId, building, startDate, endDate, uint(page), keyword, uint(mtype))
if err != nil {
meterLog.Error("查询指定园区中的表计读数,无法获取表计读数列表", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
convertedReadings := lo.Map(readings, func(element *model.DetailedMeterReading, _ int) vo.MeterReadingDetailResponse {
return vo.FromDetailedMeterReading(*element)
})
return result.Success(
"指定园区的表计读数已经列出。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"records": convertedReadings},
)
}
// 记录一条新的表计抄表记录
func recordMeterReading(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
meterCode := c.Params("code")
var readingForm vo.MeterReadingForm
if err := c.BodyParser(&readingForm); err != nil {
meterLog.Error("记录一条新的表计抄表记录,无法解析表计抄表表单", zap.Error(err))
return result.NotAccept(fmt.Sprintf("无法解析表计抄表表单,%s", err.Error()))
}
if !readingForm.Validate() {
meterLog.Warn("记录一条新的表计抄表记录,表计读数不能正常配平,尖、峰、谷电量和超过总电量。")
return result.NotAccept("表计读数不能正常配平,尖、峰、谷电量和超过总电量。")
}
err := service.MeterService.RecordReading(parkId, meterCode, &readingForm)
if err != nil {
meterLog.Error("记录一条新的表计抄表记录,无法记录表计抄表记录", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("表计抄表记录已经记录完成。")
}
// 更新指定园区中指定表计的抄表记录
func updateMeterReading(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
meterCode := c.Params("code")
readingAtMicro, err := c.ParamsInt("reading")
if err != nil {
meterLog.Error("更新一条新的表计抄表记录,无法解析抄表时间", zap.Error(err))
return result.NotAccept(fmt.Sprintf("无法解析抄表时间,%s", err.Error()))
}
readingAt := types.FromUnixMicro(int64(readingAtMicro))
var readingForm vo.MeterReadingForm
if err := c.BodyParser(&readingForm); err != nil {
meterLog.Error("更新一条新的表计抄表记录,无法解析表计抄表表单", zap.Error(err))
return result.NotAccept(fmt.Sprintf("无法解析表计抄表表单,%s", err.Error()))
}
ok, err := repository.MeterRepository.UpdateMeterReading(parkId, meterCode, readingAt, &readingForm)
if err != nil {
meterLog.Error("更新一条新的表计抄表记录,无法更新表计抄表记录", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if !ok {
meterLog.Warn("更新一条新的表计抄表记录,表计抄表更新失败。")
return result.NotAccept("表计抄表记录未能成功更新,可能指定抄表记录不存在。")
}
return result.Success("表计抄表记录已经更新完成。")
}
// 下载指定园区的表计抄表模板
func downloadMeterReadingsTemplate(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterLog.Info("下载指定的园区表计抄表模板", zap.String("park id", parkId))
result := response.NewResult(c)
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
parkDetail, err := repository.ParkRepository.RetrieveParkDetail(parkId)
if err != nil {
meterLog.Error("无法下载指定的园区表计登记模板,无法获取园区信息", zap.Error(err))
return result.NotFound(err.Error())
}
meterDocs, err := repository.MeterRepository.ListMeterDocForTemplate(parkId)
if err != nil {
meterLog.Error("无法下载指定的园区表计抄表模板,无法获取表计档案列表", zap.Error(err))
return result.NotFound(fmt.Sprintf("无法获取表计档案列表,%s", err.Error()))
}
if err != nil {
meterLog.Error("无法下载指定的园区表计登记模板,无法生成表计登记模板", zap.Error(err))
return result.NotFound(fmt.Sprintf("无法生成表计登记模板,%s", err.Error()))
}
templateGenerator := excel.NewMeterReadingsExcelTemplateGenerator()
defer templateGenerator.Close()
err = templateGenerator.WriteTemplateData(meterDocs)
if err != nil {
meterLog.Error("无法下载指定的园区表计抄表模板,无法生成表计抄表模板", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, fmt.Sprintf("无法生成表计抄表模板,%s", err.Error()))
}
c.Status(fiber.StatusOK)
c.Set(fiber.HeaderContentType, fiber.MIMEOctetStream)
c.Set("Content-Transfer-Encoding", "binary")
c.Set(fiber.HeaderContentDisposition, fmt.Sprintf("attachment; filename=%s-表计抄表模板.xlsx", parkDetail.Name))
templateGenerator.WriteTo(c.Response().BodyWriter())
return nil
}
// 处理上传的抄表记录文件
func uploadMeterReadings(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterLog.Info("从Excel文件中导入抄表档案", zap.String("park id", parkId))
result := response.NewResult(c)
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
uploadFile, err := c.FormFile("data")
if err != nil {
meterLog.Error("无法从Excel文件中导入抄表档案无法获取上传的文件", zap.Error(err))
return result.NotAccept(fmt.Sprintf("没有接收到上传的文件,%s", err.Error()))
}
errs, err := service.MeterService.BatchImportReadings(parkId, uploadFile)
if err != nil {
meterLog.Error("无法从Excel文件中导入抄表档案无法导入抄表档案", zap.Error(err))
return result.Json(fiber.StatusNotAcceptable, "上传的抄表档案存在错误。", fiber.Map{"errors": errs})
}
return result.Success("表计档案已经导入完成。", fiber.Map{"errors": errs})
}
// 列出可以作为抄表目标的表计,主要用于创建抄表记录时的下拉选择。
func listReadableMeters(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterLog.Info("列出可以作为抄表目标的表计", zap.String("parkId", parkId))
result := response.NewResult(c)
if len(parkId) == 0 {
meterLog.Error("无法列出指定园区中尚未绑定商户的表计未指定要访问的园区ID")
return result.NotAccept("未指定要访问的园区。")
}
if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass {
return err
}
limit := c.QueryInt("limit", 10)
keyword := tools.EmptyToNil(c.Query("keyword"))
meters, err := repository.MeterRepository.ListReadableMeters(parkId, keyword, uint(limit))
if err != nil {
meterLog.Error(
"无法列出可以作为抄表目标的表计,无法获取表计列表", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
var simplifiedMeters = make([]*vo.ReadableMeterQueryResponse, 0)
copier.Copy(&simplifiedMeters, &meters)
return result.Success(
"指定园区中的可作为抄表目标的表计已经列出。",
fiber.Map{"meters": simplifiedMeters},
)
}

View File

@@ -1,206 +0,0 @@
package controller
import (
"electricity_bill_calc/excel"
"electricity_bill_calc/model"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"fmt"
"log"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"github.com/samber/lo"
"github.com/shopspring/decimal"
)
func InitializeMeter04kVController(router *gin.Engine) {
router.GET("/park/:pid/meter/template", download04kvMeterArchiveTemplate)
router.GET("/park/:pid/meters", security.EnterpriseAuthorize, ListPaged04kVMeter)
router.GET("/park/:pid/meter/:code", security.EnterpriseAuthorize, fetch04kVMeterDetail)
router.POST("/park/:pid/meter", security.EnterpriseAuthorize, createSingle04kVMeter)
router.PUT("/park/:pid/meter/:code", security.EnterpriseAuthorize, modifySingle04kVMeter)
router.POST("/park/:pid/meter/batch", security.EnterpriseAuthorize, batchImport04kVMeterArchive)
}
func download04kvMeterArchiveTemplate(c *gin.Context) {
result := response.NewResult(c)
requestParkId := c.Param("pid")
// if !ensureParkBelongs(c, result, requestParkId) {
// return
// }
parkDetail, err := service.ParkService.FetchParkDetail(requestParkId)
if err != nil {
result.NotFound("未找到指定的园区信息。")
return
}
c.Status(http.StatusOK)
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Transfer-Encoding", "binary")
c.FileAttachment("./assets/meter_04kv_template.xlsx", fmt.Sprintf("%s-户表档案.xlsx", parkDetail.Name))
}
func ListPaged04kVMeter(c *gin.Context) {
result := response.NewResult(c)
requestParkId := c.Param("pid")
if !ensureParkBelongs(c, result, requestParkId) {
return
}
requestPage, err := strconv.Atoi(c.DefaultQuery("page", "1"))
if err != nil {
result.NotAccept("查询参数[page]格式不正确。")
return
}
requestKeyword := c.DefaultQuery("keyword", "")
meters, totalItem, err := service.Meter04kVService.ListMeterDetail(requestParkId, requestKeyword, requestPage)
if err != nil {
result.NotFound(err.Error())
return
}
result.Json(
http.StatusOK,
"已获取到符合条件的0.4kV表计集合。",
response.NewPagedResponse(requestPage, totalItem).ToMap(),
gin.H{"meters": meters},
)
}
func fetch04kVMeterDetail(c *gin.Context) {
result := response.NewResult(c)
requestParkId := c.Param("pid")
if !ensureParkBelongs(c, result, requestParkId) {
return
}
requestMeterCode := c.Param("code")
meter, err := service.Meter04kVService.Get04kVMeterDetail(requestParkId, requestMeterCode)
if err != nil {
result.NotFound(err.Error())
return
}
if meter == nil {
result.Json(http.StatusNotFound, "指定的表计信息未能找到。", gin.H{"meter": nil})
return
}
result.Json(http.StatusOK, "指定的表计信息已找到。", gin.H{"meter": meter})
}
type _MeterModificationFormData struct {
Address *string `json:"address" form:"address"`
CustomerName *string `json:"customerName" form:"customerName"`
ContactName *string `json:"contactName" form:"contactName"`
ContactPhone *string `json:"contactPhone" form:"contactPhone"`
Ratio decimal.Decimal `json:"ratio" form:"ratio"`
Seq int `json:"seq" form:"seq"`
IsPublicMeter bool `json:"isPublicMeter" form:"isPublicMeter"`
WillDilute bool `json:"willDilute" form:"willDilute"`
Enabled bool `json:"enabled" form:"enabled"`
}
type _MeterCreationFormData struct {
Code string `json:"code" form:"code"`
Address *string `json:"address" form:"address"`
CustomerName *string `json:"customerName" form:"customerName"`
ContactName *string `json:"contactName" form:"contactName"`
ContactPhone *string `json:"contactPhone" form:"contactPhone"`
Ratio decimal.Decimal `json:"ratio" form:"ratio"`
Seq int `json:"seq" form:"seq"`
IsPublicMeter bool `json:"isPublicMeter" form:"isPublicMeter"`
WillDilute bool `json:"willDilute" form:"willDilute"`
Enabled bool `json:"enabled" form:"enabled"`
}
func createSingle04kVMeter(c *gin.Context) {
result := response.NewResult(c)
requestParkId := c.Param("pid")
if !ensureParkBelongs(c, result, requestParkId) {
return
}
formData := new(_MeterCreationFormData)
c.BindJSON(formData)
log.Printf("[controller|debug] form: %+v", formData)
newMeter := new(model.Meter04KV)
copier.Copy(newMeter, formData)
newMeter.ParkId = requestParkId
log.Printf("[controller|debug] meter: %+v", newMeter)
err := service.Meter04kVService.CreateSingleMeter(*newMeter)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Created("新0.4kV表计已经添加完成。")
}
func modifySingle04kVMeter(c *gin.Context) {
result := response.NewResult(c)
requestParkId := c.Param("pid")
if !ensureParkBelongs(c, result, requestParkId) {
return
}
requestMeterCode := c.Param("code")
meterDetail, err := service.Meter04kVService.Get04kVMeterDetail(requestParkId, requestMeterCode)
if err != nil {
result.NotFound(err.Error())
return
}
if meterDetail == nil {
result.NotFound("指定表计的信息为找到,不能修改。")
return
}
formData := new(_MeterModificationFormData)
c.BindJSON(formData)
copier.Copy(meterDetail, formData)
err = service.Meter04kVService.UpdateSingleMeter(meterDetail)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Updated("指定0.4kV表计信息已经更新。")
}
func batchImport04kVMeterArchive(c *gin.Context) {
result := response.NewResult(c)
requestParkId := c.Param("pid")
if !ensureParkBelongs(c, result, requestParkId) {
return
}
uploadedFile, err := c.FormFile("data")
if err != nil {
result.NotAccept("没有接收到上传的档案文件。")
return
}
archiveFile, err := uploadedFile.Open()
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
analyzer, err := excel.NewMeterArchiveExcelAnalyzer(archiveFile)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
records, errs := analyzer.Analysis(*new(model.Meter04KV))
if len(errs) > 0 {
result.Json(http.StatusNotAcceptable, "上传的表计档案文件存在错误。", gin.H{"errors": errs})
return
}
mergedMeters := lo.Map(records, func(meter model.Meter04KV, index int) model.Meter04KV {
meter.ParkId = requestParkId
meter.Enabled = true
return meter
})
errs = service.Meter04kVService.DuplicateMeterCodeValidate(mergedMeters)
if len(errs) > 0 {
result.Json(http.StatusNotAcceptable, "上传的表计档案文件存在错误。", gin.H{"errors": errs})
return
}
err = service.Meter04kVService.BatchCreateMeter(mergedMeters)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Json(http.StatusOK, "上传的表计档案已经全部导入。", gin.H{"errors": make([]excel.ExcelAnalysisError, 0)})
}

View File

@@ -1,193 +1,331 @@
package controller package controller
import ( import (
"electricity_bill_calc/model" "electricity_bill_calc/logger"
"electricity_bill_calc/repository"
"electricity_bill_calc/response" "electricity_bill_calc/response"
"electricity_bill_calc/security" "electricity_bill_calc/security"
"electricity_bill_calc/service" "electricity_bill_calc/vo"
"electricity_bill_calc/tools"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gofiber/fiber/v2"
"github.com/google/uuid" "go.uber.org/zap"
"github.com/jinzhu/copier"
"github.com/shopspring/decimal"
) )
func InitializeParkController(router *gin.Engine) { var parkLog = logger.Named("Handler", "Park")
router.GET("/parks", security.EnterpriseAuthorize, listAllParksUnderSessionUser)
router.GET("/parks/:uid", security.MustAuthenticated, listAllParksUnderSpecificUser) func InitializeParkHandlers(router *fiber.App) {
router.POST("/park", security.EnterpriseAuthorize, createNewPark) router.Get("/park", security.EnterpriseAuthorize, listParksBelongsToCurrentUser)
router.PUT("/park/:pid", security.EnterpriseAuthorize, modifyPark) router.Post("/park", security.EnterpriseAuthorize, createPark)
router.GET("/park/:pid", security.EnterpriseAuthorize, fetchParkDetail) router.Get("/park/belongs/:uid", security.OPSAuthorize, listParksBelongsTo)
router.PUT("/park/:pid/enabled", security.EnterpriseAuthorize, changeParkEnableState) router.Get("/park/:pid", security.EnterpriseAuthorize, fetchParkDetail)
router.DELETE("/park/:pid", security.EnterpriseAuthorize, deleteSpecificPark) router.Put("/park/:pid", security.EnterpriseAuthorize, modifySpecificPark)
router.Delete("/park/:pid", security.EnterpriseAuthorize, deleteSpecificPark)
router.Put("/park/:pid/enabled", security.EnterpriseAuthorize, modifyParkEnabling)
router.Get("/park/:pid/building", security.EnterpriseAuthorize, listBuildingsBelongsToPark)
router.Post("/park/:pid/building", security.EnterpriseAuthorize, createBuildingInPark)
router.Put("/park/:pid/building/:bid", security.EnterpriseAuthorize, modifySpecificBuildingInPark)
router.Delete("/park/:pid/building/:bid", security.EnterpriseAuthorize, deletedParkBuilding)
router.Put("/park/:pid/building/:bid/enabled", security.EnterpriseAuthorize, modifyParkBuildingEnabling)
} }
func ensureParkBelongs(c *gin.Context, result *response.Result, requestParkId string) bool { // 列出隶属于当前用户的全部园区
userSession, err := _retreiveSession(c) func listParksBelongsToCurrentUser(c *fiber.Ctx) error {
if err != nil {
result.Unauthorized(err.Error())
return false
}
sure, err := service.ParkService.EnsurePark(userSession.Uid, requestParkId)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return false
}
if !sure {
result.Unauthorized("不能访问不属于自己的园区。")
return false
}
return true
}
func listAllParksUnderSessionUser(c *gin.Context) {
result := response.NewResult(c) result := response.NewResult(c)
userSession, err := _retreiveSession(c) session, err := _retreiveSession(c)
if err != nil { if err != nil {
result.Unauthorized(err.Error()) parkLog.Error("列出当前用的全部园区,无法获取当前用户的会话。")
return return result.Unauthorized(err.Error())
} }
parks, err := service.ParkService.ListAllParkBelongsTo(userSession.Uid) parkLog.Info("列出当前用户下的全部园区", zap.String("user id", session.Uid))
parks, err := repository.ParkRepository.ListAllParks(session.Uid)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) parkLog.Error("无法获取园区列表。", zap.String("user id", session.Uid))
return return result.Error(http.StatusInternalServerError, err.Error())
} }
result.Json(http.StatusOK, "已获取到指定用户下的园区", gin.H{"parks": parks}) return result.Success("已获取到指定用户下的园区", fiber.Map{"parks": parks})
} }
func listAllParksUnderSpecificUser(c *gin.Context) { // 列出隶属于指定用户的全部园区
func listParksBelongsTo(c *fiber.Ctx) error {
result := response.NewResult(c) result := response.NewResult(c)
requestUserId := c.Param("uid") userId := c.Params("uid")
parks, err := service.ParkService.ListAllParkBelongsTo(requestUserId) parkLog.Info("列出指定用户下的全部园区", zap.String("user id", userId))
parks, err := repository.ParkRepository.ListAllParks(userId)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) parkLog.Error("无法获取园区列表。", zap.String("user id", userId))
return return result.Error(http.StatusInternalServerError, err.Error())
} }
result.Json(http.StatusOK, "已获取到指定用户下的园区", gin.H{"parks": parks}) return result.Success("已获取到指定用户下的园区", fiber.Map{"parks": parks})
} }
type _ParkInfoFormData struct { // 获取指定园区的详细信息
Name string `json:"name" form:"name"` func fetchParkDetail(c *fiber.Ctx) error {
Region *string `json:"region" form:"region"`
Address *string `json:"address" form:"address"`
Contact *string `json:"contact" form:"contact"`
Phone *string `json:"phone" from:"phone"`
Area decimal.NullDecimal `json:"area" from:"area"`
Capacity decimal.NullDecimal `json:"capacity" from:"capacity"`
Tenement decimal.NullDecimal `json:"tenement" from:"tenement"`
Category int `json:"category" form:"category"`
Submeter int `json:"submeter" form:"submeter"`
}
func createNewPark(c *gin.Context) {
result := response.NewResult(c) result := response.NewResult(c)
userSession, err := _retreiveSession(c) parkId := c.Params("pid")
parkLog.Info("获取指定园区的详细信息", zap.String("park id", parkId))
park, err := repository.ParkRepository.RetrieveParkDetail(parkId)
if err != nil { if err != nil {
result.Unauthorized(err.Error()) parkLog.Error("无法获取园区信息。", zap.String("park id", parkId))
return return result.Error(http.StatusInternalServerError, err.Error())
} }
formData := new(_ParkInfoFormData) return result.Success("已获取到指定园区的详细信息", fiber.Map{"park": park})
c.BindJSON(formData)
newPark := new(model.Park)
copier.Copy(newPark, formData)
newPark.Id = uuid.New().String()
newPark.UserId = userSession.Uid
nameAbbr := tools.PinyinAbbr(newPark.Name)
newPark.Abbr = &nameAbbr
newPark.Enabled = true
err = service.ParkService.SaveNewPark(*newPark)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Created("新园区完成创建。")
} }
func modifyPark(c *gin.Context) { // 创建一个新的园区
func createPark(c *fiber.Ctx) error {
result := response.NewResult(c) result := response.NewResult(c)
userSession, err := _retreiveSession(c) session, err := _retreiveSession(c)
if err != nil { if err != nil {
result.Unauthorized(err.Error()) parkLog.Error("创建一个新的园区,无法获取当前用户的会话。")
return return result.Unauthorized(err.Error())
} }
requestParkId := c.Param("pid") parkLog.Info("创建一个新的园区", zap.String("user id", session.Uid))
formData := new(_ParkInfoFormData) creationForm := new(vo.ParkInformationForm)
c.BindJSON(formData) if err := c.BodyParser(creationForm); err != nil {
park, err := service.ParkService.FetchParkDetail(requestParkId) parkLog.Error("无法解析园区表单数据。", zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
park, err := creationForm.TryIntoPark()
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) parkLog.Error("无法将园区表单数据转换为园区对象。", zap.String("user id", session.Uid), zap.Error(err))
return return result.NotAccept(err.Error())
} }
if userSession.Uid != park.UserId { ok, err := repository.ParkRepository.CreatePark(session.Uid, park)
result.Unauthorized("不能修改不属于自己的园区。") switch {
return case err == nil && !ok:
parkLog.Error("无法创建新的园区。", zap.String("user id", session.Uid))
return result.NotAccept("无法创建新的园区。")
case err != nil:
parkLog.Error("无法创建新的园区。", zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
} }
copier.Copy(park, formData) return result.Created("已创建一个新的园区")
nameAbbr := tools.PinyinAbbr(formData.Name)
park.Abbr = &nameAbbr
err = service.ParkService.UpdateParkInfo(park)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Updated("指定园区资料已更新。")
} }
func fetchParkDetail(c *gin.Context) { // 修改指定园区的信息
func modifySpecificPark(c *fiber.Ctx) error {
result := response.NewResult(c) result := response.NewResult(c)
requestParkId := c.Param("pid") parkId := c.Params("pid")
if !ensureParkBelongs(c, result, requestParkId) { session, err := _retreiveSession(c)
return
}
park, err := service.ParkService.FetchParkDetail(requestParkId)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) parkLog.Error("修改指定园区的信息,无法获取当前用户的会话。")
return return result.Unauthorized(err.Error())
} }
result.Json(http.StatusOK, "已经获取到指定园区的信息。", gin.H{"park": park}) if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass {
return err
}
parkForm := new(vo.ParkInformationForm)
if err := c.BodyParser(parkForm); err != nil {
parkLog.Error("无法解析园区表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
park, err := parkForm.TryIntoPark()
if err != nil {
parkLog.Error("无法将园区表单数据转换为园区对象。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err := repository.ParkRepository.UpdatePark(parkId, park)
switch {
case err == nil && !ok:
parkLog.Error("无法更新园区信息。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法更新园区信息。")
case err != nil:
parkLog.Error("无法更新园区信息。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("已更新指定园区的详细信息")
} }
type _ParkStateFormData struct { // 修改指定园区的可用性
Enabled bool `json:"enabled" form:"enabled"` func modifyParkEnabling(c *fiber.Ctx) error {
}
func changeParkEnableState(c *gin.Context) {
result := response.NewResult(c) result := response.NewResult(c)
userSession, err := _retreiveSession(c) parkId := c.Params("pid")
session, err := _retreiveSession(c)
if err != nil { if err != nil {
result.Unauthorized(err.Error()) parkLog.Error("修改指定园区的可用性,无法获取当前用户的会话。")
return return result.Unauthorized(err.Error())
} }
requestParkId := c.Param("pid") if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass {
if !ensureParkBelongs(c, result, requestParkId) { return err
return
} }
formData := new(_ParkStateFormData) stateForm := new(vo.StateForm)
c.BindJSON(formData) if err := c.BodyParser(stateForm); err != nil {
err = service.ParkService.ChangeParkState(userSession.Uid, requestParkId, formData.Enabled) parkLog.Error("无法解析园区表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
if err != nil { return result.NotAccept(err.Error())
result.Error(http.StatusInternalServerError, err.Error())
return
} }
result.Updated("指定园区的可用性状态已成功更新。") ok, err := repository.ParkRepository.EnablingPark(parkId, stateForm.Enabled)
switch {
case err == nil && !ok:
parkLog.Error("无法更新园区可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法更新园区可用性。")
case err != nil:
parkLog.Error("无法更新园区可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("已更新指定园区的可用性。")
} }
func deleteSpecificPark(c *gin.Context) { // 删除指定的园区
func deleteSpecificPark(c *fiber.Ctx) error {
result := response.NewResult(c) result := response.NewResult(c)
userSession, err := _retreiveSession(c) parkId := c.Params("pid")
session, err := _retreiveSession(c)
if err != nil { if err != nil {
result.Unauthorized(err.Error()) parkLog.Error("删除指定的园区,无法获取当前用户的会话。")
return return result.Unauthorized(err.Error())
} }
requestParkId := c.Param("pid") if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass {
if !ensureParkBelongs(c, result, requestParkId) { return err
return
} }
err = service.ParkService.DeletePark(userSession.Uid, requestParkId) ok, err := repository.ParkRepository.DeletePark(parkId)
if err != nil { switch {
result.Error(http.StatusInternalServerError, err.Error()) case err == nil && !ok:
parkLog.Error("无法删除园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法删除园区。")
case err != nil:
parkLog.Error("无法删除园区。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
} }
result.Deleted("指定园区已成功删除。") return result.Success("已删除指定园区")
}
// 列出指定园区中已经登记的建筑
func listBuildingsBelongsToPark(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("列出指定园区中已经登记的建筑,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
switch {
case err != nil:
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
case err == nil && !ok:
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.Forbidden("您无权访问该园区。")
}
buildings, err := repository.ParkRepository.RetrieveParkBuildings(parkId)
if err != nil {
parkLog.Error("无法获取园区中的建筑列表。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("已获取到指定园区中的建筑列表", fiber.Map{"buildings": buildings})
}
// 在指定园区中创建一个新的建筑
func createBuildingInPark(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("在指定园区中创建一个新的建筑,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass {
return err
}
buildingForm := new(vo.ParkBuildingInformationForm)
if err := c.BodyParser(buildingForm); err != nil {
parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err := repository.ParkRepository.CreateParkBuilding(parkId, buildingForm.Name, &buildingForm.Floors)
switch {
case err == nil && !ok:
parkLog.Error("无法创建新的建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法创建新的建筑。")
case err != nil:
parkLog.Error("无法创建新的建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("已创建一个新的建筑")
}
// 修改指定园区中的指定建筑的信息
func modifySpecificBuildingInPark(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
buildingId := c.Params("bid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("修改指定园区中的指定建筑的信息,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass {
return err
}
buildingForm := new(vo.ParkBuildingInformationForm)
if err := c.BodyParser(buildingForm); err != nil {
parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err := repository.ParkRepository.ModifyParkBuilding(buildingId, parkId, buildingForm.Name, &buildingForm.Floors)
switch {
case err == nil && !ok:
parkLog.Error("无法更新建筑信息。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法更新建筑信息。")
case err != nil:
parkLog.Error("无法更新建筑信息。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("已更新指定建筑的信息")
}
// 修改指定园区中指定建筑的可用性
func modifyParkBuildingEnabling(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
buildingId := c.Params("bid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("修改指定园区中指定建筑的可用性,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass {
return err
}
stateForm := new(vo.StateForm)
if err := c.BodyParser(stateForm); err != nil {
parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err := repository.ParkRepository.EnablingParkBuilding(buildingId, parkId, stateForm.Enabled)
switch {
case err == nil && !ok:
parkLog.Error("无法更新建筑可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法更新建筑可用性。")
case err != nil:
parkLog.Error("无法更新建筑可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("已更新指定建筑的可用性")
}
// 删除指定园区中的指定建筑
func deletedParkBuilding(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
buildingId := c.Params("bid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("删除指定园区中的指定建筑,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass {
return err
}
ok, err := repository.ParkRepository.DeleteParkBuilding(buildingId, parkId)
switch {
case err == nil && !ok:
parkLog.Error("无法删除建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid))
return result.NotAccept("无法删除建筑。")
case err != nil:
parkLog.Error("无法删除建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Deleted("已删除指定的建筑")
} }

View File

@@ -1,44 +1,40 @@
package controller package controller
import ( import (
"electricity_bill_calc/repository"
"electricity_bill_calc/response" "electricity_bill_calc/response"
"electricity_bill_calc/service"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gofiber/fiber/v2"
) )
func InitializeRegionController(router *gin.Engine) { func InitializeRegionHandlers(router *fiber.App) {
router.GET("/region/:rid", fetchRegions) router.Get("/region/:rid", getSubRegions)
router.GET("/regions/:rid", fetchAllLeveledRegions) router.Get("/regions/:rid", getParentRegions)
} }
func fetchRegions(c *gin.Context) { func getSubRegions(c *fiber.Ctx) error {
result := response.NewResult(c) result := response.NewResult(c)
requestParentId := c.Param("rid") requestParentId := c.Params("rid")
regions, err := service.RegionService.FetchSubRegions(requestParentId) regions, err := repository.RegionRepository.FindSubRegions(requestParentId)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) return result.Error(http.StatusInternalServerError, err.Error())
return
} }
if len(regions) == 0 { if len(regions) == 0 {
result.Json(http.StatusNotFound, "未能获取到相关的行政区划。", gin.H{"regions": make([]string, 0)}) return result.Json(http.StatusNotFound, "未能获取到相关的行政区划。", fiber.Map{"regions": make([]string, 0)})
return
} }
result.Json(http.StatusOK, "已经获取到相关的行政区划。", gin.H{"regions": regions}) return result.Json(http.StatusOK, "已经获取到相关的行政区划。", fiber.Map{"regions": regions})
} }
func fetchAllLeveledRegions(c *gin.Context) { func getParentRegions(c *fiber.Ctx) error {
result := response.NewResult(c) result := response.NewResult(c)
requestRegionCode := c.Param("rid") requestRegionCode := c.Params("rid")
regions, err := service.RegionService.FetchAllParentRegions(requestRegionCode) regions, err := repository.RegionRepository.FindParentRegions(requestRegionCode)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) return result.Error(http.StatusInternalServerError, err.Error())
return
} }
if len(regions) == 0 { if len(regions) == 0 {
result.Json(http.StatusNotFound, "未能获取到相关的行政区划。", gin.H{"regions": make([]string, 0)}) return result.Json(http.StatusNotFound, "未能获取到相关的行政区划。", fiber.Map{"regions": make([]string, 0)})
return
} }
result.Json(http.StatusOK, "以及获取到相关的行政区划。", gin.H{"regions": regions}) return result.Json(http.StatusOK, "以及获取到相关的行政区划。", fiber.Map{"regions": regions})
} }

View File

@@ -1,478 +1,484 @@
package controller package controller
import ( import (
"electricity_bill_calc/exceptions" "electricity_bill_calc/logger"
"electricity_bill_calc/model" "electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/response" "electricity_bill_calc/response"
"electricity_bill_calc/security" "electricity_bill_calc/security"
"electricity_bill_calc/service" "electricity_bill_calc/service"
"electricity_bill_calc/tools" "electricity_bill_calc/tools"
"log" "electricity_bill_calc/types"
"net/http" "electricity_bill_calc/vo"
"strconv"
"time"
"github.com/fufuok/utils" "log"
"github.com/gin-gonic/gin" "strconv"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/shopspring/decimal" "go.uber.org/zap"
) )
func InitializeReportController(router *gin.Engine) { var reportLog = logger.Named("Handler", "Report")
router.GET("/reports/with/drafts", security.EnterpriseAuthorize, fetchNewestReportOfParkWithDraft)
router.POST("/park/:pid/report", security.EnterpriseAuthorize, initializeNewReport) func InitializeReportHandlers(router *fiber.App) {
router.GET("/report/:rid/step/state", security.EnterpriseAuthorize, fetchReportStepStates) router.Get("/reports", security.MustAuthenticated, reportComprehensiveSearch)
router.GET("/report/:rid/summary", security.EnterpriseAuthorize, fetchReportParkSummary) router.Post("/report", security.EnterpriseAuthorize, initNewReportCalculateTask)
router.PUT("/report/:rid/summary", security.EnterpriseAuthorize, fillReportSummary) router.Get("/report/draft", security.EnterpriseAuthorize, listDraftReportIndicies)
router.GET("/report/:rid/summary/calculate", security.EnterpriseAuthorize, testCalculateReportSummary) router.Post("/report/calcualte", security.EnterpriseAuthorize, testCalculateReportSummary)
router.POST("/report/:rid/summary/calculate", security.EnterpriseAuthorize, progressReportSummary) //TODO: 2023-07-20将calcualte错误请求改为正确的calculate请求
router.GET("/report/:rid/maintenance", security.EnterpriseAuthorize, fetchWillDilutedFees) router.Post("/report/calculate", security.EnterpriseAuthorize, testCalculateReportSummary)
router.POST("/report/:rid/maintenance", security.EnterpriseAuthorize, createTemporaryWillDilutedFee) router.Get("/report/calculate/status", security.EnterpriseAuthorize, listCalculateTaskStatus)
router.POST("/report/:rid/maintenance/import", security.EnterpriseAuthorize, importPredefinedMaintenanceFees) router.Get("/report/:rid", security.EnterpriseAuthorize, getReportDetail)
router.PUT("/report/:rid/maintenance/:mid", security.EnterpriseAuthorize, modifyWillDilutedFee) router.Put("/report/:rid", security.EnterpriseAuthorize, updateReportCalculateTask)
router.DELETE("/report/:rid/maintenance/:mid", security.EnterpriseAuthorize, deleteTemporaryWillDilutedFee) router.Post("/report/:rid/publish", security.EnterpriseAuthorize, publishReport)
router.PUT("/report/:rid/step/diluted/fees", security.EnterpriseAuthorize, progressReportWillDilutedFee) router.Put("/report/:rid/calculate", security.EnterpriseAuthorize, initiateCalculateTask)
router.PUT("/report/:rid/step/meter/register", security.EnterpriseAuthorize, progressEndUserRegister) router.Get("/report/:rid/publics", security.MustAuthenticated, listPublicMetersInReport)
router.POST("/report/:rid/publish", security.EnterpriseAuthorize, publishReport) router.Get("/report/:rid/summary", security.MustAuthenticated, getReportSummary)
router.GET("/reports", security.MustAuthenticated, searchReports) router.Get("/report/:rid/summary/filled", security.EnterpriseAuthorize, getParkFilledSummary)
router.GET("/report/:rid", security.MustAuthenticated, fetchReportPublicity) router.Get("/report/:rid/pooled", security.MustAuthenticated, listPooledMetersInReport)
router.POST("/report/:rid/calculate", security.EnterpriseAuthorize, calculateReport) router.Get("/report/:rid/pooled/:code/submeter", security.MustAuthenticated, listSubmetersInPooledMeter)
router.Get("/report/:rid/tenement", security.MustAuthenticated, listTenementsInReport)
router.Get("/report/:rid/tenement/:tid", security.MustAuthenticated, getTenementDetailInReport)
} }
func ensureReportBelongs(c *gin.Context, result *response.Result, requestReportId string) bool { // 检查指定报表是否属于当前用户
_, err := _retreiveSession(c) func checkReportBelongs(reportId string, log *zap.Logger, c *fiber.Ctx, result *response.Result) (bool, error) {
session, err := _retreiveSession(c)
if err != nil { if err != nil {
result.Unauthorized(err.Error()) log.Error("无法获取当前用户的会话信息", zap.Error(err))
return false return false, result.Unauthorized("无法获取当前用户的会话信息。")
} }
requestReport, err := service.ReportService.RetreiveReportIndex(requestReportId) ok, err := repository.ReportRepository.IsBelongsTo(reportId, session.Uid)
if err != nil { if err != nil {
result.NotFound(err.Error()) log.Error("无法检查核算报表的所有权", zap.Error(err))
return false return false, result.Error(fiber.StatusInternalServerError, "无法检查核算报表的所有权。")
} }
if requestReport == nil { if !ok {
result.NotFound("指定报表未能找到。") log.Error("核算报表不属于当前用户")
return false return false, result.Forbidden("核算报表不属于当前用户。")
} }
return ensureParkBelongs(c, result, requestReport.ParkId) return true, nil
} }
func fetchNewestReportOfParkWithDraft(c *gin.Context) { // 获取当前登录用户下所有园区的尚未发布的核算报表索引
result := response.NewResult(c) func listDraftReportIndicies(c *fiber.Ctx) error {
userSession, err := _retreiveSession(c)
if err != nil {
result.Unauthorized(err.Error())
return
}
parks, err := service.ReportService.FetchParksWithNewestReport(userSession.Uid)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Json(http.StatusOK, "已获取到指定用户下所有园区的最新报表记录。", gin.H{"parks": parks})
}
func initializeNewReport(c *gin.Context) {
result := response.NewResult(c)
requestParkId := c.Param("pid")
userSession, err := _retreiveSession(c)
if err != nil {
result.Unauthorized(err.Error())
return
}
if !ensureParkBelongs(c, result, requestParkId) {
return
}
requestPeriod := c.Query("period")
reportPeriod, err := time.Parse("2006-01", requestPeriod)
if err != nil {
result.NotAccept("提供的初始化期数格式不正确。")
return
}
valid, err := service.ReportService.IsNewPeriodValid(userSession.Uid, reportPeriod)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
if !valid {
result.NotAccept("只能初始化已发布报表下一个月份的新报表。")
return
}
newId, err := service.ReportService.InitializeNewReport(requestParkId, reportPeriod)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Created("新一期报表初始化成功。", gin.H{"reportId": newId})
}
func fetchReportStepStates(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
requestReport, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
result.NotFound(err.Error())
return
}
result.Json(http.StatusOK, "已经获取到指定报表的填写状态。", gin.H{"steps": requestReport.StepState})
}
func fetchReportParkSummary(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
summary, err := service.ReportService.RetreiveReportSummary(requestReportId)
if err != nil {
result.NotFound(err.Error())
return
}
if summary == nil {
result.NotFound("指定报表未能找到。")
return
}
result.Json(http.StatusOK, "已经获取到指定报表中的园区概况。", gin.H{"summary": summary})
}
type ReportSummaryFormData struct {
Overall decimal.Decimal `json:"overall" form:"overall"`
OverallFee decimal.Decimal `json:"overallFee" form:"overallFee"`
Critical decimal.Decimal `json:"critical" form:"critical"`
CriticalFee decimal.Decimal `json:"criticalFee" form:"criticalFee"`
Peak decimal.Decimal `json:"peak" form:"peak"`
PeakFee decimal.Decimal `json:"peakFee" form:"peakFee"`
Valley decimal.Decimal `json:"valley" form:"valley"`
ValleyFee decimal.Decimal `json:"valleyFee" form:"valleyFee"`
BasicFee decimal.Decimal `json:"basicFee" form:"basicFee"`
AdjustFee decimal.Decimal `json:"adjustFee" from:"adjustFee"`
}
func fillReportSummary(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
formData := new(ReportSummaryFormData)
c.BindJSON(formData)
originSummary, err := service.ReportService.RetreiveReportSummary(requestReportId)
if err != nil {
result.NotFound(err.Error())
return
}
copier.Copy(originSummary, formData)
err = service.ReportService.UpdateReportSummary(originSummary)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Updated("指定电费公示报表中的园区概况基本数据已经完成更新。")
}
func testCalculateReportSummary(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
summary, err := service.ReportService.RetreiveReportSummary(requestReportId)
if err != nil {
result.NotFound(err.Error())
return
}
summary.CalculatePrices()
calcResults := tools.ConvertStructToMap(summary)
result.Json(http.StatusOK, "已完成园区概况的试计算。", gin.H{"result": lo.PickByKeys(calcResults, []string{"overallPrice", "criticalPrice", "peakPrice", "flat", "flatFee", "flatPrice", "valleyPrice"})})
}
func progressReportSummary(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
err := service.ReportService.CalculateSummaryAndFinishStep(requestReportId)
if err != nil {
if nfErr, ok := err.(exceptions.NotFoundError); ok {
result.NotFound(nfErr.Error())
} else {
result.Error(http.StatusInternalServerError, err.Error())
}
return
}
result.Success("已经完成园区概况的计算,并可以进行到下一步骤。")
}
func fetchWillDilutedFees(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
fees, err := service.ReportService.FetchWillDulutedMaintenanceFees(requestReportId)
if err != nil {
result.NotFound(err.Error())
return
}
result.Json(http.StatusOK, "待摊薄费用已经获取到。", gin.H{"fees": fees})
}
type DilutedFeeCreationFormData struct {
ParkId string `json:"parkId" form:"parkId"`
Name string `json:"name" form:"name"`
Fee decimal.Decimal `json:"fee" form:"fee"`
Memo *string `json:"memo" form:"memo"`
}
func createTemporaryWillDilutedFee(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
formData := new(DilutedFeeCreationFormData)
c.BindJSON(formData)
report, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
result.NotFound(err.Error())
return
}
if formData.ParkId != report.ParkId {
result.NotAccept("选择的园区与公示报表所属的园区不一致。")
return
}
newWillDilutedFee := new(model.WillDilutedFee)
copier.Copy(newWillDilutedFee, formData)
newWillDilutedFee.ReportId = report.Id
err = service.ReportService.CreateTemporaryWillDilutedMaintenanceFee(*newWillDilutedFee)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Created("公示报表中所要使用的临时待摊薄费用已添加。")
}
func importPredefinedMaintenanceFees(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
report, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
result.NotFound(err.Error())
return
}
maintenanceFees, err := service.MaintenanceFeeService.ListMaintenanceFees([]string{report.ParkId})
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
log.Printf("[cotroller] [debug] fees: %+v", maintenanceFees)
enabledMaintenanceFees := lo.Filter(
maintenanceFees,
func(elem model.MaintenanceFee, index int) bool {
return elem.Enabled
},
)
log.Printf("[cotroller] [debug] fees: %+v", enabledMaintenanceFees)
if len(enabledMaintenanceFees) == 0 {
result.NotFound("没有找到可供导入的配电维护费记录。")
return
}
dilutedFees := lo.Map(
enabledMaintenanceFees,
func(elem model.MaintenanceFee, index int) model.WillDilutedFee {
fee := &model.WillDilutedFee{
Id: utils.UUIDString(),
ReportId: report.Id,
SourceId: lo.ToPtr(elem.Id),
Name: elem.Name,
Fee: elem.Fee,
Memo: elem.Memo,
}
return *fee
},
)
err = service.ReportService.BatchSaveMaintenanceFee(report.Id, dilutedFees)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Created("预定义的配电维护费已经导入。")
}
type DilutedFeeModificationFormData struct {
Name *string `json:"name,omitempty" form:"name"`
Fee decimal.Decimal `json:"fee" form:"fee"`
Memo *string `json:"memo,omitempty" form:"memo"`
}
func modifyWillDilutedFee(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
requestFeeId := c.Param("mid")
formData := new(DilutedFeeModificationFormData)
c.BindJSON(formData)
updateValues := tools.ConvertStructToMap(formData)
err := service.ReportService.UpdateMaintenanceFee(requestFeeId, updateValues)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Updated("指定待摊薄费用信息已经更新。")
}
func deleteTemporaryWillDilutedFee(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
requestFeeId := c.Param("mid")
err := service.ReportService.DeleteWillDilutedFee(requestFeeId)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Deleted("指定待摊薄费用信息已经删除。")
}
func progressReportWillDilutedFee(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
report, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
result.NotFound(err.Error())
return
}
err = service.ReportService.ProgressReportWillDilutedFee(*report)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Success("待摊薄费用编辑步骤已经完成。")
}
func progressEndUserRegister(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
report, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
result.NotFound(err.Error())
return
}
err = service.ReportService.ProgressReportRegisterEndUser(*report)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Success("终端用户抄表编辑步骤已经完成。")
}
func publishReport(c *gin.Context) {
result := response.NewResult(c)
requestReportId := c.Param("rid")
if !ensureReportBelongs(c, result, requestReportId) {
return
}
report, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
result.NotFound(err.Error())
return
}
err = service.ReportService.PublishReport(*report)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Success("指定的公示报表已经发布。")
}
func searchReports(c *gin.Context) {
result := response.NewResult(c) result := response.NewResult(c)
session, err := _retreiveSession(c) session, err := _retreiveSession(c)
if err != nil { if err != nil {
result.Unauthorized(err.Error()) reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return return result.Unauthorized("无法获取当前用户的会话信息。")
} }
requestUser := lo. reportLog.Info("检索指定用户下的未发布核算报表索引", zap.String("User", session.Uid))
If(session.Type == model.USER_TYPE_ENT, session.Uid). indicies, err := service.ReportService.ListDraftReportIndicies(session.Uid)
Else(c.DefaultQuery("user", ""))
requestPark := c.DefaultQuery("park", "")
if len(requestPark) > 0 && session.Type == model.USER_TYPE_ENT {
if !ensureParkBelongs(c, result, requestPark) {
return
}
}
requestPeriodString := c.DefaultQuery("period", "")
var requestPeriod *time.Time = nil
if len(requestPeriodString) > 0 {
parsedPeriod, err := time.Parse("2006-01", requestPeriodString)
if err != nil {
result.NotAccept("参数[period]的格式不正确。")
return
}
requestPeriod = lo.ToPtr(parsedPeriod)
}
requestKeyword := c.DefaultQuery("keyword", "")
requestPage, err := strconv.Atoi(c.DefaultQuery("page", "1"))
if err != nil { if err != nil {
result.NotAccept("查询参数[page]格式不正确。") reportLog.Error("无法获取当前用户的核算报表索引", zap.Error(err))
return return result.NotFound("当前用户下未找到核算报表索引。")
} }
records, totalItems, err := service.ReportService.SearchReport(requestUser, requestPark, requestKeyword, requestPeriod, requestPage) return result.Success(
if err != nil { "已经获取到指定用户的报表索引。",
result.NotFound(err.Error()) fiber.Map{"reports": indicies},
return
}
result.Success(
"已经取得符合条件的公示报表记录。",
response.NewPagedResponse(requestPage, totalItems).ToMap(),
gin.H{"reports": records},
) )
} }
func fetchReportPublicity(c *gin.Context) { // 初始化一个新的核算任务
func initNewReportCalculateTask(c *fiber.Ctx) error {
result := response.NewResult(c) result := response.NewResult(c)
requestReportId := c.Param("rid") session, err := _retreiveSession(c)
publicity, err := service.ReportService.AssembleReportPublicity(requestReportId)
if err != nil { if err != nil {
if nfErr, ok := err.(exceptions.NotFoundError); ok { reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
result.NotFound(nfErr.Error()) return result.Unauthorized("无法获取当前用户的会话信息。")
return
} else {
result.Error(http.StatusInternalServerError, err.Error())
return
}
} }
result.Success("已经取得指定公示报表的发布版本。", tools.ConvertStructToMap(publicity)) reportLog.Info("初始化指定用户的一个新核算任务", zap.String("User", session.Uid))
var form vo.ReportCreationForm
if err := c.BodyParser(&form); err != nil {
reportLog.Error("无法解析创建核算报表的请求数据。", zap.Error(err))
return result.BadRequest("无法解析创建核算报表的请求数据。")
}
if pass, err := checkParkBelongs(form.Park, reportLog, c, &result); !pass {
return err
}
ok, err := service.ReportService.CreateNewReport(&form)
if err != nil {
reportLog.Error("无法创建核算报表", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法创建核算报表。")
}
if !ok {
reportLog.Error("未能完成核算报表的保存。")
return result.NotAccept("未能完成核算报表的保存。")
}
return result.Success("已经成功创建核算报表。")
} }
func calculateReport(c *gin.Context) { // 更新指定的核算任务
func updateReportCalculateTask(c *fiber.Ctx) error {
result := response.NewResult(c) result := response.NewResult(c)
requestReportId := c.Param("rid") reportId := c.Params("rid")
if !ensureReportBelongs(c, result, requestReportId) { if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass {
return return err
} }
err := service.CalculateService.ComprehensivelyCalculateReport(requestReportId) var form vo.ReportModifyForm
if err := c.BodyParser(&form); err != nil {
reportLog.Error("无法解析更新核算报表的请求数据。", zap.Error(err))
return result.BadRequest("无法解析更新核算报表的请求数据。")
}
ok, err := service.ReportService.UpdateRepoet(reportId, &form)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) reportLog.Error("无法更新核算报表", zap.Error(err))
return return result.Error(fiber.StatusInternalServerError, "无法更新核算报表。")
} }
result.Success("指定公示报表中的数据已经计算完毕。") if !ok {
reportLog.Error("未能完成核算报表的更新。")
return result.NotAccept("未能完成核算报表的更新。")
}
return result.Success("已经成功更新核算报表。")
}
// 启动指定的核算任务
func initiateCalculateTask(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass {
return err
}
err := service.ReportService.DispatchReportCalculate(reportId)
if err != nil {
reportLog.Error("无法启动核算报表计算任务", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法启动核算报表计算任务。")
}
//开启核算任务
err = service.ReportService.ReportCalcuateDispatch(reportId)
if err != nil {
return result.Error(500, "核算任务启动失败"+err.Error())
}
return result.Success("已经成功启动核算报表计算任务。")
}
// 获取自己园区的已经填写的园区电量信息
func getParkFilledSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass {
return err
}
reportLog.Info("获取园区电量信息", zap.String("Report", reportId))
summary, err := repository.ReportRepository.RetrieveReportSummary(reportId)
if err != nil {
reportLog.Error("无法获取核算报表的园区电量信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表的园区电量信息。")
}
if summary == nil {
reportLog.Error("未找到核算报表的园区电量信息")
return result.NotFound("未找到核算报表的园区电量信息。")
}
var summaryResponse vo.SimplifiedReportSummary
copier.Copy(&summaryResponse, summary)
return result.Success(
"已经获取到核算报表的园区电量信息。",
fiber.Map{"summary": summaryResponse},
)
}
// 对提供的园区电量信息进行试计算,返回试计算结果
func testCalculateReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
reportLog.Info("试计算园区电量信息")
var form vo.TestCalculateForm
if err := c.BodyParser(&form); err != nil {
reportLog.Error("无法解析试计算核算报表的请求数据。", zap.Error(err))
return result.BadRequest("无法解析试计算核算报表的请求数据。")
}
return result.Success(
"电量电费试计算已经完成。",
fiber.Map{"summary": form.Calculate()},
)
}
// 获取指定园区中尚未发布的核算报表计算状态
func listCalculateTaskStatus(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return result.Unauthorized("无法获取当前用户的会话信息。")
}
status, err := repository.ReportRepository.GetReportTaskStatus(session.Uid)
if err != nil {
reportLog.Error("无法获取核算报表计算状态", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表计算状态。")
}
statusResponse := make([]*vo.ReportCalculateTaskStatusResponse, 0)
copier.Copy(&statusResponse, &status)
return result.Success(
"已经获取到核算报表计算状态。",
fiber.Map{"status": statusResponse},
)
}
// 获取指定报表的详细信息
func getReportDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
reportLog.Info("获取核算报表的详细信息", zap.String("Report", reportId))
user, park, report, err := service.ReportService.RetrieveReportIndexDetail(reportId)
if err != nil {
reportLog.Error("无法获取核算报表的详细信息", zap.Error(err))
return result.NotFound("无法获取核算报表的详细信息。")
}
return result.Success(
"已经获取到核算报表的详细信息。",
fiber.Map{
"detail": vo.NewReportDetailQueryResponse(user, park, report),
},
)
}
// 获取指定核算报表的总览信息
func getReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
report, err := repository.ReportRepository.RetrieveReportSummary(reportId)
if err != nil {
reportLog.Error("无法获取核算报表的总览信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表的总览信息。")
}
if report == nil {
reportLog.Error("未找到核算报表的总览信息")
return result.NotFound("未找到核算报表的总览信息。")
}
summaryResponse := vo.ParkSummaryResponse{
ReportId: report.ReportId,
OverallDisplay: vo.ConsumptionDisplay{
AmountStr: strconv.FormatFloat(report.Overall.Amount.InexactFloat64(), 'f', -1, 64),
FeeStr: strconv.FormatFloat(report.Overall.Fee.InexactFloat64(), 'f', -1, 64),
PriceStr: strconv.FormatFloat(report.Overall.Price.InexactFloat64(), 'f', -1, 64),
ProportionStr: strconv.FormatFloat(report.Overall.Proportion.InexactFloat64(), 'f', -1, 64),
},
Area: report.OverallArea,
BasicFee: report.BasicFee,
PooledBasicFeeByAmount: report.BasicPooledPriceConsumption.Decimal,
PooledBasicFeeByArea: report.BasicPooledPriceArea.Decimal,
AdjustFee: report.AdjustFee,
PooledAdjustFeeByAmount: report.AdjustPooledPriceConsumption.Decimal,
PooledAdjustFeeByArea: report.AdjustPooledPriceArea.Decimal,
Consumption: report.ConsumptionFee.Decimal,
//Loss: report.Loss.Decimal,
Loss: report.AuthorizeLoss.Amount,
//LossRate: report.LossFee.Decimal,
LossRate: report.AuthorizeLoss.Proportion,
}
//copier.Copy(&summaryResponse, report)
return result.Success(
"已经获取到核算报表的总览信息。",
fiber.Map{"summary": summaryResponse},
)
}
// 获取指定报表中分页的公共表计的核算摘要信息
func listPublicMetersInReport(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
reportLog.Info("获取核算报表中的公共表计信息", zap.String("Report", reportId))
page := c.QueryInt("page", 1)
keyword := tools.EmptyToNil(c.Query("keyword"))
meters, total, err := repository.ReportRepository.ListPublicMetersInReport(reportId, uint(page), keyword)
if err != nil {
reportLog.Error("无法获取核算报表中的公共表计信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的公共表计信息。")
}
var meterResponses []vo.Public
for _, meter := range meters {
meterResponse := vo.Public{
Address: meter.Address,
AdjustLoss: model.ConsumptionUnit{
Amount: meter.LossAdjust.Amount,
Fee: meter.LossAdjust.Fee,
Price: meter.LossAdjust.Price,
Proportion: meter.LossAdjust.Proportion,
},
Area: meter.Area.Decimal.String(),
AttachedAt: meter.AttachedAt,
Building: meter.Building,
BuildingName: meter.BuildingName,
Code: meter.ParkMeterID,
DetachedAt: meter.DetachedAt,
DisplayRatio: strconv.FormatFloat(meter.DisplayRatio, 'f', -1, 64),
Enabled: meter.Enabled,
OnFloor: meter.OnFloor,
Overall: model.ConsumptionUnit{
Amount: meter.Overall.Amount,
Fee: meter.Overall.Fee,
Price: meter.Overall.Price,
Proportion: meter.Overall.Proportion,
},
ParkID: meter.ParkID,
Ratio: meter.Ratio.String(),
Seq: meter.Seq,
Type: float64(meter.MeterType),
}
meterResponses = append(meterResponses, meterResponse)
}
return result.Success(
"已经获取到指定核算报表中的分页公共表计的核算信息。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"public": meterResponses},
)
}
// 获取指定报表中的分页的公摊表计的核算摘要信息
func listPooledMetersInReport(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
reportLog.Info("获取核算报表中的公摊表计信息", zap.String("Report", reportId))
page := c.QueryInt("page", 1)
keyword := tools.EmptyToNil(c.Query("keyword"))
meters, total, err := repository.ReportRepository.ListPooledMetersInReport(reportId, uint(page), keyword)
if err != nil {
reportLog.Error("无法获取核算报表中的公摊表计信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的公摊表计信息。")
}
meterResponse := lo.Map(meters, func(meter *model.ReportDetailedPooledConsumption, _ int) *vo.ReportPooledQueryResponse {
m := &vo.ReportPooledQueryResponse{}
m.FromReportDetailPooledConsumption(meter)
return m
})
return result.Success(
"已经获取到指定核算报表中的分页公摊表计的核算信息。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"pooled": meterResponse},
)
}
// 列出指定报表中指定公共表计下各个分摊表计的消耗数据
func listSubmetersInPooledMeter(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
meterId := c.Params("code")
if len(meterId) == 0 {
reportLog.Error("未提供公共表计的编号")
return result.BadRequest("未提供公共表计的编号。")
}
meters, err := repository.ReportRepository.ListPooledMeterDetailInReport(reportId, meterId)
if err != nil {
reportLog.Error("无法获取核算报表中的公共表计信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的公共表计信息。")
}
meterResponse := lo.Map(meters, func(meter *model.ReportDetailNestedMeterConsumption, _ int) *vo.ReportPooledQueryResponse {
m := &vo.ReportPooledQueryResponse{}
m.FromReportDetailNestedMeterConsumption(meter)
return m
})
return result.Success(
"已经获取到指定核算报表中的公共表计的核算信息。",
fiber.Map{"meters": meterResponse},
)
}
// 获取指定报表中分页的商户核算电量电费概要数据
func listTenementsInReport(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
page := c.QueryInt("page", 1)
keyword := tools.EmptyToNil(c.Query("keyword"))
tenements, total, err := repository.ReportRepository.ListTenementInReport(reportId, uint(page), keyword)
if err != nil {
reportLog.Error("无法获取核算报表中的商户信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的商户信息。")
}
tenementsResponse := lo.Map(tenements, func(tenement *model.ReportTenement, _ int) *vo.ReportTenementSummaryResponse {
t := &vo.ReportTenementSummaryResponse{}
t.FromReportTenement(tenement)
return t
})
return result.Success(
"已经获取到指定核算报表中的分页商户的核算信息。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"tenements": tenementsResponse},
)
}
// 获取指定报表中指定商户的详细核算信息
func getTenementDetailInReport(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
tenementId := c.Params("tid")
detail, err := repository.ReportRepository.GetTenementDetailInReport(reportId, tenementId)
if err != nil {
reportLog.Error("无法获取核算报表中的商户信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的商户信息。")
}
var detailResponse vo.ReportTenementDetailResponse
detailResponse.FromReportTenement(detail)
return result.Success(
"已经获取到指定核算报表中的商户的详细核算信息。",
fiber.Map{"detail": detailResponse},
)
}
// 发布指定的核算报表
func publishReport(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass {
return err
}
ok, err := repository.ReportRepository.PublishReport(reportId)
if err != nil {
reportLog.Error("无法发布核算报表", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "发布核算报表出错。")
}
if !ok {
reportLog.Error("未能完成核算报表的发布。")
return result.NotAccept("未能完成核算报表的发布。")
}
return result.Success("已经成功发布核算报表。")
}
// 对核算报表进行综合检索
func reportComprehensiveSearch(c *fiber.Ctx) error {
result := response.NewResult(c)
user := tools.EmptyToNil(c.Query("user"))
session, err := _retreiveSession(c)
if err != nil {
reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return result.Unauthorized("无法获取当前用户的会话信息。")
}
park := tools.EmptyToNil(c.Query("park"))
if session.Type == model.USER_TYPE_ENT && park != nil && len(*park) > 0 {
if pass, err := checkParkBelongs(*park, reportLog, c, &result); !pass {
return err
}
}
var requestUser *string
if session.Type == model.USER_TYPE_ENT {
requestUser = lo.ToPtr(tools.DefaultTo(user, session.Uid))
} else {
requestUser = user
}
page := c.QueryInt("page", 1)
keyword := tools.EmptyToNil(c.Query("keyword"))
startDate, err := types.ParseDatep(c.Query("period_start"))
if err != nil {
reportLog.Error("无法解析核算报表查询的开始日期", zap.Error(err))
return result.BadRequest("无法解析核算报表查询的开始日期。")
}
endDate, err := types.ParseDatep(c.Query("period_end"))
if err != nil {
reportLog.Error("无法解析核算报表查询的结束日期", zap.Error(err))
return result.BadRequest("无法解析核算报表查询的结束日期。")
}
reports, total, err := service.ReportService.QueryReports(requestUser, park, uint(page), keyword, startDate, endDate)
if err != nil {
reportLog.Error("无法查询核算报表", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法查询核算报表。")
}
return result.Success(
"已经获取到指定核算报表的分页信息。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"reports": reports},
)
} }

View File

@@ -1,40 +1,43 @@
package controller package controller
import ( import (
"electricity_bill_calc/logger"
"electricity_bill_calc/model" "electricity_bill_calc/model"
"electricity_bill_calc/response" "electricity_bill_calc/response"
"electricity_bill_calc/security" "electricity_bill_calc/security"
"electricity_bill_calc/service" "electricity_bill_calc/service"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"net/http" "net/http"
"github.com/gin-gonic/gin"
) )
func InitializeStatisticsController(router *gin.Engine) { var StatisticsWithdrawLog = logger.Named("Handler", "StatisticsWithdraw")
router.GET("/audits", security.OPSAuthorize, currentAuditAmount)
router.GET("/stat/reports", security.MustAuthenticated, statReports) func InitializeStatisticsController(router *fiber.App) {
router.Get("/audits", security.OPSAuthorize, currentAuditAmount)
router.Get("/stat/reports", security.OPSAuthorize, statReports)
} }
func currentAuditAmount(c *gin.Context) { //获取当前系统中待审核的内容数量
func currentAuditAmount(c *fiber.Ctx) error {
StatisticsWithdrawLog.Info("开始获取当前系统中待审核的内容数量")
result := response.NewResult(c) result := response.NewResult(c)
amount, err := service.WithdrawService.AuditWaits() amount, err := service.WithdrawService.AuditWaits()
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) StatisticsWithdrawLog.Error("获取当前系统中待审核的内容数量出错", zap.Error(err))
return return result.Error(http.StatusInternalServerError, err.Error())
} }
result.Json(http.StatusOK, "已经获取到指定的统计信息。", gin.H{
"amounts": map[string]int64{ return result.Success("已经获取到指定的统计信息",
"withdraw": amount, fiber.Map{"withdraw": amount})
},
})
} }
func statReports(c *gin.Context) { func statReports(c *fiber.Ctx) error {
result := response.NewResult(c) result := response.NewResult(c)
session, err := _retreiveSession(c) session, err := _retreiveSession(c)
if err != nil { if err != nil {
result.Unauthorized(err.Error()) return result.Unauthorized(err.Error())
return
} }
var ( var (
enterprises int64 = 0 enterprises int64 = 0
@@ -44,34 +47,36 @@ func statReports(c *gin.Context) {
if session.Type != 0 { if session.Type != 0 {
enterprises, err = service.StatisticsService.EnabledEnterprises() enterprises, err = service.StatisticsService.EnabledEnterprises()
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) StatisticsWithdrawLog.Error(err.Error())
return return result.Error(http.StatusInternalServerError, err.Error())
} }
parks, err = service.StatisticsService.EnabledParks() parks, err = service.StatisticsService.EnabledParks()
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) StatisticsWithdrawLog.Error(err.Error())
return return result.Error(http.StatusInternalServerError, err.Error())
} }
reports, err = service.StatisticsService.ParksNewestState() //TODO: 2023.07.26 报表数据库结构改变,此处逻辑复杂放在最后处理
reports, err = service.StatisticsService.ParkNewestState()
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) StatisticsWithdrawLog.Error(err.Error())
return return result.Error(http.StatusInternalServerError, err.Error())
} }
} else { } else {
parks, err = service.StatisticsService.EnabledParks(session.Uid) parks, err = service.StatisticsService.EnabledParks(session.Uid)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) StatisticsWithdrawLog.Error(err.Error())
return return result.Error(http.StatusInternalServerError, err.Error())
} }
reports, err = service.StatisticsService.ParksNewestState(session.Uid) //TODO: 2023.07.26 报表数据库结构改变,此处逻辑复杂放在最后处理
reports, err = service.StatisticsService.ParkNewestState(session.Uid)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) StatisticsWithdrawLog.Error(err.Error())
return return result.Error(http.StatusInternalServerError, err.Error())
} }
} }
result.Json(http.StatusOK, "已经完成园区报告的统计。", gin.H{ return result.Success("已经完成园区报告的统计。", fiber.Map{
"statistics": gin.H{ "statistics": fiber.Map{
"enterprises": enterprises, "enterprises": enterprises,
"parks": parks, "parks": parks,
"reports": reports, "reports": reports,

121
controller/sync.go Normal file
View File

@@ -0,0 +1,121 @@
package controller
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"electricity_bill_calc/tools"
"electricity_bill_calc/vo"
"fmt"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
var synchronizeLog = logger.Named("Handler", "Synchronize")
func InitializeSynchronizeHandlers(router *fiber.App) {
router.Get("/synchronize/task", security.EnterpriseAuthorize, searchSynchronizeSchedules)
router.Get("/synchronize/configuration", security.EnterpriseAuthorize, getSynchronizeConfiguration)
router.Post("/synchronize/configuration", security.EnterpriseAuthorize, recordsynchronizeConfiguration)
}
// 查询当前平台中符合查询条件的同步任务企业用户无论传入什么用户ID条件都仅能看到自己的同步任务
func searchSynchronizeSchedules(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
synchronizeLog.Error("查询同步任务失败,未能获取当前用户会话信息", zap.Error(err))
return result.Unauthorized("未能获取当前用户会话信息。")
}
parkId := tools.EmptyToNil(c.Params("park"))
if parkId != nil && len(*parkId) > 0 {
if pass, err := checkParkBelongs(*parkId, reportLog, c, &result); !pass {
return err
}
}
userId := tools.EmptyToNil(c.Params("user"))
keyword := tools.EmptyToNil(c.Query("keyword"))
page := c.QueryInt("page", 1)
synchronizeLog.Info("查询当前平台中符合查询条件的同步任务。", zap.String("Ent", session.Uid), zap.Stringp("Park", parkId))
schedules, total, err := repository.SynchronizeRepository.SearchSynchronizeSchedules(userId, parkId, uint(page), keyword)
if err != nil {
reportLog.Error("无法获取同步任务", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取同步任务")
}
return result.Success(
" ",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"tasks": schedules},
)
}
// 获取指定的同步任务配置
func getSynchronizeConfiguration(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Query("park")
userId := c.Query("user")
session, err := _retreiveSession(c)
if err != nil {
reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return result.Unauthorized("无法获取当前用户的会话信息。")
}
var user_id string
if session.Type == model.USER_TYPE_ENT {
user_id = session.Uid
} else {
if userId != "" {
user_id = userId
} else {
return result.NotAccept(fmt.Sprintf("必须指定要记录同步任务的用户,%s", err.Error()))
}
}
fmt.Println("pppppppppppppppppppppppppppp", parkId, len(parkId))
if parkId == "" {
return result.NotAccept("必须指定要获取同步任务的园区。")
}
fmt.Printf(user_id)
configurations, err := repository.SynchronizeRepository.RetrieveSynchronizeConfiguration(user_id, parkId)
if err != nil {
reportLog.Error("无法获取同步任务", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取同步任务")
}
return result.Success(
" 123",
fiber.Map{"setup": configurations},
)
}
func recordsynchronizeConfiguration(c *fiber.Ctx) error {
userId := c.Query("user")
synchronizeLog.Info("记录一个新的同步任务配置", zap.String("user id", userId))
session, err := _retreiveSession(c)
result := response.NewResult(c)
if err != nil {
reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return result.Unauthorized("无法获取当前用户的会话信息。")
}
var Form vo.SynchronizeConfigurationCreateForm
if err := c.BodyParser(&Form); err != nil {
meterLog.Error("无法更新同步配置,无法解析表计更新表单", zap.Error(err))
return result.NotAccept(err.Error())
}
var user_id string
if session.Type == model.USER_TYPE_ENT {
user_id = session.Uid
} else {
if userId != "" {
user_id = userId
} else {
return result.NotAccept(fmt.Sprintf("必须指定更新同步任务的用户,%s", err.Error()))
}
}
//configurations, err := repository.SynchronizeRepository.CreateSynchronizeConfiguration
if err := service.SynchronizeService.CreateSynchronizeConfiguration(user_id, &Form); err != nil {
synchronizeLog.Error("无法更新同步配置", zap.Error(err))
return result.NotAccept(err.Error())
}
return result.Success("更新完成。")
}

288
controller/tenement.go Normal file
View File

@@ -0,0 +1,288 @@
package controller
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"electricity_bill_calc/tools"
"electricity_bill_calc/types"
"electricity_bill_calc/vo"
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
"github.com/samber/lo"
"go.uber.org/zap"
)
var tenementLog = logger.Named("Handler", "Tenement")
func InitializeTenementHandler(router *fiber.App) {
router.Get("/tenement/choice", security.EnterpriseAuthorize, listTenementForChoice)
router.Get("/tenement/:pid", security.EnterpriseAuthorize, listTenement)
router.Put("/tenement/:pid/:tid", security.EnterpriseAuthorize, updateTenement)
router.Get("/tenement/:pid/:tid", security.EnterpriseAuthorize, getTenementDetail)
router.Get("/tenement/:pid/:tid/meter", security.EnterpriseAuthorize, listMeters)
//TODO: 2023-07-19再apiFox上该请求是个PUT请求后端接收是个POST请求不知道是否有误或是缺少对应请求apiFox测试请求返回值为405[完成]
router.Put("/tenement/:pid/:tid/move/out", security.EnterpriseAuthorize, moveOutTenement)
router.Post("/tenement/:pid", security.EnterpriseAuthorize, addTenement)
router.Post("/tenement/:pid/:tid/binding", security.EnterpriseAuthorize, bindMeterToTenement)
//TODO: 2023-07-19再apiFox上该请求是个PUT请求后端接收是个POST请求不知道是否有误或是缺少对应请求apiFox测试请求返回值为405[完成]
router.Put("/tenement/:pid/:tid/binding/:code/unbind", security.EnterpriseAuthorize, unbindMeterFromTenement)
}
// 列出园区中的商户
func listTenement(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
tenementLog.Info("列出园区中的商户", zap.String("Park", parkId))
if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass {
return err
}
page := c.QueryInt("page", 1)
keyword := tools.EmptyToNil(c.Query("keyword"))
building := tools.EmptyToNil(c.Query("building"))
startDate, err := types.ParseDatep(c.Query("startDate"))
if err != nil {
tenementLog.Error("列出园区中的商户失败,未能解析查询开始日期", zap.Error(err))
return result.BadRequest(err.Error())
}
endDate, err := types.ParseDatep(c.Query("endDate"))
if err != nil {
tenementLog.Error("列出园区中的商户失败,未能解析查询结束日期", zap.Error(err))
return result.BadRequest(err.Error())
}
state := c.QueryInt("state", 0)
tenements, total, err := repository.TenementRepository.ListTenements(parkId, uint(page), keyword, building, startDate, endDate, state)
if err != nil {
tenementLog.Error("列出园区中的商户失败,未能获取商户列表", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, err.Error())
}
tenementsResponse := make([]*vo.TenementQueryResponse, 0)
copier.Copy(&tenementsResponse, &tenements)
return result.Success(
"已经获取到要查询的商户。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{
"tenements": tenementsResponse,
},
)
}
// 列出指定商户下所有的表计
func listMeters(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass {
return err
}
tenementId := c.Params("tid")
tenementLog.Info("列出指定商户下所有的表计", zap.String("Park", parkId), zap.String("Tenement", tenementId))
meters, err := service.TenementService.ListMeter(parkId, tenementId)
if err != nil {
tenementLog.Error("列出指定商户下所有的表计失败,未能获取表计列表", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, err.Error())
}
return result.Success(
"已经获取到要查询的表计。",
fiber.Map{
"meters": meters,
},
)
}
// 增加一个新的商户
func addTenement(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass {
return err
}
tenementLog.Info("增加一个新的商户", zap.String("Park", parkId))
var form vo.TenementCreationForm
if err := c.BodyParser(&form); err != nil {
tenementLog.Error("增加一个新的商户失败,未能解析要添加的商户信息", zap.Error(err))
return result.BadRequest(fmt.Sprintf("无法解析要添加的商户信息,%s", err.Error()))
}
err := service.TenementService.CreateTenementRecord(parkId, &form)
if err != nil {
tenementLog.Error("增加一个新的商户失败,未能添加商户记录", zap.Error(err))
return result.NotAccept(fmt.Sprintf("无法添加商户记录,%s", err.Error()))
}
return result.Success("已经成功添加商户。")
}
// 给指定商户绑定一个新的表计
func bindMeterToTenement(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass {
return err
}
tenementId := c.Params("tid")
if len(tenementId) == 0 {
tenementLog.Error("给指定商户绑定一个新的表计失败,未指定商户。")
return result.BadRequest("未指定商户。")
}
tenementLog.Info("向指定商户绑定一个表计。", zap.String("Park", parkId), zap.String("Tenement", tenementId))
var form vo.MeterReadingFormWithCode
if err := c.BodyParser(&form); err != nil {
tenementLog.Error("给指定商户绑定一个新的表计失败,未能解析要绑定的表计信息", zap.Error(err))
return result.BadRequest(fmt.Sprintf("无法解析要绑定的表计信息,%s", err.Error()))
}
if !form.MeterReadingForm.Validate() {
tenementLog.Error("给指定商户绑定一个新的表计失败,表计读数不能正确配平,尖锋电量、峰电量、谷电量之和超过总电量。")
return result.NotAccept("表计读数不能正确配平,尖锋电量、峰电量、谷电量之和超过总电量。")
}
err := service.TenementService.BindMeter(parkId, tenementId, form.Code, &form.MeterReadingForm)
if err != nil {
tenementLog.Error("给指定商户绑定一个新的表计失败,未能绑定表计", zap.Error(err))
return result.NotAccept(fmt.Sprintf("无法绑定表计,%s", err.Error()))
}
return result.Success("已经成功绑定表计。")
}
// 从指定商户下解除一个表计的绑定
func unbindMeterFromTenement(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass {
return err
}
tenementId := c.Params("tid")
if len(tenementId) == 0 {
tenementLog.Error("从指定商户下解除一个表计的绑定失败,未指定商户。")
return result.BadRequest("未指定商户。")
}
meterCode := c.Params("code")
if len(meterCode) == 0 {
tenementLog.Error("从指定商户下解除一个表计的绑定失败,未指定表计。")
return result.BadRequest("未指定表计。")
}
tenementLog.Info("从指定商户处解绑一个表计。", zap.String("Park", parkId), zap.String("Tenement", tenementId), zap.String("Meter", meterCode))
var form vo.MeterReadingForm
if err := c.BodyParser(&form); err != nil {
tenementLog.Error("从指定商户下解除一个表计的绑定失败,未能解析要解除绑定的表计抄表数据。", zap.Error(err))
return result.BadRequest(fmt.Sprintf("无法解析要解除绑定的表计抄表数据,%s", err.Error()))
}
err := service.TenementService.UnbindMeter(parkId, tenementId, meterCode, &form)
if err != nil {
tenementLog.Error("从指定商户下解除一个表计的绑定失败,未能解除绑定表计。", zap.Error(err))
return result.NotAccept(fmt.Sprintf("无法解除绑定表计,%s", err.Error()))
}
return result.Success("已经成功解除表计绑定。")
}
// 修改指定商户的详细信息
func updateTenement(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass {
return err
}
tenementId := c.Params("tid")
if len(tenementId) == 0 {
tenementLog.Error("修改指定商户的详细信息失败,未指定商户。")
return result.BadRequest("未指定商户。")
}
tenementLog.Info("修改指定商户的详细信息。", zap.String("Park", parkId), zap.String("Tenement", tenementId))
var form vo.TenementCreationForm
if err := c.BodyParser(&form); err != nil {
tenementLog.Error("修改指定商户的详细信息失败,未能解析要修改的商户信息", zap.Error(err))
return result.BadRequest(fmt.Sprintf("无法解析要修改的商户信息,%s", err.Error()))
}
err := repository.TenementRepository.UpdateTenement(parkId, tenementId, &form)
if err != nil {
tenementLog.Error("修改指定商户的详细信息失败,未能修改商户信息", zap.Error(err))
return result.NotAccept(fmt.Sprintf("无法修改商户信息,%s", err.Error()))
}
return result.Success("商户信息修改成功。")
}
// 迁出指定园区中的商户
func moveOutTenement(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass {
return err
}
tenementId := c.Params("tid")
if len(tenementId) == 0 {
tenementLog.Error("迁出指定园区中的商户失败,未指定商户。")
return result.BadRequest("未指定商户。")
}
tenementLog.Info("迁出指定园区中的商户。", zap.String("Park", parkId), zap.String("Tenement", tenementId))
var readings []*vo.MeterReadingFormWithCode
if err := c.BodyParser(&readings); err != nil {
tenementLog.Error("迁出指定园区中的商户失败,未能解析要迁出商户的抄表数据。", zap.Error(err))
return result.BadRequest(fmt.Sprintf("无法解析要迁出商户的抄表数据,%s", err.Error()))
}
err := service.TenementService.MoveOutTenement(parkId, tenementId, readings)
if err != nil {
tenementLog.Error("迁出指定园区中的商户失败,未能迁出商户。", zap.Error(err))
return result.NotAccept(fmt.Sprintf("无法迁出商户,%s", err.Error()))
}
return result.Success("商户迁出成功。")
}
// 列出园区中的商户列表,主要用于下拉列表
func listTenementForChoice(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
tenementLog.Error("列出园区中的商户列表失败,未能获取当前用户会话信息", zap.Error(err))
return result.Unauthorized("未能获取当前用户会话信息。")
}
parkId := tools.EmptyToNil(c.Params("pid"))
if parkId != nil && len(*parkId) > 0 {
if pass, err := checkParkBelongs(*parkId, tenementLog, c, &result); !pass {
return err
}
}
tenementLog.Info("列出园区中的商户列表,主要用于下拉列表。", zap.String("Ent", session.Uid), zap.Stringp("Park", parkId))
keyword := tools.EmptyToNil(c.Query("keyword"))
limit := c.QueryInt("limit", 6)
tenements, err := repository.TenementRepository.ListForSelect(session.Uid, parkId, keyword, lo.ToPtr(uint(limit)))
if err != nil {
tenementLog.Error("列出园区中的商户列表失败,未能获取商户列表", zap.Error(err))
return result.NotFound(fmt.Sprintf("未能获取商户列表,%s", err.Error()))
}
var tenementsResponse []*vo.SimplifiedTenementResponse
copier.Copy(&tenementsResponse, &tenements)
return result.Success(
"已经获取到要查询的商户。",
fiber.Map{
"tenements": tenementsResponse,
},
)
}
// 获取指定园区中指定商户的详细信息
func getTenementDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Params("pid")
if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass {
return err
}
tenementId := c.Params("tid")
if len(tenementId) == 0 {
tenementLog.Error("获取指定园区中指定商户的详细信息失败,未指定商户。")
return result.BadRequest("未指定商户。")
}
tenementLog.Info("获取指定园区中指定商户的详细信息。", zap.String("Park", parkId), zap.String("Tenement", tenementId))
tenement, err := repository.TenementRepository.RetrieveTenementDetail(parkId, tenementId)
if err != nil {
tenementLog.Error("获取指定园区中指定商户的详细信息失败,未能获取商户信息", zap.Error(err))
return result.NotFound(fmt.Sprintf("未能获取商户信息,%s", err.Error()))
}
var detail vo.TenementDetailResponse
copier.Copy(&detail, &tenement)
return result.Success(
"已经获取到要查询的商户。",
fiber.Map{
"tenement": detail,
},
)
}

144
controller/top_up.go Normal file
View File

@@ -0,0 +1,144 @@
package controller
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/tools"
"electricity_bill_calc/types"
"electricity_bill_calc/vo"
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
"go.uber.org/zap"
)
var topUpLog = logger.Named("Controller", "TopUp")
func InitializeTopUpHandlers(router *fiber.App) {
router.Get("/topup/:pid", security.EnterpriseAuthorize, listTopUps)
router.Post("/topup/:pid", security.EnterpriseAuthorize, createTopUp)
router.Get("/topup/:pid/:code", security.EnterpriseAuthorize, getTopUp)
router.Delete("/topup/:pid/:code", security.EnterpriseAuthorize, deleteTopUp)
}
// 查询符合条件的商户充值记录
func listTopUps(c *fiber.Ctx) error {
result := response.NewResult(c)
park := tools.EmptyToNil(c.Params("pid"))
if park == nil {
topUpLog.Error("查询符合条件的商户充值记录,未指定要访问的园区")
return result.BadRequest("未指定要访问的园区")
}
if pass, err := checkParkBelongs(*park, topUpLog, c, &result); !pass {
return err
}
keyword := tools.EmptyToNil(c.Query("keyword"))
startDate, err := types.ParseDatep(c.Query("start_date"))
if err != nil {
topUpLog.Error("查询符合条件的商户充值记录,查询起始日期格式错误", zap.Error(err))
return result.BadRequest("查询起始日期格式错误")
}
endDate, err := types.ParseDatep(c.Query("end_date"))
if err != nil {
topUpLog.Error("查询符合条件的商户充值记录,查询结束日期格式错误", zap.Error(err))
return result.BadRequest("查询结束日期格式错误")
}
page := c.QueryInt("page", 1)
topUps, total, err := repository.TopUpRepository.ListTopUps(*park, startDate, endDate, keyword, uint(page))
if err != nil {
topUpLog.Error("查询符合条件的商户充值记录,查询失败", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "商户充值记录查询不成功")
}
topUpLog.Debug("检查获取到的数据", zap.Any("topUps", topUps), zap.Int64("total", total))
topUpDetails := make([]*vo.TopUpDetailQueryResponse, 0)
copier.Copy(&topUpDetails, &topUps)
topUpLog.Debug("检查转换后的数据", zap.Any("topUpDetails", topUpDetails))
return result.Success(
"已经获取到符合条件的商户充值记录",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"topUps": topUpDetails},
)
}
// 获取指定充值记录的详细内容
func getTopUp(c *fiber.Ctx) error {
result := response.NewResult(c)
park := tools.EmptyToNil(c.Params("pid"))
if park == nil {
topUpLog.Error("获取指定充值记录的详细内容,未指定要访问的园区")
return result.BadRequest("未指定要访问的园区")
}
if pass, err := checkParkBelongs(*park, topUpLog, c, &result); !pass {
return err
}
topUpCode := tools.EmptyToNil(c.Params("code"))
if topUpCode == nil {
topUpLog.Error("获取指定充值记录的详细内容,未指定要查询的充值记录")
return result.BadRequest("未指定要查询的充值记录")
}
topUp, err := repository.TopUpRepository.GetTopUp(*park, *topUpCode)
if err != nil {
topUpLog.Error("获取指定充值记录的详细内容,查询失败", zap.Error(err))
return result.NotFound("未找到指定的商户充值记录")
}
var topUpDetail vo.TopUpDetailQueryResponse
copier.Copy(&topUpDetail, &topUp)
return result.Success(
"已经获取到指定充值记录的详细内容",
fiber.Map{"topup": topUpDetail},
)
}
// 创建一条新的商户充值记录
func createTopUp(c *fiber.Ctx) error {
result := response.NewResult(c)
park := tools.EmptyToNil(c.Params("pid"))
if park == nil {
topUpLog.Error("创建一条新的商户充值记录,未指定要访问的园区")
return result.BadRequest("未指定要访问的园区")
}
if pass, err := checkParkBelongs(*park, topUpLog, c, &result); !pass {
return err
}
var form vo.TopUpCreationForm
if err := c.BodyParser(&form); err != nil {
topUpLog.Error("创建一条新的商户充值记录,请求体解析失败", zap.Error(err))
return result.BadRequest("请求体解析失败")
}
if err := repository.TopUpRepository.CreateTopUp(*park, &form); err != nil {
topUpLog.Error("创建一条新的商户充值记录,创建失败", zap.Error(err))
return result.NotAccept("商户充值记录创建不成功")
}
return result.Created(
"已经创建一条新的商户充值记录",
)
}
// 删除一条指定的商户充值记录
func deleteTopUp(c *fiber.Ctx) error {
result := response.NewResult(c)
park := tools.EmptyToNil(c.Params("pid"))
if park == nil {
topUpLog.Error("删除一条指定的商户充值记录,未指定要访问的园区")
return result.BadRequest("未指定要访问的园区")
}
if pass, err := checkParkBelongs(*park, topUpLog, c, &result); !pass {
return err
}
topUpCode := tools.EmptyToNil(c.Params("code"))
if topUpCode == nil {
topUpLog.Error("删除一条指定的商户充值记录,未指定要删除的充值记录")
return result.BadRequest("未指定要删除的充值记录")
}
if err := repository.TopUpRepository.DeleteTopUp(*park, *topUpCode); err != nil {
topUpLog.Error("删除一条指定的商户充值记录,删除失败", zap.Error(err))
return result.NotAccept("商户充值记录删除不成功")
}
fmt.Println("已经删除一条指定的商户充值记录")
return result.Success(
"已经删除一条指定的商户充值记录",
)
}

View File

@@ -3,52 +3,56 @@ package controller
import ( import (
"electricity_bill_calc/cache" "electricity_bill_calc/cache"
"electricity_bill_calc/exceptions" "electricity_bill_calc/exceptions"
"electricity_bill_calc/global" "electricity_bill_calc/logger"
"electricity_bill_calc/model" "electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/response" "electricity_bill_calc/response"
"electricity_bill_calc/security" "electricity_bill_calc/security"
"electricity_bill_calc/service" "electricity_bill_calc/service"
"fmt" "electricity_bill_calc/tools"
"electricity_bill_calc/vo"
"net/http" "net/http"
"strconv" "strconv"
"time"
"github.com/gin-gonic/gin" "github.com/gofiber/fiber/v2"
"github.com/shopspring/decimal" "go.uber.org/zap"
) )
func InitializeUserController(router *gin.Engine) { func InitializeUserHandlers(router *fiber.App) {
router.DELETE("/password/:uid", security.OPSAuthorize, invalidUserPassword) router.Delete("/login", security.MustAuthenticated, doLogout)
router.DELETE("/login", security.MustAuthenticated, logout) router.Post("/login", doLogin)
router.PUT("/password", resetUserPassword) router.Get("/account", security.OPSAuthorize, searchUsers)
router.GET("/accounts", security.OPSAuthorize, listPagedUser) router.Post("/account", security.OPSAuthorize, createOPSAccount)
router.POST("/login", login) router.Get("/account/:uid", security.MustAuthenticated, fetchUserInformation)
router.PUT("/account/enabled/state", security.OPSAuthorize, switchUserEnabling) router.Put("/account/:uid", security.OPSAuthorize, modifyUserInformation)
router.POST("/account", security.OPSAuthorize, createOPSAndManagementAccount) router.Put("/account/enabled/state", security.OPSAuthorize, changeUserState)
router.GET("/account/:uid", security.MustAuthenticated, getUserDetail) router.Get("/expiration", security.EnterpriseAuthorize, getAccountExpiration)
router.POST("/enterprise", security.OPSAuthorize, createEnterpriseAccount) router.Post("/enterprise", security.OPSAuthorize, createEnterpriseAccount)
router.PUT("/account/:uid", security.OPSAuthorize, modifyAccountDetail) router.Get("/enterprise/quick/search", security.OPSAuthorize, quickSearchEnterprise)
router.GET("/enterprise/quick/search", security.OPSAuthorize, quickSearchEnterprise) router.Put("/password", resetUserPassword)
router.GET("/expiration", security.EnterpriseAuthorize, fetchExpiration) router.Delete("/password/:uid", security.OPSAuthorize, invalidUserPassword)
} }
type _LoginFormData struct { var userLog = logger.Named("Handler", "User")
type _LoginForm struct {
Username string `json:"uname"` Username string `json:"uname"`
Password string `json:"upass"` Password string `json:"upass"`
Type int8 `json:"type"` Type int16 `json:"type"`
} }
func login(c *gin.Context) { func doLogin(c *fiber.Ctx) error {
result := response.NewResult(c) result := response.NewResult(c)
loginData := new(_LoginFormData) loginData := new(_LoginForm)
err := c.BindJSON(loginData) if err := c.BodyParser(loginData); err != nil {
if err != nil { userLog.Error("表单解析失败!", zap.Error(err))
result.Error(http.StatusInternalServerError, "表单解析失败。") return result.Error(http.StatusInternalServerError, "表单解析失败。")
return
} }
var ( var (
session *model.Session session *model.Session
err error
) )
userLog.Info("有用户请求登录。", zap.String("username", loginData.Username), zap.Int16("type", loginData.Type))
if loginData.Type == model.USER_TYPE_ENT { if loginData.Type == model.USER_TYPE_ENT {
session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password) session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password)
} else { } else {
@@ -57,100 +61,40 @@ func login(c *gin.Context) {
if err != nil { if err != nil {
if authError, ok := err.(*exceptions.AuthenticationError); ok { if authError, ok := err.(*exceptions.AuthenticationError); ok {
if authError.NeedReset { if authError.NeedReset {
result.LoginNeedReset() return result.LoginNeedReset()
return
} }
result.Error(int(authError.Code), authError.Message) return result.Error(int(authError.Code), authError.Message)
return
} else { } else {
result.Error(http.StatusInternalServerError, err.Error()) userLog.Error("用户登录请求处理失败!", zap.Error(err))
return return result.Error(http.StatusInternalServerError, err.Error())
} }
} }
result.LoginSuccess(session) return result.LoginSuccess(session)
} }
func doLogout(c *fiber.Ctx) error {
func logout(c *gin.Context) {
result := response.NewResult(c) result := response.NewResult(c)
session, exists := c.Get("session") session, err := _retreiveSession(c)
if !exists {
result.Success("用户会话已结束。")
return
}
_, err := cache.ClearSession(session.(*model.Session).Token)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) return result.Success("用户会话已结束。")
return
} }
result.Success("用户已成功登出系统。") _, err = cache.ClearSession(session.Token)
if err != nil {
userLog.Error("用户登出处理失败!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("用户已成功登出系统。")
} }
func invalidUserPassword(c *gin.Context) { func searchUsers(c *fiber.Ctx) error {
result := response.NewResult(c) result := response.NewResult(c)
targetUserId := c.Param("uid") requestPage, err := strconv.Atoi(c.Query("page", "1"))
verifyCode, err := service.UserService.InvalidUserPassword(targetUserId)
if _, ok := err.(exceptions.NotFoundError); ok {
result.NotFound("未找到指定用户。")
return
}
if _, ok := err.(exceptions.UnsuccessfulOperationError); ok {
result.NotAccept("未能成功更新用户的密码。")
return
}
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) return result.NotAccept("查询参数[page]格式不正确。")
return
}
result.Json(http.StatusAccepted, "用户密码已经失效", gin.H{"verify": verifyCode})
}
type _ResetPasswordFormData struct {
VerifyCode string `json:"verifyCode"`
Username string `json:"uname"`
NewPassword string `json:"newPass"`
}
func resetUserPassword(c *gin.Context) {
result := response.NewResult(c)
resetForm := new(_ResetPasswordFormData)
c.BindJSON(resetForm)
verified, err := service.UserService.VerifyUserPassword(resetForm.Username, resetForm.VerifyCode)
if _, ok := err.(exceptions.NotFoundError); ok {
result.NotFound("指定的用户不存在。")
return
} }
requestKeyword := c.Query("keyword")
requestUserType, err := strconv.Atoi(c.Query("type", "-1"))
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) return result.NotAccept("查询参数[type]格式不正确。")
return
}
if !verified {
result.Error(http.StatusUnauthorized, "验证码不正确。")
return
}
completed, err := service.UserService.ResetUserPassword(resetForm.Username, resetForm.NewPassword)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
if completed {
result.Updated("用户凭据已更新。")
return
}
result.NotAccept("用户凭据未能成功更新。")
}
func listPagedUser(c *gin.Context) {
result := response.NewResult(c)
requestPage, err := strconv.Atoi(c.DefaultQuery("page", "1"))
if err != nil {
result.NotAccept("查询参数[page]格式不正确。")
return
}
requestKeyword := c.DefaultQuery("keyword", "")
requestUserType, err := strconv.Atoi(c.DefaultQuery("type", "-1"))
if err != nil {
result.NotAccept("查询参数[type]格式不正确。")
return
} }
var requestUserStat *bool var requestUserStat *bool
state, err := strconv.ParseBool(c.Query("state")) state, err := strconv.ParseBool(c.Query("state"))
@@ -159,213 +103,230 @@ func listPagedUser(c *gin.Context) {
} else { } else {
requestUserStat = &state requestUserStat = &state
} }
users, total, err := service.UserService.ListUserDetail(requestKeyword, requestUserType, requestUserStat, requestPage) users, total, err := repository.UserRepository.FindUser(
&requestKeyword,
int16(requestUserType),
requestUserStat,
uint(requestPage),
)
if err != nil { if err != nil {
result.NotFound(err.Error()) return result.NotFound(err.Error())
return
} }
result.Json( return result.Success(
http.StatusOK,
"已取得符合条件的用户集合。", "已取得符合条件的用户集合。",
response.NewPagedResponse(requestPage, total).ToMap(), response.NewPagedResponse(requestPage, total).ToMap(),
gin.H{"accounts": users}, fiber.Map{"accounts": users},
) )
} }
type _UserStateChangeFormData struct { func getAccountExpiration(c *fiber.Ctx) error {
UserID string `json:"uid" form:"uid"`
Enabled bool `json:"enabled" form:"enabled"`
}
func switchUserEnabling(c *gin.Context) {
result := response.NewResult(c)
switchForm := new(_UserStateChangeFormData)
c.BindJSON(switchForm)
err := service.UserService.SwitchUserState(switchForm.UserID, switchForm.Enabled)
if err != nil {
if nfErr, ok := err.(*exceptions.NotFoundError); ok {
result.NotFound(nfErr.Message)
return
} else {
result.Error(http.StatusInternalServerError, err.Error())
return
}
}
result.Updated("用户状态已经更新。")
}
type _OPSAccountCreationFormData struct {
Username string `json:"username" form:"username"`
Name string `json:"name" form:"name"`
Contact *string `json:"contact" form:"contact"`
Phone *string `json:"phone" form:"phone"`
Type int `json:"type" form:"type"`
}
func createOPSAndManagementAccount(c *gin.Context) {
result := response.NewResult(c)
creationForm := new(_OPSAccountCreationFormData)
c.BindJSON(creationForm)
exists, err := service.UserService.IsUsernameExists(creationForm.Username)
if exists {
result.Conflict("指定的用户名已经被使用了。")
return
}
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
newUser := new(model.User)
newUser.Username = creationForm.Username
newUser.Type = int8(creationForm.Type)
newUser.Enabled = true
newUserDetail := new(model.UserDetail)
newUserDetail.Name = &creationForm.Name
newUserDetail.Contact = creationForm.Contact
newUserDetail.Phone = creationForm.Phone
newUserDetail.UnitServiceFee = decimal.Zero
newUserDetail.ServiceExpiration, _ = time.Parse("2006-01-02 15:04:05", "2099-12-31 23:59:59")
verifyCode, err := service.UserService.CreateUser(newUser, newUserDetail)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
cache.AbolishRelation("user")
result.Json(http.StatusCreated, "用户已经成功创建。", gin.H{"verify": verifyCode})
}
func getUserDetail(c *gin.Context) {
result := response.NewResult(c)
targetUserId := c.Param("uid")
exists, err := service.UserService.IsUserExists(targetUserId)
if !exists {
result.NotFound("指定的用户不存在。")
return
}
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
}
userDetail, err := service.UserService.FetchUserDetail(targetUserId)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Json(http.StatusOK, "用户详细信息已获取到。", gin.H{"user": userDetail})
}
type _EnterpriseCreationFormData struct {
Username string `json:"username" form:"username"`
Name string `json:"name" form:"name"`
Region *string `json:"region" form:"region"`
Address *string `json:"address" form:"address"`
Contact *string `json:"contact" form:"contact"`
Phone *string `json:"phone" form:"phone"`
UnitServiceFee *string `json:"unitServiceFee" form:"unitServiceFee"`
}
func createEnterpriseAccount(c *gin.Context) {
result := response.NewResult(c)
creationForm := new(_EnterpriseCreationFormData)
c.BindJSON(creationForm)
exists, err := service.UserService.IsUsernameExists(creationForm.Username)
if exists {
result.Conflict("指定的用户名已经被使用了。")
return
}
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
newUser := new(model.User)
newUser.Username = creationForm.Username
newUser.Type = model.USER_TYPE_ENT
newUser.Enabled = true
newUserDetail := new(model.UserDetail)
newUserDetail.Name = &creationForm.Name
newUserDetail.Contact = creationForm.Contact
newUserDetail.Phone = creationForm.Phone
newUserDetail.UnitServiceFee, err = decimal.NewFromString(*creationForm.UnitServiceFee)
if err != nil {
result.BadRequest("用户月服务费无法解析。")
return
}
newUserDetail.ServiceExpiration = time.Now()
verifyCode, err := service.UserService.CreateUser(newUser, newUserDetail)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
cache.AbolishRelation("user")
result.Json(http.StatusCreated, "用户已经成功创建。", gin.H{"verify": verifyCode})
}
type _AccountModificationFormData struct {
Name string `json:"name" form:"name"`
Region *string `json:"region" form:"region"`
Address *string `json:"address" form:"address"`
Contact *string `json:"contact" form:"contact"`
Phone *string `json:"phone" form:"phone"`
UnitServiceFee *string `json:"unitServiceFee" form:"unitServiceFee"`
}
func modifyAccountDetail(c *gin.Context) {
result := response.NewResult(c)
targetUserId := c.Param("uid")
modForm := new(_AccountModificationFormData)
c.BindJSON(modForm)
exists, err := service.UserService.IsUserExists(targetUserId)
if !exists {
result.NotFound("指定的用户不存在。")
return
}
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
newUserInfo := new(model.UserDetail)
newUserInfo.Name = &modForm.Name
newUserInfo.Region = modForm.Region
newUserInfo.Address = modForm.Address
newUserInfo.Contact = modForm.Contact
newUserInfo.Phone = modForm.Phone
newUserInfo.UnitServiceFee, err = decimal.NewFromString(*modForm.UnitServiceFee)
if err != nil {
result.BadRequest("用户月服务费无法解析。")
return
}
_, err = global.DBConn.ID(targetUserId).Update(newUserInfo)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
cache.AbolishRelation("user")
cache.AbolishRelation(fmt.Sprintf("user_%s", targetUserId))
result.Updated("指定用户的信息已经更新。")
}
func quickSearchEnterprise(c *gin.Context) {
result := response.NewResult(c)
keyword := c.Query("keyword")
searchResult, err := service.UserService.SearchLimitUsers(keyword, 6)
if err != nil {
result.Error(http.StatusInternalServerError, err.Error())
return
}
result.Json(http.StatusOK, "已查询到存在符合条件的企业", gin.H{"users": searchResult})
}
func fetchExpiration(c *gin.Context) {
result := response.NewResult(c) result := response.NewResult(c)
session, err := _retreiveSession(c) session, err := _retreiveSession(c)
if err != nil { if err != nil {
result.Unauthorized(err.Error()) userLog.Error("未找到有效的用户会话。", zap.Error(err))
return return result.Error(http.StatusInternalServerError, err.Error())
} }
user, err := service.UserService.FetchUserDetail(session.Uid) userDetail, err := repository.UserRepository.FindUserDetailById(session.Uid)
if err != nil { if err != nil {
result.NotFound(err.Error()) return result.NotFound("未找到指定的用户档案")
return
} }
result.Json(http.StatusOK, "已经取得用户的服务期限信息", gin.H{"expiration": user.ServiceExpiration.Format("2006-01-02")}) return result.Success(
"已经取得用户的服务期限信息",
fiber.Map{"expiration": userDetail.ServiceExpiration.Format("2006-01-02")},
)
}
func createOPSAccount(c *fiber.Ctx) error {
userLog.Info("请求创建运维或监管账户。")
result := response.NewResult(c)
creationForm := new(vo.MGTAndOPSAccountCreationForm)
if err := c.BodyParser(creationForm); err != nil {
userLog.Error("表单解析失败!", zap.Error(err))
return result.UnableToParse("无法解析提交的数据。")
}
exists, err := repository.UserRepository.IsUsernameExists(creationForm.Username)
if err != nil {
userLog.Error("检查用户名是否已经被使用时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if exists {
return result.Conflict("指定的用户名已经被使用了。")
}
verifyCode, err := service.UserService.CreateUserAccount(creationForm.IntoUser(), creationForm.IntoUserDetail())
if err != nil {
userLog.Error("创建用户账户时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("用户已经成功创建。", fiber.Map{"verify": verifyCode})
}
func fetchUserInformation(c *fiber.Ctx) error {
userLog.Info("请求获取用户详细信息。")
result := response.NewResult(c)
targetUserId := c.Params("uid")
exists, err := repository.UserRepository.IsUserExists(targetUserId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !exists {
return result.NotFound("指定的用户不存在。")
}
userDetail, err := repository.UserRepository.FindUserDetailById(targetUserId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("用户详细信息已获取到。", fiber.Map{"user": userDetail})
}
func modifyUserInformation(c *fiber.Ctx) error {
userLog.Info("请求修改用户详细信息。")
session, _ := _retreiveSession(c)
result := response.NewResult(c)
targetUserId := c.Params("uid")
exists, err := repository.UserRepository.IsUserExists(targetUserId)
if err != nil {
userLog.Error("检查用户是否存在时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if !exists {
return result.NotFound("指定的用户不存在。")
}
modificationForm := new(vo.UserDetailModificationForm)
if err := c.BodyParser(modificationForm); err != nil {
userLog.Error("表单解析失败!", zap.Error(err))
return result.UnableToParse("无法解析提交的数据。")
}
userLog.Debug("用户服务费修改表单:", zap.Any("form", modificationForm))
detailFormForUpdate, err := modificationForm.IntoModificationModel()
if err != nil {
userLog.Error("用户服务费解析转换失败!", zap.Error(err))
return result.UnableToParse("无法解析提交的数据,服务费格式不正确。")
}
if ok, err := repository.UserRepository.UpdateDetail(targetUserId, *detailFormForUpdate, &session.Uid); err != nil || !ok {
userLog.Error("更新用户详细信息失败!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定用户的信息已经更新。")
}
func changeUserState(c *fiber.Ctx) error {
userLog.Info("请求修改用户状态。")
result := response.NewResult(c)
modificationForm := new(vo.UserStateChangeForm)
if err := c.BodyParser(modificationForm); err != nil {
userLog.Error("表单解析失败!", zap.Error(err))
return result.UnableToParse("无法解析提交的数据。")
}
exists, err := repository.UserRepository.IsUserExists(modificationForm.Uid)
if err != nil {
userLog.Error("检查用户是否存在时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if !exists {
return result.NotFound("指定的用户不存在。")
}
if ok, err := repository.UserRepository.ChangeState(modificationForm.Uid, modificationForm.Enabled); err != nil || !ok {
userLog.Error("更新用户状态失败!", zap.Error(err))
return result.NotAccept("无法更新用户状态。")
}
return result.Updated("用户的状态已经更新。")
}
func createEnterpriseAccount(c *fiber.Ctx) error {
userLog.Info("请求创建企业账户。")
result := response.NewResult(c)
creationForm := new(vo.EnterpriseAccountCreationForm)
if err := c.BodyParser(creationForm); err != nil {
userLog.Error("表单解析失败!", zap.Error(err))
return result.UnableToParse("无法解析提交的数据。")
}
exists, err := repository.UserRepository.IsUsernameExists(creationForm.Username)
if err != nil {
userLog.Error("检查用户名是否已经被使用时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if exists {
return result.Conflict("指定的用户名已经被使用了。")
}
userDetail, err := creationForm.IntoUserDetail()
if err != nil {
userLog.Error("转换用户详细档案时发生错误!", zap.Error(err))
return result.UnableToParse("无法解析提交的数据,服务费格式不正确。")
}
verifyCode, err := service.UserService.CreateUserAccount(creationForm.IntoUser(), userDetail)
if err != nil {
userLog.Error("创建用户账户时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("用户已经成功创建。", fiber.Map{"verify": verifyCode})
}
func quickSearchEnterprise(c *fiber.Ctx) error {
userLog.Info("请求快速查询企业账户。")
result := response.NewResult(c)
keyword := c.Query("keyword")
limit := c.QueryInt("limit", 6)
if limit < 1 {
limit = 6
}
users, err := repository.UserRepository.SearchUsersWithLimit(nil, &keyword, uint(limit))
if err != nil {
userLog.Error("查询用户时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("已查询到存在符合条件的企业", fiber.Map{"users": users})
}
func resetUserPassword(c *fiber.Ctx) error {
userLog.Info("请求重置用户密码。")
result := response.NewResult(c)
repasswordForm := new(vo.RepasswordForm)
if err := c.BodyParser(repasswordForm); err != nil {
userLog.Error("表单解析失败!", zap.Error(err))
return result.UnableToParse("无法解析提交的数据。")
}
user, err := repository.UserRepository.FindUserByUsername(repasswordForm.Username)
if err != nil {
userLog.Error("检查用户是否存在时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if user == nil {
return result.NotFound("指定的用户不存在。")
}
if !service.UserService.MatchUserPassword(user.Password, repasswordForm.VerifyCode) {
return result.Unauthorized("验证码不正确。")
}
ok, err := repository.UserRepository.UpdatePassword(user.Id, repasswordForm.NewPassword, false)
if err != nil {
userLog.Error("更新用户凭据时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if !ok {
return result.NotAccept("无法更新用户凭据。")
}
return result.Updated("用户凭据已经更新。")
}
func invalidUserPassword(c *fiber.Ctx) error {
userLog.Info("请求使用户凭据失效。")
result := response.NewResult(c)
uid := c.Params("uid")
exists, err := repository.UserRepository.IsUserExists(uid)
if err != nil {
userLog.Error("检查用户是否存在时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if !exists {
return result.NotFound("指定的用户不存在。")
}
verifyCode := tools.RandStr(10)
ok, err := repository.UserRepository.UpdatePassword(uid, verifyCode, true)
if err != nil {
userLog.Error("更新用户凭据时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if !ok {
return result.NotAccept("未能更新用户凭据。")
}
return result.Updated("用户凭据已经更新。", fiber.Map{"verify": verifyCode})
} }

View File

@@ -1,86 +1,135 @@
package controller package controller
import ( import (
"electricity_bill_calc/exceptions" "electricity_bill_calc/logger"
"electricity_bill_calc/repository"
"electricity_bill_calc/response" "electricity_bill_calc/response"
"electricity_bill_calc/security" "electricity_bill_calc/security"
"electricity_bill_calc/service" "electricity_bill_calc/vo"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"net/http" "net/http"
"strconv"
"github.com/gin-gonic/gin"
) )
func InitializeWithdrawController(router *gin.Engine) { var withdrawLog = logger.Named("Handler", "Withdraw")
router.DELETE("/publicity/:pid", security.EnterpriseAuthorize, applyReportWithdraw)
router.GET("/withdraws", security.OPSAuthorize, fetchWithdrawsWaitingAutdit) func InitializeWithdrawHandlers(router *fiber.App) {
router.PUT("/withdraw/:rid", security.OPSAuthorize, auditWithdraw) router.Get("/withdraw", security.OPSAuthorize, withdraw)
router.Put("/withdraw/:rid", security.OPSAuthorize, reviewRequestWithdraw)
router.Delete("/withdraw/:rid", security.EnterpriseAuthorize, recallReport)
} }
func applyReportWithdraw(c *gin.Context) { // 用于分页检索用户的核算报表
func withdraw(c *fiber.Ctx) error {
//记录日志
withdrawLog.Info("带分页的待审核的核算撤回申请列表")
//获取请求参数
result := response.NewResult(c) result := response.NewResult(c)
requestReportId := c.Param("pid") keyword := c.Query("keyword", "")
if !ensureReportBelongs(c, result, requestReportId) { page := c.QueryInt("page", 1)
return withdrawLog.Info("参数为: ", zap.String("keyword", keyword), zap.Int("page", page))
} //中间数据库操作暂且省略。。。。
deleted, err := service.WithdrawService.ApplyWithdraw(requestReportId) //首先进行核算报表的分页查询
withdraws, total, err := repository.WithdrawRepository.FindWithdraw(uint(page), &keyword)
if err != nil { if err != nil {
if nfErr, ok := err.(exceptions.NotFoundError); ok { withdrawLog.Error("检索用户核算报表失败。", zap.Error(err))
result.NotFound(nfErr.Error()) return result.Error(http.StatusInternalServerError, err.Error())
return
} else if ioErr, ok := err.(exceptions.ImproperOperateError); ok {
result.NotAccept(ioErr.Error())
return
} else {
result.Error(http.StatusInternalServerError, err.Error())
return
}
} }
if !deleted { //TODO: 2023-07-18 此处返回值是个示例,具体返回值需要查询数据库(完成)
result.Error(http.StatusInternalServerError, "未能完成公示报表的申请撤回操作。") return result.Success(
return "withdraw请求成功",
} response.NewPagedResponse(page, total).ToMap(),
result.Success("指定的公示报表已经申请撤回。") fiber.Map{"records": withdraws},
}
func fetchWithdrawsWaitingAutdit(c *gin.Context) {
result := response.NewResult(c)
keyword := c.DefaultQuery("keyword", "")
requestPage, err := strconv.Atoi(c.DefaultQuery("page", "1"))
if err != nil {
result.NotAccept("查询参数[page]格式不正确。")
return
}
reports, totalitems, err := service.WithdrawService.FetchPagedWithdrawApplies(requestPage, keyword)
if err != nil {
result.NotFound(err.Error())
return
}
result.Json(
http.StatusOK,
"已经取得符合条件的等待审核的撤回申请。",
response.NewPagedResponse(requestPage, totalitems).ToMap(),
gin.H{"records": reports},
) )
} }
type WithdrawAuditFormData struct { // 用于审核撤回报表
Audit bool `json:"audit" form:"audit"` func reviewRequestWithdraw(c *fiber.Ctx) error {
Rid := c.Params("rid", "")
Data := new(vo.ReviewWithdraw)
result := response.NewResult(c)
if err := c.BodyParser(&Data); err != nil {
withdrawLog.Error("无法解析审核指定报表的请求数据", zap.Error(err))
return result.BadRequest("无法解析审核指定报表的请求数据。")
}
if Data.Audit == true { //审核通过
ok, err := repository.WithdrawRepository.ReviewTrueReportWithdraw(Rid)
if err != nil {
withdrawLog.Error("审核同意撤回报表失败")
return result.Error(http.StatusInternalServerError, err.Error())
}
if !ok {
withdrawLog.Error("审核同意撤回报表失败")
return result.NotAccept("审核同意撤回报表失败")
} else {
return result.Success("审核同意撤回报表成功!")
}
} else { //审核不通过
ok, err := repository.WithdrawRepository.ReviewFalseReportWithdraw(Rid)
if err != nil {
withdrawLog.Error("审核拒绝撤回报表失败")
return result.Error(http.StatusInternalServerError, err.Error())
}
if !ok {
withdrawLog.Error("审核拒绝撤回报表失败")
return result.NotAccept("审核拒绝撤回报表失败")
} else {
return result.Success("审核拒绝撤回报表成功!")
}
}
} }
func auditWithdraw(c *gin.Context) { // 用于撤回电费核算
func recallReport(c *fiber.Ctx) error {
// 获取用户会话信息和参数
rid := c.Params("rid", "")
result := response.NewResult(c) result := response.NewResult(c)
requestReportId := c.Param("rid") session, err := _retreiveSession(c)
formData := new(WithdrawAuditFormData)
c.BindJSON(formData)
err := service.WithdrawService.AuditWithdraw(requestReportId, formData.Audit)
if err != nil { if err != nil {
if nfErr, ok := err.(exceptions.NotFoundError); ok { withdrawLog.Error("无法获取当前用户的会话。")
result.NotFound(nfErr.Error()) return result.Unauthorized(err.Error())
} else {
result.NotAccept(err.Error())
}
return
} }
result.Success("指定公示报表的撤回申请已经完成审核") // 检查指定报表的所属情况
isBelongsTo, err := repository.ReportRepository.IsBelongsTo(rid, session.Uid)
if err != nil {
withdrawLog.Error("检查报表所属情况出现错误。", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if err == nil && isBelongsTo == true {
// 判断指定报表是否是当前园区的最后一张报表
isLastReport, err := repository.ReportRepository.IsLastReport(rid)
if err != nil {
withdrawLog.Error("判断指定报表是否为当前园区的最后一张报表时出错", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if err == nil && isLastReport == true {
// 申请撤回指定的核算报表
//TODO: 2023.07.25 申请撤回指定核算报表,正确状态未处理(完成)
ok, err := repository.ReportRepository.ApplyWithdrawReport(rid)
if err != nil {
withdrawLog.Error("申请撤回指定核算报表出错", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if ok {
withdrawLog.Info("申请撤回指定核算报表成功")
return result.Success("申请撤回指定核算报表成功")
}
} else {
withdrawLog.Info("当前报表不是当前园区的最后一张报表")
return result.Error(http.StatusForbidden, "当前报表不是当前园区的最后一张报表")
}
} else {
withdrawLog.Info("指定的核算报表不属于当前用户。")
return result.Error(http.StatusForbidden, "指定的核算报表不属于当前用户")
}
return result.Error(http.StatusInternalServerError, "其他错误")
} }

View File

@@ -1,9 +1,10 @@
package excel package excel
import ( import (
"electricity_bill_calc/model"
"electricity_bill_calc/tools" "electricity_bill_calc/tools"
"electricity_bill_calc/types"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"reflect" "reflect"
@@ -18,13 +19,13 @@ import (
type ExcelTemplateGenerator interface { type ExcelTemplateGenerator interface {
Close() Close()
WriteTo(w io.Writer) (int64, error) WriteTo(w io.Writer) (int64, error)
WriteMeterData(meters []model.EndUserDetail) error
} }
type ColumnRecognizer struct { type ColumnRecognizer struct {
Pattern []string Pattern [][]string
Tag string Tag string
MatchIndex int MatchIndex int
MustFill bool
} }
type ExcelAnalyzer[T any] struct { type ExcelAnalyzer[T any] struct {
@@ -43,7 +44,7 @@ type ExcelAnalysisError struct {
Err AnalysisError `json:"error"` Err AnalysisError `json:"error"`
} }
func NewColumnRecognizer(tag string, patterns ...string) ColumnRecognizer { func NewColumnRecognizer(tag string, patterns ...[]string) ColumnRecognizer {
return ColumnRecognizer{ return ColumnRecognizer{
Pattern: patterns, Pattern: patterns,
Tag: tag, Tag: tag,
@@ -65,9 +66,17 @@ func (e ExcelAnalysisError) Error() string {
func (r *ColumnRecognizer) Recognize(cellValue string) bool { func (r *ColumnRecognizer) Recognize(cellValue string) bool {
matches := make([]bool, 0) matches := make([]bool, 0)
for _, p := range r.Pattern { for _, pG := range r.Pattern {
matches = append(matches, strings.Contains(cellValue, p)) groupMatch := make([]bool, 0)
for _, p := range pG {
groupMatch = append(groupMatch, strings.Contains(cellValue, p))
}
// 这句表示在每一个匹配组中,只要有一个匹配项,就算匹配成功
matches = append(matches, lo.Reduce(groupMatch, func(acc, elem bool, index int) bool {
return acc || elem
}, false))
} }
// 这句表示在尊有的匹配组中,必须全部的匹配组都完成匹配,才算匹配成功
return lo.Reduce(matches, func(acc, elem bool, index int) bool { return lo.Reduce(matches, func(acc, elem bool, index int) bool {
return acc && elem return acc && elem
}, true) }, true)
@@ -126,8 +135,18 @@ func (a *ExcelAnalyzer[T]) Analysis(bean T) ([]T, []ExcelAnalysisError) {
if alias, ok := field.Tag.Lookup("excel"); ok { if alias, ok := field.Tag.Lookup("excel"); ok {
for _, recognizer := range a.Regconizers { for _, recognizer := range a.Regconizers {
if alias == recognizer.Tag && recognizer.MatchIndex != -1 { if alias == recognizer.Tag && recognizer.MatchIndex != -1 {
var matchValue string
actualField := instance.Elem().FieldByName(field.Name) actualField := instance.Elem().FieldByName(field.Name)
matchValue := cols[recognizer.MatchIndex] if recognizer.MatchIndex > len(cols)-1 {
if recognizer.MustFill {
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: errors.New("单元格内不能没有内容。")}})
continue
} else {
matchValue = ""
}
} else {
matchValue = cols[recognizer.MatchIndex]
}
switch field.Type.String() { switch field.Type.String() {
case "string": case "string":
actualField.Set(reflect.ValueOf(matchValue)) actualField.Set(reflect.ValueOf(matchValue))
@@ -154,7 +173,9 @@ func (a *ExcelAnalyzer[T]) Analysis(bean T) ([]T, []ExcelAnalysisError) {
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为纯数字内容。%w", err)}}) errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为纯数字内容。%w", err)}})
actualField.Set(reflect.ValueOf((nullValue))) actualField.Set(reflect.ValueOf((nullValue)))
} else { } else {
actualField.Set(reflect.ValueOf(decimalValue)) value := decimal.NewNullDecimal(decimalValue)
value.Valid = true
actualField.Set(reflect.ValueOf(value))
} }
} }
case "int64", "int": case "int64", "int":
@@ -175,6 +196,54 @@ func (a *ExcelAnalyzer[T]) Analysis(bean T) ([]T, []ExcelAnalysisError) {
} else { } else {
actualField.SetBool(false) actualField.SetBool(false)
} }
case "types.Date":
if len(matchValue) == 0 {
actualField.Set(reflect.ValueOf(types.NewEmptyDate()))
} else {
v, err := types.ParseDate(matchValue)
if err != nil {
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期格式。%w", err)}})
actualField.Set(reflect.ValueOf(types.NewEmptyDate()))
} else {
actualField.Set(reflect.ValueOf(v))
}
}
case "*types.Date":
if len(matchValue) == 0 {
actualField.Set(reflect.ValueOf(nil))
} else {
v, err := types.ParseDate(matchValue)
if err != nil {
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期格式。%w", err)}})
actualField.Set(reflect.ValueOf(nil))
} else {
actualField.Set(reflect.ValueOf(&v))
}
}
case "types.DateTime":
if len(matchValue) == 0 {
actualField.Set(reflect.ValueOf(types.NewEmptyDateTime()))
} else {
v, err := types.ParseDateTime(matchValue)
if err != nil {
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期时间格式。%w", err)}})
actualField.Set(reflect.ValueOf(types.NewEmptyDateTime()))
} else {
actualField.Set(reflect.ValueOf(v))
}
}
case "*types.DateTime":
if len(matchValue) == 0 {
actualField.Set(reflect.ValueOf(nil))
} else {
v, err := types.ParseDateTime(matchValue)
if err != nil {
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期时间格式。%w", err)}})
actualField.Set(reflect.ValueOf(nil))
} else {
actualField.Set(reflect.ValueOf(&v))
}
}
} }
} }
} }

View File

@@ -1,31 +0,0 @@
package excel
import (
"electricity_bill_calc/model"
"io"
)
var (
endUserNonPVRecognizers = []*ColumnRecognizer{
{Pattern: []string{"电表编号"}, Tag: "meterId", MatchIndex: -1},
{Pattern: []string{"本期", "(总)"}, Tag: "currentPeriodOverall", MatchIndex: -1},
{Pattern: []string{"退补", "(总)"}, Tag: "adjustOverall", MatchIndex: -1},
}
endUserPVRecognizers = append(
endUserNonPVRecognizers,
&ColumnRecognizer{Pattern: []string{"本期", "(尖峰)"}, Tag: "currentPeriodCritical", MatchIndex: -1},
&ColumnRecognizer{Pattern: []string{"本期", "(峰)"}, Tag: "currentPeriodPeak", MatchIndex: -1},
&ColumnRecognizer{Pattern: []string{"本期", "(谷)"}, Tag: "currentPeriodValley", MatchIndex: -1},
&ColumnRecognizer{Pattern: []string{"退补", "(尖峰)"}, Tag: "adjustCritical", MatchIndex: -1},
&ColumnRecognizer{Pattern: []string{"退补", "(峰)"}, Tag: "adjustPeak", MatchIndex: -1},
&ColumnRecognizer{Pattern: []string{"退补", "(谷)"}, Tag: "adjustValley", MatchIndex: -1},
)
)
func NewEndUserNonPVExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.EndUserImport], error) {
return NewExcelAnalyzer[model.EndUserImport](file, endUserNonPVRecognizers)
}
func NewEndUserPVExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.EndUserImport], error) {
return NewExcelAnalyzer[model.EndUserImport](file, endUserPVRecognizers)
}

View File

@@ -1,22 +1,139 @@
package excel package excel
import ( import (
"electricity_bill_calc/logger"
"electricity_bill_calc/model" "electricity_bill_calc/model"
"fmt"
"io" "io"
"github.com/samber/lo"
"github.com/xuri/excelize/v2"
"go.uber.org/zap"
) )
var meter04kVExcelRecognizers = []*ColumnRecognizer{ var meterArchiveRecognizers = []*ColumnRecognizer{
{Pattern: []string{"表号"}, Tag: "code", MatchIndex: -1}, {Pattern: [][]string{{"表号"}}, Tag: "code", MatchIndex: -1, MustFill: true},
{Pattern: []string{"户"}, Tag: "name", MatchIndex: -1}, {Pattern: [][]string{{"表址", "地址", "户"}}, Tag: "address", MatchIndex: -1},
{Pattern: []string{"户址"}, Tag: "address", MatchIndex: -1}, {Pattern: [][]string{{"类型"}}, Tag: "meterType", MatchIndex: -1, MustFill: true},
{Pattern: []string{"联系人"}, Tag: "contact", MatchIndex: -1}, {Pattern: [][]string{{"建筑"}}, Tag: "building", MatchIndex: -1},
{Pattern: []string{"电话"}, Tag: "phone", MatchIndex: -1}, {Pattern: [][]string{{"楼层"}}, Tag: "onFloor", MatchIndex: -1},
{Pattern: []string{"倍率"}, Tag: "ratio", MatchIndex: -1}, {Pattern: [][]string{{"面积"}}, Tag: "area", MatchIndex: -1},
{Pattern: []string{"序号"}, Tag: "seq", MatchIndex: -1}, {Pattern: [][]string{{"倍率"}}, Tag: "ratio", MatchIndex: -1, MustFill: true},
{Pattern: []string{"公用设备"}, Tag: "public", MatchIndex: -1}, {Pattern: [][]string{{"序号"}}, Tag: "seq", MatchIndex: -1, MustFill: true},
{Pattern: []string{"摊薄"}, Tag: "dilute", MatchIndex: -1}, {Pattern: [][]string{{"抄表"}, {"时间", "日期"}}, Tag: "readAt", MatchIndex: -1, MustFill: true},
{Pattern: [][]string{{"有功", "表底", "底数"}, {"总"}}, Tag: "overall", MatchIndex: -1, MustFill: true},
{Pattern: [][]string{{"有功", "表底", "底数"}, {"尖"}}, Tag: "critical", MatchIndex: -1},
{Pattern: [][]string{{"有功", "表底", "底数"}, {"峰"}}, Tag: "peak", MatchIndex: -1},
{Pattern: [][]string{{"有功", "表底", "底数"}, {"平"}}, Tag: "flat", MatchIndex: -1},
{Pattern: [][]string{{"有功", "表底", "底数"}, {"谷"}}, Tag: "valley", MatchIndex: -1},
} }
func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.Meter04KV], error) { func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.MeterImportRow], error) {
return NewExcelAnalyzer[model.Meter04KV](file, meter04kVExcelRecognizers) return NewExcelAnalyzer[model.MeterImportRow](file, meterArchiveRecognizers)
}
type MeterArchiveExcelTemplateGenerator struct {
file *excelize.File
log *zap.Logger
}
func NewMeterArchiveExcelTemplateGenerator() *MeterArchiveExcelTemplateGenerator {
return &MeterArchiveExcelTemplateGenerator{
file: excelize.NewFile(),
log: logger.Named("Excel", "MeterArchive"),
}
}
func (MeterArchiveExcelTemplateGenerator) titles() *[]interface{} {
return &[]interface{}{
"序号",
"表址",
"表号",
"表计类型",
"倍率",
"所在建筑",
"所在楼层",
"辖盖面积",
"抄表时间",
"有功(总)",
"有功(尖)",
"有功(峰)",
"有功(平)",
"有功(谷)",
}
}
func (g *MeterArchiveExcelTemplateGenerator) Close() {
g.file.Close()
}
func (g MeterArchiveExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) {
return g.file.WriteTo(w)
}
func (g *MeterArchiveExcelTemplateGenerator) WriteTemplateData(buildings []*model.ParkBuilding) error {
var err error
defaultSheet := g.file.GetSheetName(0)
g.log.Debug("选定默认输出表格", zap.String("sheet", defaultSheet))
err = g.file.SetColWidth(defaultSheet, "B", "I", 20)
if err != nil {
g.log.Error("未能设定长型列宽。", zap.Error(err))
return fmt.Errorf("未能设定长型列宽,%w", err)
}
err = g.file.SetColWidth(defaultSheet, "J", "N", 15)
if err != nil {
g.log.Error("未能设定短型列宽。", zap.Error(err))
return fmt.Errorf("未能设定短型列宽,%w", err)
}
err = g.file.SetSheetRow(defaultSheet, "A1", g.titles())
if err != nil {
g.log.Error("未能输出模板标题。", zap.Error(err))
return fmt.Errorf("未能输出模板标题,%w", err)
}
err = g.file.SetRowHeight(defaultSheet, 1, 20)
if err != nil {
g.log.Error("未能设定标题行高度。", zap.Error(err))
return fmt.Errorf("未能设定标题行高度,%w", err)
}
dateTimeExp := "yyyy-mm-dd hh:mm"
dateTimeColStyle, err := g.file.NewStyle(&excelize.Style{
CustomNumFmt: &dateTimeExp,
})
if err != nil {
g.log.Error("未能创建日期时间格式。", zap.Error(err))
return fmt.Errorf("未能创建日期时间格式,%w", err)
}
g.file.SetCellStyle(defaultSheet, "I2", "I1048576", dateTimeColStyle)
numExp := "0.0000"
numColStyle, err := g.file.NewStyle(&excelize.Style{
CustomNumFmt: &numExp,
})
if err != nil {
g.log.Error("未能创建抄表数字格式。", zap.Error(err))
return fmt.Errorf("未能创建抄表数字格式,%w", err)
}
g.file.SetCellStyle(defaultSheet, "J2", "N1048576", numColStyle)
meterInstallationTypeValidation := excelize.NewDataValidation(false)
meterInstallationTypeValidation.SetDropList([]string{"商户表", "公共表", "楼道表"})
meterInstallationTypeValidation.Sqref = "D2:D1048576"
err = g.file.AddDataValidation(defaultSheet, meterInstallationTypeValidation)
if err != nil {
g.log.Error("未能设定表计类型选择器。", zap.Error(err))
return fmt.Errorf("未能设定表计类型选择器,%w", err)
}
buildingValidation := excelize.NewDataValidation(true)
buildingNames := lo.Map(buildings, func(b *model.ParkBuilding, _ int) string {
return b.Name
})
buildingValidation.SetDropList(buildingNames)
buildingValidation.Sqref = "F2:F1048576"
err = g.file.AddDataValidation(defaultSheet, buildingValidation)
if err != nil {
g.log.Error("未能设定所在建筑选择器。", zap.Error(err))
return fmt.Errorf("未能设定所在建筑选择器,%w", err)
}
return nil
} }

View File

@@ -1,90 +0,0 @@
package excel
import (
"electricity_bill_calc/model"
"io"
"github.com/xuri/excelize/v2"
)
type MeterNonPVExcelTemplateGenerator struct {
file *excelize.File
}
// 生成峰谷计量抄表Excel模板
func NewMeterNonPVExcelTemplateGenerator() *MeterNonPVExcelTemplateGenerator {
return &MeterNonPVExcelTemplateGenerator{
file: excelize.NewFile(),
}
}
func (MeterNonPVExcelTemplateGenerator) titles() []interface{} {
return []interface{}{
"序号",
"用户名称",
"户址",
"电表编号",
"倍率",
"上期表底(总)",
"本期表底(总)",
"退补电量(总)",
}
}
func (t *MeterNonPVExcelTemplateGenerator) Close() {
t.file.Close()
}
func (t MeterNonPVExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) {
return t.file.WriteTo(w)
}
func (t *MeterNonPVExcelTemplateGenerator) WriteMeterData(meters []model.EndUserDetail) error {
defaultSheet := t.file.GetSheetName(0)
stream, err := t.file.NewStreamWriter(defaultSheet)
if err != nil {
return err
}
firstCell, err := excelize.CoordinatesToCellName(1, 1)
if err != nil {
return err
}
stream.SetColWidth(2, 4, 20)
stream.SetColWidth(6, 8, 15)
stream.SetRow(firstCell, t.titles(), excelize.RowOpts{Height: 20})
for index, meter := range meters {
firstCell, err := excelize.CoordinatesToCellName(1, index+2)
if err != nil {
return err
}
customerName := ""
if meter.CustomerName != nil {
customerName = *meter.CustomerName
}
customerAddress := ""
if meter.Address != nil {
customerAddress = *meter.Address
}
if err = stream.SetRow(
firstCell,
[]interface{}{
meter.Seq,
customerName,
customerAddress,
meter.MeterId,
meter.Ratio,
meter.LastPeriodOverall,
nil,
nil,
},
excelize.RowOpts{Height: 15},
); err != nil {
return err
}
}
if err = stream.Flush(); err != nil {
return err
}
return nil
}

View File

@@ -1,108 +0,0 @@
package excel
import (
"electricity_bill_calc/model"
"io"
"github.com/xuri/excelize/v2"
)
type MeterPVExcelTemplateGenerator struct {
file *excelize.File
}
// 生成峰谷计量抄表Excel模板
func NewMeterPVExcelTemplateGenerator() *MeterPVExcelTemplateGenerator {
return &MeterPVExcelTemplateGenerator{
file: excelize.NewFile(),
}
}
func (MeterPVExcelTemplateGenerator) titles() []interface{} {
return []interface{}{
"序号",
"用户名称",
"户址",
"电表编号",
"倍率",
"上期表底(总)",
"本期表底(总)",
"上期表底(尖峰)",
"本期表底(尖峰)",
"上期表底(峰)",
"本期表底(峰)",
"上期表底(谷)",
"本期表底(谷)",
"退补电量(总)",
"退补电量(尖峰)",
"退补电量(峰)",
"退补电量(谷)",
}
}
func (t *MeterPVExcelTemplateGenerator) Close() {
t.file.Close()
}
func (t MeterPVExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) {
return t.file.WriteTo(w)
}
func (t *MeterPVExcelTemplateGenerator) WriteMeterData(meters []model.EndUserDetail) error {
defaultSheet := t.file.GetSheetName(0)
stream, err := t.file.NewStreamWriter(defaultSheet)
if err != nil {
return err
}
firstCell, err := excelize.CoordinatesToCellName(1, 1)
if err != nil {
return err
}
stream.SetColWidth(2, 4, 20)
stream.SetColWidth(6, 17, 15)
stream.SetRow(firstCell, t.titles(), excelize.RowOpts{Height: 20})
for index, meter := range meters {
firstCell, err := excelize.CoordinatesToCellName(1, index+2)
if err != nil {
return err
}
customerName := ""
if meter.CustomerName != nil {
customerName = *meter.CustomerName
}
customerAddress := ""
if meter.Address != nil {
customerAddress = *meter.Address
}
if err = stream.SetRow(
firstCell,
[]interface{}{
meter.Seq,
customerName,
customerAddress,
meter.MeterId,
meter.Ratio,
meter.LastPeriodOverall,
nil,
meter.LastPeriodCritical,
nil,
meter.LastPeriodPeak,
nil,
meter.LastPeriodValley,
nil,
nil,
nil,
nil,
nil,
},
excelize.RowOpts{Height: 15},
); err != nil {
return err
}
}
if err = stream.Flush(); err != nil {
return err
}
return nil
}

131
excel/meter_reading.go Normal file
View File

@@ -0,0 +1,131 @@
package excel
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"fmt"
"io"
"github.com/xuri/excelize/v2"
"go.uber.org/zap"
)
var meterReadingsRecognizers = []*ColumnRecognizer{
{Pattern: [][]string{{"表", "表计"}, {"编号"}}, Tag: "code", MatchIndex: -1, MustFill: true},
{Pattern: [][]string{{"抄表", "结束"}, {"时间", "日期"}}, Tag: "readAt", MatchIndex: -1, MustFill: true},
{Pattern: [][]string{{"用电", "有功", "表底", "底数"}, {"总", "量"}}, Tag: "overall", MatchIndex: -1, MustFill: true},
{Pattern: [][]string{{"有功", "表底", "底数"}, {"尖"}}, Tag: "critical", MatchIndex: -1},
{Pattern: [][]string{{"有功", "表底", "底数"}, {"峰"}}, Tag: "peak", MatchIndex: -1},
{Pattern: [][]string{{"有功", "表底", "底数"}, {"谷"}}, Tag: "valley", MatchIndex: -1},
}
func NewMeterReadingsExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.ReadingImportRow], error) {
return NewExcelAnalyzer[model.ReadingImportRow](file, meterReadingsRecognizers)
}
type MeterReadingsExcelTemplateGenerator struct {
file *excelize.File
log *zap.Logger
}
func NewMeterReadingsExcelTemplateGenerator() *MeterReadingsExcelTemplateGenerator {
return &MeterReadingsExcelTemplateGenerator{
file: excelize.NewFile(),
log: logger.Named("Excel", "MeterReadings"),
}
}
func (MeterReadingsExcelTemplateGenerator) titles() *[]interface{} {
return &[]interface{}{
"抄表序号",
"抄表时间",
"表计编号",
"表计名称",
"商户名称",
"倍率",
"有功(总)",
"有功(尖)",
"有功(峰)",
"有功(谷)",
}
}
func (g MeterReadingsExcelTemplateGenerator) Close() {
g.file.Close()
}
func (g MeterReadingsExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) {
return g.file.WriteTo(w)
}
func (g MeterReadingsExcelTemplateGenerator) WriteTemplateData(meters []*model.SimpleMeterDocument) error {
var err error
defaultSheet := g.file.GetSheetName(0)
g.log.Debug("选定默认输出表格", zap.String("sheet", defaultSheet))
err = g.file.SetColWidth(defaultSheet, "A", "E", 30)
if err != nil {
g.log.Error("未能设定长型单元格的宽度。", zap.Error(err))
return fmt.Errorf("未能设定长型单元格的宽度,%w", err)
}
err = g.file.SetColWidth(defaultSheet, "F", "F", 10)
if err != nil {
g.log.Error("未能设定倍率单元格的宽度。", zap.Error(err))
return fmt.Errorf("未能设定倍率单元格的宽度,%w", err)
}
err = g.file.SetColWidth(defaultSheet, "G", "J", 20)
if err != nil {
g.log.Error("未能设定短型单元格的宽度。", zap.Error(err))
return fmt.Errorf("未能设定短型单元格的宽度,%w", err)
}
err = g.file.SetSheetRow(defaultSheet, "A1", g.titles())
if err != nil {
g.log.Error("未能输出模板标题。", zap.Error(err))
return fmt.Errorf("未能输出模板标题,%w", err)
}
err = g.file.SetRowHeight(defaultSheet, 1, 30)
if err != nil {
g.log.Error("未能设定标题行的高度。", zap.Error(err))
return fmt.Errorf("未能设定标题行的高度,%w", err)
}
dateTimeExp := "yyyy-mm-dd hh:mm"
dateTimeColStyle, err := g.file.NewStyle(&excelize.Style{
CustomNumFmt: &dateTimeExp,
})
if err != nil {
g.log.Error("未能创建日期时间格式。", zap.Error(err))
return fmt.Errorf("未能创建日期时间格式,%w", err)
}
endCellCoord, _ := excelize.CoordinatesToCellName(2, len(meters)+1)
g.file.SetCellStyle(defaultSheet, "B2", endCellCoord, dateTimeColStyle)
numExp := "0.0000"
numColStyle, err := g.file.NewStyle(&excelize.Style{
CustomNumFmt: &numExp,
})
if err != nil {
g.log.Error("未能创建抄表数字格式。", zap.Error(err))
return fmt.Errorf("未能创建抄表数字格式,%w", err)
}
endCellCoord, _ = excelize.CoordinatesToCellName(9, len(meters)+1)
g.file.SetCellStyle(defaultSheet, "F2", endCellCoord, numColStyle)
for i, meter := range meters {
cellCoord, _ := excelize.CoordinatesToCellName(1, i+2)
ratio, _ := meter.Ratio.Float64()
if err := g.file.SetSheetRow(defaultSheet, cellCoord, &[]interface{}{
meter.Seq,
"",
meter.Code,
tools.DefaultTo(meter.Address, ""),
tools.DefaultTo(meter.TenementName, ""),
ratio,
}); err != nil {
g.log.Error("向模板写入数据出现错误。", zap.Error(err))
return fmt.Errorf("向模板写入数据出现错误,%w", err)
}
}
return err
}

22
excel/tenement.go Normal file
View File

@@ -0,0 +1,22 @@
package excel
var tenementRecognizers = []*ColumnRecognizer{
{Pattern: [][]string{{"商户全称"}}, Tag: "fullName", MatchIndex: -1},
{Pattern: [][]string{{"联系地址"}}, Tag: "address", MatchIndex: -1},
{Pattern: [][]string{{"入驻时间"}}, Tag: "movedInAt", MatchIndex: -1},
{Pattern: [][]string{{"商铺名称"}}, Tag: "shortName", MatchIndex: -1},
{Pattern: [][]string{{"联系人"}}, Tag: "contactName", MatchIndex: -1},
{Pattern: [][]string{{"电话"}}, Tag: "contactPhone", MatchIndex: -1},
{Pattern: [][]string{{"USCI"}}, Tag: "usci", MatchIndex: -1},
{Pattern: [][]string{{"开票地址"}}, Tag: "invoiceAddress", MatchIndex: -1},
{Pattern: [][]string{{"账号"}}, Tag: "account", MatchIndex: -1},
{Pattern: [][]string{{"开户行"}}, Tag: "bank", MatchIndex: -1},
}
//func NewTenementExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.TenementImportRow], error) {
// return NewExcelAnalyzer[model.TenementImportRow](file, tenementRecognizers)
//}
//func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.MeterImportRow], error) {
// return NewExcelAnalyzer[model.MeterImportRow](file, meterArchiveRecognizers)
//}

View File

@@ -31,5 +31,5 @@ func NewUnauthorizedError(msg string) *UnauthorizedError {
} }
func (e UnauthorizedError) Error() string { func (e UnauthorizedError) Error() string {
return fmt.Sprintf("Unauthorized: %s", e.Message) return fmt.Sprintf("用户未获得授权: %s", e.Message)
} }

View File

@@ -15,5 +15,5 @@ func NewIllegalArgumentsError(msg string, arguments ...string) IllegalArgumentsE
} }
func (e IllegalArgumentsError) Error() string { func (e IllegalArgumentsError) Error() string {
return fmt.Sprintf("Illegal Arguments, %s", e.Message) return fmt.Sprintf("使用了非法参数, %s", e.Message)
} }

View File

@@ -15,5 +15,5 @@ func NewImproperOperateError(msg string, arguments ...string) ImproperOperateErr
} }
func (e ImproperOperateError) Error() string { func (e ImproperOperateError) Error() string {
return fmt.Sprintf("Improper Operate, %s", e.Message) return fmt.Sprintf("操作不恰当, %s", e.Message)
} }

View File

@@ -0,0 +1,16 @@
package exceptions
import "fmt"
type InsufficientDataError struct {
Field string
Message string
}
func NewInsufficientDataError(field, msg string) *InsufficientDataError {
return &InsufficientDataError{Field: field, Message: msg}
}
func (e InsufficientDataError) Error() string {
return fmt.Sprintf("字段 [%s] 数据不足,%s", e.Field, e.Message)
}

View File

@@ -11,7 +11,7 @@ func NewNotFoundError(msg string) *NotFoundError {
} }
func NewNotFoundErrorFromError(msg string, err error) *NotFoundError { func NewNotFoundErrorFromError(msg string, err error) *NotFoundError {
return &NotFoundError{Message: fmt.Sprintf("%s%v", msg, err)} return &NotFoundError{Message: fmt.Sprintf("所需数据未找到,%s%v", msg, err)}
} }
func (e NotFoundError) Error() string { func (e NotFoundError) Error() string {

View File

@@ -1,11 +1,130 @@
package exceptions package exceptions
type UnsuccessfulOperationError struct{} import (
"strings"
)
func NewUnsuccessfulOperationError() *UnsuccessfulOperationError { type OperationType int16
return &UnsuccessfulOperationError{}
const (
OPERATE_CREATE OperationType = iota
OPERATE_UPDATE
OPERATE_DELETE
OEPRATE_QUERY
OPERATE_CALCULATE
OPERATE_DB
OPERATE_DB_TRANSACTION
OPERATE_CUSTOM OperationType = 98
OPERATE_OTHER OperationType = 99
)
type UnsuccessfulOperationError struct {
Operate OperationType
Description string
Message string
} }
func (UnsuccessfulOperationError) Error() string { func NewUnsuccessfulOperationError(oeprate OperationType, describe, message string) *UnsuccessfulOperationError {
return "Unsuccessful Operation" return &UnsuccessfulOperationError{
Operate: oeprate,
Description: describe,
Message: message,
}
}
func NewUnsuccessCreateError(message string) *UnsuccessfulOperationError {
return &UnsuccessfulOperationError{
Operate: OPERATE_CREATE,
Message: message,
}
}
func NewUnsuccessUpdateError(message string) *UnsuccessfulOperationError {
return &UnsuccessfulOperationError{
Operate: OPERATE_UPDATE,
Message: message,
}
}
func NewUnsuccessDeleteError(message string) *UnsuccessfulOperationError {
return &UnsuccessfulOperationError{
Operate: OPERATE_DELETE,
Message: message,
}
}
func NewUnsuccessQueryError(message string) *UnsuccessfulOperationError {
return &UnsuccessfulOperationError{
Operate: OEPRATE_QUERY,
Message: message,
}
}
func NewUnsuccessCalculateError(message string) *UnsuccessfulOperationError {
return &UnsuccessfulOperationError{
Operate: OPERATE_CALCULATE,
Message: message,
}
}
func NewUnsuccessDBError(message string) *UnsuccessfulOperationError {
return &UnsuccessfulOperationError{
Operate: OPERATE_DB,
Message: message,
}
}
func NewUnsuccessDBTransactionError(message string) *UnsuccessfulOperationError {
return &UnsuccessfulOperationError{
Operate: OPERATE_DB_TRANSACTION,
Message: message,
}
}
func NewUnsuccessCustomError(describe, message string) *UnsuccessfulOperationError {
return &UnsuccessfulOperationError{
Operate: OPERATE_CUSTOM,
Description: describe,
Message: message,
}
}
func NewUnsuccessOtherError(message string) *UnsuccessfulOperationError {
return &UnsuccessfulOperationError{
Operate: OPERATE_OTHER,
Message: message,
}
}
func (e UnsuccessfulOperationError) Error() string {
var builder strings.Builder
switch e.Operate {
case OPERATE_CREATE:
builder.WriteString("创建")
case OPERATE_UPDATE:
builder.WriteString("更新")
case OPERATE_DELETE:
builder.WriteString("删除")
case OEPRATE_QUERY:
builder.WriteString("查询")
case OPERATE_CALCULATE:
builder.WriteString("计算")
case OPERATE_DB:
builder.WriteString("数据库")
case OPERATE_DB_TRANSACTION:
builder.WriteString("数据库事务")
case OPERATE_CUSTOM:
builder.WriteString(e.Description)
case OPERATE_OTHER:
builder.WriteString("其他")
default:
builder.WriteString("未知")
}
builder.WriteString("操作不成功,")
if len(e.Message) > 0 {
builder.WriteString(e.Message)
} else {
builder.WriteString("未知原因")
}
return builder.String()
} }

16
global/context.go Normal file
View File

@@ -0,0 +1,16 @@
package global
import (
"context"
"time"
)
// 生成一个超时时间为5秒的倍率的上下文如果不传递任何值默认生成6倍的上下文即超时时间为30秒。
func TimeoutContext(multiply ...int64) (context.Context, context.CancelFunc) {
var ratio int64 = 6
if len(multiply) > 0 {
ratio = multiply[0]
}
timeout := time.Duration(ratio*5) * time.Second
return context.WithTimeout(context.TODO(), timeout)
}

View File

@@ -1,55 +1,89 @@
package global package global
import ( import (
"context"
"fmt" "fmt"
"strings"
"time"
"electricity_bill_calc/config" "electricity_bill_calc/config"
"electricity_bill_calc/logger"
// _ "github.com/lib/pq" "github.com/jackc/pgx/v5"
_ "github.com/jackc/pgx/v5/stdlib" "github.com/jackc/pgx/v5/pgxpool"
"xorm.io/xorm" "github.com/samber/lo"
"xorm.io/xorm/log" "go.uber.org/zap"
) )
var ( var (
DBConn *xorm.Engine DB *pgxpool.Pool
) )
func SetupDatabaseConnection() error { func SetupDatabaseConnection() error {
var err error connString := fmt.Sprintf(
// 以下连接方式是采用pgx驱动的时候使用的。 "postgres://%s:%s@%s:%d/%s?sslmode=disable&connect_timeout=%d&application_name=%s&pool_max_conns=%d&pool_min_conns=%d&pool_max_conn_lifetime=%s&pool_max_conn_idle_time=%s&pool_health_check_period=%s",
DBConn, err = xorm.NewEngine("pgx", fmt.Sprintf(
"postgresql://%s:%s@%s:%d/%s?sslmode=disable&",
config.DatabaseSettings.User, config.DatabaseSettings.User,
config.DatabaseSettings.Pass, config.DatabaseSettings.Pass,
config.DatabaseSettings.Host, config.DatabaseSettings.Host,
config.DatabaseSettings.Port, config.DatabaseSettings.Port,
config.DatabaseSettings.DB, config.DatabaseSettings.DB,
)) 0,
// 以下连接方式是采用lib/pq驱动的时候使用的。 "elec_service_go",
// DBConn, err = xorm.NewEngine("postgres", fmt.Sprintf( config.DatabaseSettings.MaxOpenConns,
// "host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai connect_timeout=0", config.DatabaseSettings.MaxIdleConns,
// config.DatabaseSettings.Host, "60m",
// config.DatabaseSettings.User, "10m",
// config.DatabaseSettings.Pass, "10s",
// config.DatabaseSettings.DB, )
// config.DatabaseSettings.Port, poolConfig, err := pgxpool.ParseConfig(connString)
// ))
if err != nil { if err != nil {
logger.Named("DB INIT").Error("数据库连接初始化失败。", zap.Error(err))
return err return err
} }
poolConfig.ConnConfig.Tracer = QueryLogger{logger: logger.Named("PG")}
DBConn.Ping() DB, _ = pgxpool.NewWithConfig(context.Background(), poolConfig)
DBConn.SetMaxIdleConns(config.DatabaseSettings.MaxIdleConns)
DBConn.SetMaxOpenConns(config.DatabaseSettings.MaxOpenConns)
DBConn.SetConnMaxLifetime(60 * time.Second)
if strings.ToLower(config.ServerSettings.RunMode) == "debug" {
DBConn.ShowSQL(true)
DBConn.Logger().SetLevel(log.LOG_DEBUG)
}
return nil return nil
} }
type QueryLogger struct {
logger *zap.Logger
}
func (ql QueryLogger) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
ql.logger.Info(fmt.Sprintf("将要执行查询: %s", data.SQL))
ql.logger.Info("查询参数", lo.Map(data.Args, func(elem any, index int) zap.Field {
return zap.Any(fmt.Sprintf("[Arg %d]: ", index), elem)
})...)
return ctx
}
func (ql QueryLogger) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
var logFunc func(string, ...zap.Field)
var templateString string
if data.Err != nil {
logFunc = ql.logger.Error
templateString = "命令 [%s] 执行失败。"
} else {
logFunc = ql.logger.Info
templateString = "命令 [%s] 执行成功。"
}
switch {
case data.CommandTag.Update():
fallthrough
case data.CommandTag.Delete():
fallthrough
case data.CommandTag.Insert():
logFunc(
fmt.Sprintf(templateString, data.CommandTag.String()),
zap.Error(data.Err),
zap.Any("affected", data.CommandTag.RowsAffected()))
case data.CommandTag.Select():
logFunc(
fmt.Sprintf(templateString, data.CommandTag.String()),
zap.Error(data.Err))
default:
logFunc(
fmt.Sprintf(templateString, data.CommandTag.String()),
zap.Error(data.Err))
}
}

View File

@@ -9,22 +9,25 @@ import (
) )
var ( var (
RedisConn rueidis.Client Rd rueidis.Client
Ctx = context.Background() Ctx = context.Background()
) )
func SetupRedisConnection() error { func SetupRedisConnection() error {
var err error var err error
RedisConn, err = rueidis.NewClient(rueidis.ClientOption{ a := fmt.Sprintf("%s:%d", config.RedisSettings.Host, config.RedisSettings.Port)
InitAddress: []string{fmt.Sprintf("%s:%d", config.RedisSettings.Host, config.RedisSettings.Port)}, fmt.Println(a)
Password: config.RedisSettings.Password, Rd, err = rueidis.NewClient(rueidis.ClientOption{
SelectDB: config.RedisSettings.DB, InitAddress: []string{a},
Password: config.RedisSettings.Password,
SelectDB: config.RedisSettings.DB,
DisableCache: true,
}) })
if err != nil { if err != nil {
return err return err
} }
pingCmd := RedisConn.B().Ping().Build() pingCmd := Rd.B().Ping().Build()
result := RedisConn.Do(Ctx, pingCmd) result := Rd.Do(Ctx, pingCmd)
if result.Error() != nil { if result.Error() != nil {
return result.Error() return result.Error()
} }

90
go.mod
View File

@@ -3,63 +3,71 @@ module electricity_bill_calc
go 1.19 go 1.19
require ( require (
github.com/deckarep/golang-set/v2 v2.1.0 github.com/fufuok/utils v0.10.2
github.com/fufuok/utils v0.7.13 github.com/georgysavva/scany/v2 v2.0.0
github.com/gin-gonic/gin v1.8.1 github.com/gofiber/fiber/v2 v2.46.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/jackc/pgx/v5 v5.0.0-beta.1 github.com/jackc/pgx/v5 v5.3.1
github.com/jinzhu/copier v0.3.5 github.com/jinzhu/copier v0.3.5
github.com/liamylian/jsontime/v2 v2.0.0 github.com/liamylian/jsontime/v2 v2.0.0
github.com/mozillazg/go-pinyin v0.19.0 github.com/mozillazg/go-pinyin v0.20.0
github.com/rueian/rueidis v0.0.73 github.com/pkg/errors v0.9.1
github.com/samber/lo v1.27.0 github.com/rueian/rueidis v0.0.100
github.com/samber/lo v1.38.1
github.com/shopspring/decimal v1.3.1 github.com/shopspring/decimal v1.3.1
github.com/spf13/viper v1.12.0 github.com/spf13/viper v1.16.0
github.com/xuri/excelize/v2 v2.6.1 github.com/valyala/fasthttp v1.47.0
xorm.io/builder v0.3.12 github.com/xuri/excelize/v2 v2.7.1
xorm.io/xorm v1.3.1 go.uber.org/zap v1.24.0
golang.org/x/sync v0.2.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )
require ( require (
github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/andybalholm/brotli v1.0.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.0 // indirect
github.com/goccy/go-json v0.9.10 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.0 // indirect
github.com/klauspost/compress v1.16.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
)
require (
github.com/doug-martin/goqu/v9 v9.18.0
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.3 // indirect github.com/richardlehane/msoleps v1.0.3 // indirect
github.com/spf13/afero v1.8.2 // indirect github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.3.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83 // indirect
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect go.uber.org/atomic v1.11.0 // indirect
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect golang.org/x/crypto v0.10.0 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 // indirect golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.10.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

751
go.sum

File diff suppressed because it is too large Load Diff

184
logger/logger.go Normal file
View File

@@ -0,0 +1,184 @@
package logger
import (
"electricity_bill_calc/types"
"os"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
logger *zap.Logger
sugaredLogger *zap.SugaredLogger
)
func init() {
consoleWriterSync := zapcore.AddSync(os.Stderr)
rollingWriterSync := zapcore.AddSync(newRollingWriter())
consoleEncoderConfig := zap.NewProductionEncoderConfig()
consoleEncoderConfig.EncodeTime = zapcore.RFC3339TimeEncoder
consoleEncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
jsonEncoderConfig := zap.NewProductionEncoderConfig()
jsonEncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
consoleEncoder := zapcore.NewConsoleEncoder(consoleEncoderConfig)
jsonEncoder := zapcore.NewJSONEncoder(jsonEncoderConfig)
core := zapcore.NewTee(
zapcore.NewCore(
consoleEncoder,
consoleWriterSync,
zapcore.DebugLevel,
),
zapcore.NewCore(
jsonEncoder,
rollingWriterSync,
zapcore.DebugLevel,
),
)
logger = zap.New(core).Named("App")
sugaredLogger = logger.Sugar()
logger.Info("日志系统初始化完成。")
}
func GetLogger() *zap.Logger {
return logger
}
func Panic(msg string, fields ...zap.Field) {
logger.Panic(msg, fields...)
}
func Fatal(msg string, fields ...zap.Field) {
logger.Fatal(msg, fields...)
}
func Error(msg string, fields ...zap.Field) {
logger.Error(msg, fields...)
}
func Warn(msg string, fields ...zap.Field) {
logger.Warn(msg, fields...)
}
func Info(msg string, fields ...zap.Field) {
logger.Info(msg, fields...)
}
func Debug(msg string, fields ...zap.Field) {
logger.Debug(msg, fields...)
}
func Panicr(v ...interface{}) {
sugaredLogger.Panic(v...)
}
func Panicf(format string, v ...interface{}) {
sugaredLogger.Panicf(format, v...)
}
func Errorr(v ...interface{}) {
sugaredLogger.Panic(v...)
}
func Errorf(format string, v ...interface{}) {
sugaredLogger.Panicf(format, v...)
}
func Warnr(v ...interface{}) {
sugaredLogger.Warn(v...)
}
func Warnf(format string, v ...interface{}) {
sugaredLogger.Warnf(format, v...)
}
func Infor(v ...interface{}) {
sugaredLogger.Info(v...)
}
func Infof(format string, v ...interface{}) {
sugaredLogger.Infof(format, v...)
}
func Debugr(v ...interface{}) {
sugaredLogger.Debug(v...)
}
func Debugf(format string, v ...interface{}) {
sugaredLogger.Debugf(format, v...)
}
func Named(names ...string) *zap.Logger {
var l = logger
for _, name := range names {
l = l.Named(name)
}
return l
}
func NamedSugar(names ...string) *zap.SugaredLogger {
return Named(names...).Sugar()
}
func With(fields ...zap.Field) *zap.Logger {
return logger.With(fields...)
}
func WithSugar(fields ...zap.Field) *zap.SugaredLogger {
return logger.With(fields...).Sugar()
}
func DecimalField(key string, val decimal.Decimal) zap.Field {
return zap.String(key, val.String())
}
func DecimalFieldp(key string, val *decimal.Decimal) zap.Field {
if val == nil {
return zap.Stringp(key, nil)
}
return DecimalField(key, *val)
}
func NullDecimalField(key string, val decimal.NullDecimal) zap.Field {
if val.Valid {
return DecimalField(key, val.Decimal)
}
return zap.Stringp(key, nil)
}
func NullDecimalFieldp(key string, val *decimal.NullDecimal) zap.Field {
if val == nil {
return zap.Stringp(key, nil)
}
if val.Valid {
return DecimalField(key, val.Decimal)
}
return zap.Stringp(key, nil)
}
func DateField(key string, val types.Date) zap.Field {
return val.Log(key)
}
func DateFieldp(key string, val *types.Date) zap.Field {
if val == nil {
return zap.Stringp(key, nil)
}
return DateField(key, *val)
}
func DateTimeField(key string, val types.DateTime) zap.Field {
return val.Log(key)
}
func DateTimeFieldp(key string, val *types.DateTime) zap.Field {
if val == nil {
return zap.Stringp(key, nil)
}
return DateTimeField(key, *val)
}

91
logger/middleware.go Normal file
View File

@@ -0,0 +1,91 @@
package logger
import (
"os"
"strconv"
"sync"
"time"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
// Config defines the config for middleware
type LogMiddlewareConfig struct {
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c *fiber.Ctx) bool
// Logger defines zap logger instance
Logger *zap.Logger
}
// New creates a new middleware handler
func NewLogMiddleware(config LogMiddlewareConfig) fiber.Handler {
var (
errPadding = 15
start, stop time.Time
once sync.Once
errHandler fiber.ErrorHandler
)
return func(c *fiber.Ctx) error {
if config.Next != nil && config.Next(c) {
return c.Next()
}
once.Do(func() {
errHandler = c.App().Config().ErrorHandler
stack := c.App().Stack()
for m := range stack {
for r := range stack[m] {
if len(stack[m][r].Path) > errPadding {
errPadding = len(stack[m][r].Path)
}
}
}
})
start = time.Now()
chainErr := c.Next()
if chainErr != nil {
if err := errHandler(c, chainErr); err != nil {
_ = c.SendStatus(fiber.StatusInternalServerError)
}
}
stop = time.Now()
fields := []zap.Field{
zap.Namespace("context"),
zap.String("pid", strconv.Itoa(os.Getpid())),
zap.String("method", c.Method()),
zap.String("remote", c.IP()),
zap.Strings("forwarded", c.IPs()),
zap.String("url", c.OriginalURL()),
zap.String("time", stop.Sub(start).String()),
// zap.Object("response", Resp(c.Response())),
// zap.Object("request", Req(c)),
}
if u := c.Locals("userId"); u != nil {
fields = append(fields, zap.Uint("userId", u.(uint)))
}
formatErr := ""
if chainErr != nil {
formatErr = chainErr.Error()
fields = append(fields, zap.String("error", formatErr))
config.Logger.With(fields...).Error(formatErr)
return nil
}
config.Logger.With(fields...).Info("api.request")
return nil
}
}

119
logger/middleware_types.go Normal file
View File

@@ -0,0 +1,119 @@
package logger
import (
"bytes"
"encoding/json"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/valyala/fasthttp"
"go.uber.org/zap/zapcore"
)
func getAllowedHeaders() map[string]bool {
return map[string]bool{
"User-Agent": true,
"X-Mobile": true,
}
}
type resp struct {
code int
_type string
}
func Resp(r *fasthttp.Response) *resp {
return &resp{
code: r.StatusCode(),
_type: bytes.NewBuffer(r.Header.ContentType()).String(),
}
}
func (r *resp) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddString("type", r._type)
enc.AddInt("code", r.code)
return nil
}
type req struct {
body string
fullPath string
user string
ip string
method string
route string
headers *headerbag
}
func Req(c *fiber.Ctx) *req {
reqq := c.Request()
var body []byte
buffer := new(bytes.Buffer)
err := json.Compact(buffer, reqq.Body())
if err != nil {
body = reqq.Body()
} else {
body = buffer.Bytes()
}
headers := &headerbag{
vals: make(map[string]string),
}
allowedHeaders := getAllowedHeaders()
reqq.Header.VisitAll(func(key, val []byte) {
k := bytes.NewBuffer(key).String()
if _, exist := allowedHeaders[k]; exist {
headers.vals[strings.ToLower(k)] = bytes.NewBuffer(val).String()
}
})
var userEmail string
if u := c.Locals("userEmail"); u != nil {
userEmail = u.(string)
}
return &req{
body: bytes.NewBuffer(body).String(),
fullPath: bytes.NewBuffer(reqq.RequestURI()).String(),
headers: headers,
ip: c.IP(),
method: c.Method(),
route: c.Route().Path,
user: userEmail,
}
}
func (r *req) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddString("fullPath", r.fullPath)
enc.AddString("ip", r.ip)
enc.AddString("method", r.method)
enc.AddString("route", r.route)
if r.body != "" {
enc.AddString("body", r.body)
}
if r.user != "" {
enc.AddString("user", r.user)
}
err := enc.AddObject("headers", r.headers)
if err != nil {
return err
}
return nil
}
type headerbag struct {
vals map[string]string
}
func (h *headerbag) MarshalLogObject(enc zapcore.ObjectEncoder) error {
for k, v := range h.vals {
enc.AddString(k, v)
}
return nil
}

27
logger/rolling.go Normal file
View File

@@ -0,0 +1,27 @@
package logger
import (
"fmt"
"io"
"log"
"math"
"os"
"time"
"gopkg.in/natefinch/lumberjack.v2"
)
func newRollingWriter() io.Writer {
if err := os.MkdirAll("log", 0744); err != nil {
log.Println("不能创建用于保存日志的目录。")
return nil
}
now := time.Now()
return &lumberjack.Logger{
Filename: fmt.Sprintf("log/service_%s.log", now.Format("2006-01-02_15")),
MaxBackups: math.MaxInt, // files
MaxSize: 200, // megabytes
MaxAge: math.MaxInt, // days
}
}

160
main.go
View File

@@ -1,165 +1,103 @@
package main package main
import ( import (
"electricity_bill_calc/cache"
"electricity_bill_calc/config" "electricity_bill_calc/config"
"electricity_bill_calc/global" "electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model" "electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/router" "electricity_bill_calc/router"
"electricity_bill_calc/service" "electricity_bill_calc/service"
"encoding/csv" "electricity_bill_calc/types"
"fmt" "fmt"
"io"
"log"
"os"
"strconv"
"time" "time"
"github.com/gin-gonic/gin" "go.uber.org/zap"
jsontime "github.com/liamylian/jsontime/v2/v2"
"github.com/samber/lo"
"github.com/shopspring/decimal"
) )
func init() { func init() {
l := logger.Named("Init")
err := config.SetupSetting() err := config.SetupSetting()
if err != nil { if err != nil {
log.Fatalf("Configuration load failed: %v", err) l.Fatal("服务配置文件加载失败!", zap.Error(err))
} }
log.Println("Configuration loaded!") l.Info("服务配置已经完成加载。")
err = global.SetupDatabaseConnection() err = global.SetupDatabaseConnection()
if err != nil { if err != nil {
log.Fatalf("Main Database connect failed: %v", err) l.Fatal("主数据库连接失败!", zap.Error(err))
} }
log.Println("Main Database connected!") l.Info("主数据库已经连接。")
err = global.DBConn.Sync(
&model.Region{},
&model.User{},
&model.UserDetail{},
&model.UserCharge{},
&model.Park{},
&model.Meter04KV{},
&model.MaintenanceFee{},
&model.Report{},
&model.ReportSummary{},
&model.WillDilutedFee{},
&model.EndUserDetail{})
if err != nil {
log.Fatalf("Database structure synchronize failed: %v", err)
}
log.Println("Database structure synchronized.")
err = global.SetupRedisConnection() err = global.SetupRedisConnection()
if err != nil { if err != nil {
log.Fatalf("Main Cache Database connect failed: %v", err) l.Fatal("主缓存数据库连接失败!", zap.Error(err))
} }
log.Println("Main Cache Database connected!") l.Info("主缓存数据库已经连接。")
err = initializeRegions()
if err != nil {
log.Fatalf("Regions initialize failed: %v", err)
}
log.Println("Regions synchronized.")
err = intializeSingularity() err = intializeSingularity()
if err != nil { if err != nil {
log.Fatalf("Singularity account intialize failed: %v", err) l.Fatal("奇点账号初始化失败。", zap.Error(err))
} }
log.Println("Singularity account intialized.") l.Info("奇点账号已经完成初始化。")
timeZoneShanghai, _ := time.LoadLocation("Asia/Shanghai")
jsontime.AddTimeFormatAlias("simple_datetime", "2006-01-02 15:04:05")
jsontime.AddTimeFormatAlias("simple_date", "2006-01-02")
jsontime.AddLocaleAlias("shanghai", timeZoneShanghai)
}
func initializeRegions() error {
log.Println("Synchronize regions...")
regionCsvFile, err := os.Open("regions.csv")
if err != nil {
return fmt.Errorf("region initialize file is not found: %w", err)
}
defer regionCsvFile.Close()
var existRegions = make([]string, 0)
err = global.DBConn.Table("region").Cols("code").Find(&existRegions)
if err != nil {
return fmt.Errorf("unable to retreive regions from database: %w", err)
}
regionCsv := csv.NewReader(regionCsvFile)
transaction := global.DBConn.NewSession()
defer transaction.Close()
if err = transaction.Begin(); err != nil {
return fmt.Errorf("unable to intiate database transaction: %w", err)
}
for {
record, err := regionCsv.Read()
if err == io.EOF {
break
}
if lo.Contains(existRegions, record[0]) {
continue
}
level, err := strconv.Atoi(record[2])
if err != nil {
continue
}
if _, err = transaction.Insert(&model.Region{Code: record[0], Name: record[1], Level: level, Parent: record[3]}); err != nil {
return fmt.Errorf("region synchronize in failed: %v, %w", record, err)
}
}
if err = transaction.Commit(); err != nil {
return fmt.Errorf("synchronize regions to database failed: %w", err)
}
return nil
} }
func intializeSingularity() error { func intializeSingularity() error {
singularityExists, err := service.UserService.IsUserExists("000") l := logger.Named("Init", "Singularity")
singularityExists, err := repository.UserRepository.IsUserExists("000")
if err != nil { if err != nil {
return fmt.Errorf("singularity detect failed: %w", err) l.Error("检测奇点账号失败。", zap.Error(err))
return fmt.Errorf("检测奇点账号失败: %w", err)
} }
if singularityExists { if singularityExists {
l.Info("奇点账号已经存在,跳过剩余初始化步骤。")
return nil return nil
} }
singularity := &model.User{ singularityId := "000"
Id: "000", singularityExpires, err := types.ParseDate("2099-12-31")
if err != nil {
l.Error("奇点用户账号过期时间解析失败。", zap.Error(err))
return fmt.Errorf("奇点用户账号过期时间解析失败: %w", err)
}
singularity := &model.ManagementAccountCreationForm{
Id: &singularityId,
Username: "singularity", Username: "singularity",
Name: "Singularity",
Type: 2, Type: 2,
Enabled: true, Enabled: true,
Expires: singularityExpires,
} }
singularityName := "Singularity" verifyCode, err := service.UserService.CreateUserAccount(
singularityExpires, err := time.Parse("2006-01-02 15:04:05", "2099-12-31 23:59:59") singularity.IntoUser(),
singularity.IntoUserDetail())
if err != nil { if err != nil {
return fmt.Errorf("singularity expires time parse failed: %w", err) l.Error("创建奇点账号失败。", zap.Error(err))
return fmt.Errorf("创建奇点账号失败: %w", err)
} }
singularityDetail := &model.UserDetail{ logger.Info(
Name: &singularityName, fmt.Sprintf("奇点账号已经完成创建, 首次登录需要使用验证码 [%s] 重置密码。", *verifyCode),
UnitServiceFee: decimal.Zero, zap.String("账号名称", "singularity"),
ServiceExpiration: singularityExpires, zap.String("验证码", *verifyCode),
} )
verifyCode, err := service.UserService.CreateUser(singularity, singularityDetail)
if err != nil {
return fmt.Errorf("singularity account failed to create: %w", err)
}
log.Printf("Singularity account created, use %s as verify code to reset password.", verifyCode)
return nil return nil
} }
func DBConnectionKeepLive() { // 清理Redis缓存中的孤儿键。
for range time.Tick(30 * time.Second) { func RedisOrphanCleanup() {
err := global.DBConn.Ping() cleanLogger := logger.Named("Cache").With(zap.String("function", "Cleanup"))
for range time.Tick(2 * time.Minute) {
cleanLogger.Info("Proceeding cleanup orphan keys.")
err := cache.ClearOrphanRelationItems()
if err != nil { if err != nil {
cleanLogger.Error("Orphan keys clear failed.")
continue continue
} }
} }
} }
func main() { func main() {
go DBConnectionKeepLive() go RedisOrphanCleanup()
gin.SetMode(config.ServerSettings.RunMode) app := router.App()
r := router.Router() app.Listen(fmt.Sprintf(":%d", config.ServerSettings.HttpPort))
r.Run(fmt.Sprintf(":%d", config.ServerSettings.HttpPort))
} }

View File

@@ -0,0 +1,214 @@
package calculate
import (
"electricity_bill_calc/model"
"electricity_bill_calc/types"
"fmt"
"github.com/shopspring/decimal"
)
type Reading struct {
ReadAt types.DateTime
Ratio decimal.Decimal
Overall decimal.Decimal
Critical decimal.Decimal
Peak decimal.Decimal
Flat decimal.Decimal
Valley decimal.Decimal
}
type Pooling struct {
Code string
Detail model.ConsumptionUnit
}
type Meter struct {
Code string `json:"code"`
Detail model.MeterDetail `json:"detail"`
CoveredArea decimal.Decimal `json:"covered_area"`
LastTermReading *Reading `json:"last_term_reading"`
CurrentTermReading *Reading `json:"current_term_reading"`
Overall model.ConsumptionUnit `json:"overall"`
Critical model.ConsumptionUnit `json:"critical"`
Peak model.ConsumptionUnit `json:"peak"`
Flat model.ConsumptionUnit `json:"flat"`
Valley model.ConsumptionUnit `json:"valley"`
AdjustLoss model.ConsumptionUnit `json:"adjust_loss"`
PooledBasic model.ConsumptionUnit `json:"pooled_basic"`
PooledAdjust model.ConsumptionUnit `json:"pooled_adjust"`
PooledLoss model.ConsumptionUnit `json:"pooled_loss"`
PooledPublic model.ConsumptionUnit `json:"pooled_public"`
SharedPoolingProportion decimal.Decimal `json:"shared_pooling_proportion"`
Poolings []*Pooling `json:"poolings"`
}
type PrimaryTenementStatistics struct {
Tenement model.Tenement
Meters []Meter
}
type TenementCharge struct {
Tenement string `json:"tenement"`
Overall model.ConsumptionUnit `json:"overall"`
Critical model.ConsumptionUnit `json:"critical"`
Peak model.ConsumptionUnit `json:"peak"`
Flat model.ConsumptionUnit `json:"flat"`
Valley model.ConsumptionUnit `json:"valley"`
BasicFee decimal.Decimal `json:"basic_fee"`
AdjustFee decimal.Decimal `json:"adjust_fee"`
LossPooled decimal.Decimal `json:"loss_pooled"`
PublicPooled decimal.Decimal `json:"public_pooled"`
FinalCharges decimal.Decimal `json:"final_charges"`
Loss model.ConsumptionUnit `json:"loss"`
Submeters []*Meter `json:"submeters"`
Poolings []*Meter `json:"poolings"`
}
type Summary struct {
ReportId string
OverallArea decimal.Decimal
Overall model.ConsumptionUnit
ConsumptionFee decimal.Decimal
Critical model.ConsumptionUnit
Peak model.ConsumptionUnit
Flat model.ConsumptionUnit
Valley model.ConsumptionUnit
Loss decimal.Decimal
LossFee decimal.Decimal
LossProportion decimal.Decimal
AuthoizeLoss model.ConsumptionUnit
BasicFee decimal.Decimal
BasicPooledPriceConsumption decimal.Decimal
BasicPooledPriceArea decimal.Decimal
AdjustFee decimal.Decimal
AdjustPooledPriceConsumption decimal.Decimal
AdjustPooledPriceArea decimal.Decimal
LossDilutedPrice decimal.Decimal
TotalConsumption decimal.Decimal
FinalDilutedOverall decimal.Decimal
}
type PoolingSummary struct {
Tenement string
Meter string
TargetMeter string
Area decimal.NullDecimal
OverallAmount decimal.Decimal
PoolingProportion decimal.Decimal
}
func FromReportSummary(summary *model.ReportSummary, pricingMode *model.ReportIndex) Summary {
var parkPrice float64
switch pricingMode.PricePolicy {
case model.PRICING_POLICY_CONSUMPTION:
parkPrice = summary.ConsumptionFee.Decimal.InexactFloat64() / summary.Overall.Amount.InexactFloat64()
case model.PRICING_POLICY_ALL:
parkPrice = summary.Overall.Fee.InexactFloat64() / summary.Overall.Amount.InexactFloat64()
default:
fmt.Println("无法识别类型")
}
flatAmount := summary.Overall.Amount.InexactFloat64() -
summary.Critical.Amount.InexactFloat64() -
summary.Peak.Amount.InexactFloat64() -
summary.Valley.Amount.InexactFloat64()
flatFee := summary.Overall.Amount.InexactFloat64() -
summary.Critical.Fee.InexactFloat64() -
summary.Peak.Fee.InexactFloat64() -
summary.Valley.Fee.InexactFloat64()
var OverallPrice float64
if summary.Overall.Amount.GreaterThan(decimal.Zero) {
OverallPrice = parkPrice
} else {
OverallPrice = decimal.Zero.InexactFloat64()
}
var CriticalPrice float64
if summary.Critical.Amount.GreaterThan(decimal.Zero) {
CriticalPrice = summary.Critical.Fee.InexactFloat64() / summary.Critical.Amount.InexactFloat64()
} else {
CriticalPrice = decimal.Zero.InexactFloat64()
}
var PeakPrice float64
if summary.Peak.Amount.GreaterThan(decimal.Zero) {
PeakPrice = summary.Peak.Fee.InexactFloat64() / summary.Peak.Amount.InexactFloat64()
} else {
PeakPrice = decimal.Zero.InexactFloat64()
}
var FlatPrice float64
if decimal.NewFromFloat(flatAmount).GreaterThan(decimal.Zero) {
FlatPrice = flatFee / flatAmount
} else {
FlatPrice = decimal.Zero.InexactFloat64()
}
var ValleyPrice float64
if summary.Valley.Amount.GreaterThan(decimal.Zero) {
ValleyPrice = summary.Valley.Fee.InexactFloat64() / summary.Valley.Amount.InexactFloat64()
} else {
ValleyPrice = decimal.Zero.InexactFloat64()
}
var LossDilutedPrice float64
if summary.Overall.Amount.GreaterThan(decimal.Zero) {
LossDilutedPrice = parkPrice
} else {
LossDilutedPrice = decimal.Zero.InexactFloat64()
}
_ = parkPrice
return Summary{
ReportId: summary.ReportId,
OverallArea: decimal.Zero,
Overall: model.ConsumptionUnit{
Amount: summary.Overall.Amount,
Fee: summary.Overall.Fee,
Price: decimal.NewFromFloat(OverallPrice),
Proportion: decimal.NewFromFloat(1.0),
},
ConsumptionFee: summary.ConsumptionFee.Decimal,
Critical: model.ConsumptionUnit{
Amount: summary.Critical.Amount,
Fee: summary.Critical.Fee,
Price: decimal.NewFromFloat(CriticalPrice),
Proportion: decimal.NewFromFloat(summary.Critical.Amount.InexactFloat64() / summary.Overall.Amount.InexactFloat64()),
},
Peak: model.ConsumptionUnit{
Amount: summary.Peak.Amount,
Fee: summary.Peak.Fee,
Price: decimal.NewFromFloat(PeakPrice),
Proportion: decimal.NewFromFloat(summary.Peak.Amount.InexactFloat64() / summary.Overall.Amount.InexactFloat64()),
},
Flat: model.ConsumptionUnit{
Amount: decimal.NewFromFloat(flatAmount),
Fee: decimal.NewFromFloat(flatFee),
Price: decimal.NewFromFloat(FlatPrice),
Proportion: decimal.NewFromFloat(flatAmount / summary.Overall.Amount.InexactFloat64()),
},
Valley: model.ConsumptionUnit{
Amount: summary.Valley.Amount,
Fee: summary.Valley.Fee,
Price: decimal.NewFromFloat(ValleyPrice),
Proportion: decimal.NewFromFloat(summary.Valley.Amount.InexactFloat64() / summary.Overall.Amount.InexactFloat64()),
},
Loss: decimal.Zero,
LossFee: decimal.Zero,
LossProportion: decimal.Zero,
AuthoizeLoss: model.ConsumptionUnit{},
BasicFee: summary.BasicFee,
BasicPooledPriceConsumption: decimal.Zero,
BasicPooledPriceArea: decimal.Zero,
AdjustFee: summary.AdjustFee,
AdjustPooledPriceConsumption: decimal.Zero,
AdjustPooledPriceArea: decimal.Zero,
LossDilutedPrice: decimal.NewFromFloat(LossDilutedPrice),
TotalConsumption: decimal.Zero,
FinalDilutedOverall: decimal.Zero,
}
}

32
model/charge.go Normal file
View File

@@ -0,0 +1,32 @@
package model
import (
"electricity_bill_calc/types"
"github.com/shopspring/decimal"
)
type UserChargeDetail struct {
Seq int64 `json:"seq"`
UserId string `json:"userId" db:"user_id"`
Name string `json:"name"`
Fee *float64 `json:"fee"`
Discount *float64 `json:"discount"`
Amount *float64 `json:"amount"`
ChargeTo types.Date `json:"chargeTo"`
Settled bool `json:"settled"`
SettledAt *types.DateTime `json:"settledAt"`
Cancelled bool `json:"cancelled"`
CancelledAt *types.DateTime `json:"cancelledAt"`
Refunded bool `json:"refunded"`
RefundedAt *types.DateTime `json:"refundedAt"`
CreatedAt types.DateTime `json:"createdAt"`
}
type ChargeRecordCreationForm struct {
UserId string `json:"userId"`
Fee decimal.NullDecimal `json:"fee"`
Discount decimal.NullDecimal `json:"discount"`
Amount decimal.NullDecimal `json:"amount"`
ChargeTo types.Date `json:"chargeTo"`
}

10
model/cunsumption.go Normal file
View File

@@ -0,0 +1,10 @@
package model
import "github.com/shopspring/decimal"
type ConsumptionUnit struct {
Amount decimal.Decimal `json:"amount"`
Fee decimal.Decimal `json:"fee"`
Price decimal.Decimal `json:"price"`
Proportion decimal.Decimal `json:"proportion"`
}

View File

@@ -1,94 +0,0 @@
package model
import (
"errors"
"github.com/shopspring/decimal"
)
type EndUserDetail struct {
CreatedAndModified `xorm:"extends"`
ReportId string `xorm:"varchar(120) pk not null" json:"reportId"`
ParkId string `xorm:"varchar(120) pk not null" json:"parkId"`
MeterId string `xorm:"meter_04kv_id varchar(120) pk not null" json:"meterId"`
Seq int64 `xorm:"bigint not null default 0" json:"seq"`
Ratio decimal.Decimal `xorm:"numeric(8,4) not null default 1" json:"ratio"`
Address *string `xorm:"varchar(100)" json:"address"`
CustomerName *string `xorm:"varchar(100)" json:"customerName"`
ContactName *string `xorm:"varchar(70)" json:"contactName"`
ContactPhone *string `xorm:"varchar(50)" json:"contactPhone"`
IsPublicMeter bool `xorm:"'public_meter' bool not null default false" json:"isPublicMeter"`
WillDilute bool `xorm:"'dilute' bool not null default false" json:"willDilute"`
LastPeriodOverall decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"lastPeriodOverall"`
LastPeriodCritical decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"lastPeriodCritical"`
LastPeriodPeak decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"lastPeriodPeak"`
LastPeriodFlat decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"lastPeriodFlat"`
LastPeriodValley decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"lastPeriodValley"`
CurrentPeriodOverall decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"currentPeriodOverall"`
CurrentPeriodCritical decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"currentPeriodCritical"`
CurrentPeriodPeak decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"currentPeriodPeak"`
CurrentPeriodFlat decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"currentPeriodFlat"`
CurrentPeriodValley decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"currentPeriodValley"`
AdjustOverall decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"adjustOverall"`
AdjustCritical decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"adjustCritical"`
AdjustPeak decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"adjustPeak"`
AdjustFlat decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"adjustFlat"`
AdjustValley decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"adjustValley"`
Overall decimal.NullDecimal `xorm:"numeric(14,2)" json:"overall"`
OverallFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"overallFee"`
OverallProportion decimal.Decimal `xorm:"numeric(16,15) not null default 0" json:"-"`
Critical decimal.NullDecimal `xorm:"numeric(14,2)" json:"critical"`
CriticalFee decimal.NullDecimal `xorm:"numeric(18,8)" json:"criticalFee"`
Peak decimal.NullDecimal `xorm:"numeric(14,2)" json:"peak"`
PeakFee decimal.NullDecimal `xorm:"numeric(18,8)" json:"peakFee"`
Flat decimal.NullDecimal `xorm:"numeric(14,2)" json:"flat"`
FlatFee decimal.NullDecimal `xorm:"numeric(18,8)" json:"flatFee"`
Valley decimal.NullDecimal `xorm:"numeric(14,2)" json:"valley"`
ValleyFee decimal.NullDecimal `xorm:"numeric(18,8)" json:"valleyFee"`
BasicFeeDiluted decimal.NullDecimal `xorm:"numeric(18,8)" json:"basicFeeDiluted"`
AdjustFeeDiluted decimal.NullDecimal `xorm:"numeric(18,8)" json:"adjustFeeDiluted"`
LossDiluted decimal.NullDecimal `xorm:"numeric(18,8)" json:"lossDiluted"`
LossFeeDiluted decimal.NullDecimal `xorm:"numeric(18,8)" json:"lossFeeDiluted"`
MaintenanceFeeDiluted decimal.NullDecimal `xorm:"numeric(18,8)" json:"maintenanceFeeDiluted"`
PublicConsumptionDiluted decimal.NullDecimal `xorm:"numeric(18,8)" json:"publicConsumptionDiluted"`
FinalDiluted decimal.NullDecimal `xorm:"numeric(14,2)" json:"finalDiluted"`
FinalCharge decimal.NullDecimal `xorm:"numeric(14,2)" json:"finalCharge"`
}
func (EndUserDetail) TableName() string {
return "end_user_detail"
}
func (d EndUserDetail) Validate() (bool, error) {
lastPeriodSum := decimal.Sum(d.LastPeriodCritical, d.LastPeriodPeak, d.LastPeriodValley)
if lastPeriodSum.GreaterThan(d.LastPeriodOverall) {
return false, errors.New("上期峰谷计量总量大于上期总计电量")
}
currentPeriodSum := decimal.Sum(d.CurrentPeriodCritical, d.CurrentPeriodPeak, d.CurrentPeriodValley)
if currentPeriodSum.GreaterThan(d.CurrentPeriodOverall) {
return false, errors.New("本期峰谷计量总量大于本期总计电量")
}
return true, nil
}
func (d *EndUserDetail) CalculatePeriod() {
d.CurrentPeriodFlat = d.CurrentPeriodOverall.Sub(d.CurrentPeriodCritical).Sub(d.CurrentPeriodPeak).Sub(d.CurrentPeriodValley)
d.Overall = decimal.NewNullDecimal(d.CurrentPeriodOverall.Sub(d.LastPeriodOverall).Mul(d.Ratio).Add(d.AdjustOverall).RoundBank(2))
d.Critical = decimal.NewNullDecimal(d.CurrentPeriodCritical.Sub(d.LastPeriodCritical).Mul(d.Ratio).Add(d.AdjustCritical).RoundBank(2))
d.Peak = decimal.NewNullDecimal(d.CurrentPeriodPeak.Sub(d.LastPeriodPeak).Mul(d.Ratio).Add(d.AdjustPeak).RoundBank(2))
d.Flat = decimal.NewNullDecimal(d.CurrentPeriodFlat.Sub(d.LastPeriodFlat).Mul(d.Ratio).Add(d.AdjustFlat).RoundBank(2))
d.Valley = decimal.NewNullDecimal(d.CurrentPeriodValley.Sub(d.LastPeriodValley).Mul(d.Ratio).Add(d.AdjustValley).RoundBank(2))
}
type EndUserImport struct {
MeterId string `excel:"meterId"`
CurrentPeriodOverall decimal.Decimal `excel:"currentPeriodOverall"`
CurrentPeriodCritical decimal.NullDecimal `excel:"currentPeriodCritical"`
CurrentPeriodPeak decimal.NullDecimal `excel:"currentPeriodPeak"`
CurrentPeriodValley decimal.NullDecimal `excel:"currentPeriodValley"`
AdjustOverall decimal.Decimal `excel:"adjustOverall"`
AdjustCritical decimal.NullDecimal `excel:"adjustCritical"`
AdjustPeak decimal.NullDecimal `excel:"adjustPeak"`
AdjustFlat decimal.NullDecimal `excel:"adjustFlat"`
AdjustValley decimal.NullDecimal `excel:"adjustValley"`
}

90
model/enums.go Normal file
View File

@@ -0,0 +1,90 @@
package model
import (
"fmt"
"strings"
)
const (
ELECTRICITY_CATE_TWO_PART int16 = iota
ELECTRICITY_CATE_UNITARY_PV
ELECTRICITY_CATE_FULL_PV
)
const (
METER_TYPE_UNITARY int16 = iota
METER_TYPE_PV
)
const (
METER_INSTALLATION_TENEMENT int16 = iota
METER_INSTALLATION_PARK
METER_INSTALLATION_POOLING
)
func ParseMeterInstallationType(s string) (int16, error) {
switch {
case strings.Contains(s, "商户"):
return METER_INSTALLATION_TENEMENT, nil
case strings.Contains(s, "公共"):
return METER_INSTALLATION_PARK, nil
case strings.Contains(s, "楼道"):
return METER_INSTALLATION_POOLING, nil
default:
return -1, fmt.Errorf("提供了一个无法识别的表计类型: %s", s)
}
}
const (
PRICING_POLICY_CONSUMPTION int16 = iota
PRICING_POLICY_ALL
)
const (
POOLING_MODE_NONE int16 = iota
POOLING_MODE_CONSUMPTION
POOLING_MODE_AREA
)
const (
PAYMENT_CASH int16 = iota
PAYMENT_BANK_CARD
PAYMENT_ALIPAY
PAYMENT_WECHAT
PAYMENT_UNION_PAY
PAYMENT_OTHER int16 = 99
)
const (
METER_TELEMETER_HYBRID int16 = iota
METER_TELEMETER_AUTOMATIC
METER_TELEMETER_MANUAL
)
const (
RETRY_INTERVAL_ALGORITHM_EXPONENTIAL_BACKOFF int16 = iota
RETRY_INTERVAL_ALGORITHM_DOUBLE_LINEAR_BACKOFF
RETRY_INTERVAL_ALGORITHM_TRIPLE_LINEAR_BACKOFF
RETRY_INTERVAL_ALGORITHM_FIXED
)
const (
TAX_METHOD_INCLUSIVE int16 = iota
TAX_METHOD_EXCLUSIVE
)
const (
REPORT_CALCULATE_TASK_STATUS_PENDING int16 = iota
REPORT_CALCULATE_TASK_STATUS_SUCCESS
REPORT_CALCULATE_TASK_STATUS_INSUFICIENT_DATA
REPORT_CALCULATE_TASK_STATUS_SUSPENDED
REPORT_CALCULATE_TASK_STATUS_UNKNOWN_ERROR
REPORT_CALCULATE_TASK_STATUS_UNEXISTS = 99
)
const (
REPORT_WITHDRAW_NON int16 = iota
REPORT_WITHDRAW_APPLYING
REPORT_WITHDRAW_DENIED
REPORT_WITHDRAW_GRANTED
)

45
model/invoice.go Normal file
View File

@@ -0,0 +1,45 @@
package model
import (
"electricity_bill_calc/tools"
"electricity_bill_calc/types"
"github.com/shopspring/decimal"
)
type InvoiceTitle struct {
Name string `json:"name"`
USCI string `json:"usci"`
Address string `json:"address"`
Phone string `json:"phone"`
Bank string `json:"bank"`
Account string `json:"account"`
}
type InvoiceCargo struct {
Name string `json:"name"`
Price decimal.Decimal `json:"price"`
Unit string `json:"unit"`
Quantity decimal.Decimal `json:"quantity"`
TaxRate decimal.Decimal `json:"taxRate"`
Tax decimal.Decimal `json:"tax"`
Total decimal.Decimal `json:"total"`
}
type Invoice struct {
InvoiceNo string `json:"invoiceNo"`
Park string `json:"parkId" db:"park_id"`
Tenement string `json:"tenementId" db:"tenement_id"`
InvoiceType *string `json:"type" db:"type"`
Info InvoiceTitle `json:"invoiceInfo" db:"invoice_info"`
Cargos []InvoiceCargo `json:"cargos"`
TaxRate decimal.Decimal `json:"taxRate" db:"tax_rate"`
TaxMethod int16 `json:"taxMethod" db:"tax_method"`
Total decimal.Decimal `json:"total" db:"total"`
IssuedAt types.DateTime `json:"issuedAt" db:"issued_at"`
Covers []string `json:"covers"`
}
func (i Invoice) Type() string {
return tools.DefaultOrEmptyStr(i.InvoiceType, "")
}

View File

@@ -1,20 +0,0 @@
package model
import (
"github.com/shopspring/decimal"
)
type MaintenanceFee struct {
CreatedAndModified `xorm:"extends"`
Deleted `xorm:"extends"`
Id string `xorm:"varchar(120) pk not null" json:"id"`
ParkId string `xorm:"varchar(120) not null" json:"parkId"`
Name string `xorm:"varchar(50) not null" json:"name"`
Fee decimal.Decimal `xorm:"numeric(8,2) not null" json:"fee"`
Memo *string `xorm:"text" json:"memo"`
Enabled bool `xorm:"bool not null" json:"enabled"`
}
func (MaintenanceFee) TableName() string {
return "maintenance_fee"
}

128
model/meter.go Normal file
View File

@@ -0,0 +1,128 @@
package model
import (
"electricity_bill_calc/types"
"github.com/shopspring/decimal"
)
type MeterDetail struct {
Code string `json:"code" db:"code"`
Park string `json:"parkId" db:"park_id"`
Address *string `json:"address" db:"address"`
MeterType int16 `json:"type" db:"meter_type"`
Building *string `json:"building" db:"building"`
BuildingName *string `json:"buildingName" db:"building_name"`
OnFloor *string `json:"onFloor" db:"on_floor" `
Area decimal.NullDecimal `json:"area" db:"area"`
Ratio decimal.Decimal `json:"ratio" db:"ratio"`
Seq int64 `json:"seq" db:"seq"`
Enabled bool `json:"enabled" db:"enabled"`
AttachedAt *types.DateTime `json:"attachedAt" db:"attached_at"`
DetachedAt *types.DateTime `json:"detachedAt" db:"detached_at"`
CreatedAt types.DateTime `json:"createdAt" db:"created_at"`
LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"`
DisplayRatio float64 `json:"display_ratio" db:"display_ratio"`
}
type MeterRelation struct {
Id string `json:"id"`
Park string `json:"parkId" db:"park_id"`
MasterMeter string `json:"masterMeterId" db:"master_meter_id"`
SlaveMeter string `json:"slaveMeterId" db:"slave_meter_id"`
EstablishedAt types.DateTime `json:"establishedAt"`
SuspendedAt *types.DateTime `json:"suspendedAt"`
RevokedAt *types.DateTime `json:"revokedAt"`
}
type MeterSynchronization struct {
Park string `json:"parkId" db:"park_id"`
Meter string `json:"meterId" db:"meter_id"`
ForeignMeter string `json:"foreignMeter"`
SystemType string `json:"systemType"`
SystemIdentity string `json:"systemIdentity"`
Enabled bool `json:"enabled"`
LastSynchronizedAt types.DateTime `json:"lastSynchronizedAt" db:"last_synchronized_at"`
RevokeAt *types.DateTime `json:"revokeAt" db:"revoke_at"`
}
type SimpleMeterDocument struct {
Code string `json:"code"`
Seq int64 `json:"seq"`
Address *string `json:"address"`
Ratio decimal.Decimal `json:"ratio"`
TenementName *string `json:"tenementName"`
}
type NestedMeter struct {
MeterId string `json:"meterId"`
MeterDetail MeterDetail `json:"meterDetail"`
LastTermReadings Reading `json:"lastTermReadings"`
CurrentTermReadings Reading `json:"currentTermReadings"`
Overall ConsumptionUnit `json:"overall"`
Critical ConsumptionUnit `json:"critical"`
Peak ConsumptionUnit `json:"peak"`
Flat ConsumptionUnit `json:"flat"`
Valley ConsumptionUnit `json:"valley"`
BasicPooled decimal.Decimal `json:"basicPooled"`
AdjustPooled decimal.Decimal `json:"adjustPooled"`
LossPooled decimal.Decimal `json:"lossPooled"`
PublicPooled decimal.Decimal `json:"publicPooled"`
FinalTotal decimal.Decimal `json:"finalTotal"`
Area decimal.Decimal `json:"area"`
Proportion decimal.Decimal `json:"proportion"`
}
type PooledMeterDetailCompound struct {
MeterDetail
BindMeters []MeterDetail `json:"bindedMeters"`
}
// 以下结构体用于导入表计档案数据
type MeterImportRow struct {
Code string `json:"code" excel:"code"`
Address *string `json:"address" excel:"address"`
MeterType *string `json:"meterType" excel:"meterType"`
Building *string `json:"building" excel:"building"`
OnFloor *string `json:"onFloor" excel:"onFloor"`
Area decimal.NullDecimal `json:"area" excel:"area"`
Ratio decimal.Decimal `json:"ratio" excel:"ratio"`
Seq int64 `json:"seq" excel:"seq"`
ReadAt types.DateTime `json:"readAt" excel:"readAt"`
Overall decimal.Decimal `json:"overall" excel:"overall"`
Critical decimal.NullDecimal `json:"critical" excel:"critical"`
Peak decimal.NullDecimal `json:"peak" excel:"peak"`
Flat decimal.NullDecimal `json:"flat" excel:"flat"`
Valley decimal.NullDecimal `json:"valley" excel:"valley"`
}
// 以下结构体用于导入表计抄表数据
type ReadingImportRow struct {
Code string `json:"code" excel:"code"`
ReadAt types.DateTime `json:"readAt" excel:"readAt"`
Overall decimal.Decimal `json:"overall" excel:"overall"`
Critical decimal.NullDecimal `json:"critical" excel:"critical"`
Peak decimal.NullDecimal `json:"peak" excel:"peak"`
Valley decimal.NullDecimal `json:"valley" excel:"valley"`
}
type ReadAbleMeter struct {
CreatedAt types.DateTime `json:"createdAt" db:"created_at"`
LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"`
Code string `json:"code" db:"code"`
Park string `json:"parkId" db:"park_id"`
Address *string `json:"address" db:"address"`
Ratio decimal.Decimal `json:"ratio" db:"ratio"`
Seq int64 `json:"seq" db:"seq"`
Enabled bool `json:"enabled" db:"enabled"`
MeterType int16 `json:"type" db:"meter_type"`
Building *string `json:"building" db:"building"`
OnFloor *string `json:"onFloor" db:"on_floor" `
Area decimal.NullDecimal `json:"area" db:"area"`
AttachedAt *types.DateTime `json:"attachedAt" db:"attached_at"`
DetachedAt *types.DateTime `json:"detachedAt" db:"detached_at"`
DisplayRatio decimal.Decimal `db:"display_ratio"`
BuildingName *string `db:"building_name"`
}

View File

@@ -1,24 +0,0 @@
package model
import (
"github.com/shopspring/decimal"
)
type Meter04KV struct {
CreatedAndModified `xorm:"extends"`
Code string `xorm:"varchar(120) pk not null" json:"code" excel:"code"`
ParkId string `xorm:"varchar(120) pk not null" json:"parkId"`
Address *string `xorm:"varchar(100)" json:"address" excel:"address"`
CustomerName *string `xorm:"varchar(100)" json:"customerName" excel:"name"`
ContactName *string `xorm:"varchar(70)" json:"contactName" excel:"contact"`
ContactPhone *string `xorm:"varchar(50)" json:"contactPhone" excel:"phone"`
Ratio decimal.Decimal `xorm:"numeric(8,4) not null default 1" json:"ratio" excel:"ratio"`
Seq int64 `xorm:"bigint not null" json:"seq" excel:"seq"`
IsPublicMeter bool `xorm:"'public_meter' bool not null default false" json:"isPublicMeter" excel:"public"`
WillDilute bool `xorm:"'dilute' bool not null default false" json:"willDilute" excel:"dilute"`
Enabled bool `xorm:"bool not null default true" json:"enabled"`
}
func (Meter04KV) TableName() string {
return "meter_04kv"
}

View File

@@ -1,67 +1,42 @@
package model package model
import ( import (
"electricity_bill_calc/types"
"time" "time"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
) )
const (
CATEGORY_TWO_PART int8 = iota
CATEGORY_SINGLE_PV
CATEGORY_SINGLE_NON_PV
)
const (
CUSTOMER_METER_NON_PV int8 = iota
CUSTOMER_METER_PV
)
type Park struct { type Park struct {
CreatedAndModified `xorm:"extends"` Id string `json:"id"`
Deleted `xorm:"extends"` UserId string `json:"userId"`
Id string `xorm:"varchar(120) pk not null" json:"id"` Name string `json:"name"`
UserId string `xorm:"varchar(120) not null" json:"userId"` Abbr string `json:"-"`
Name string `xorm:"varchar(70) not null" json:"name"` Area decimal.NullDecimal `json:"area"`
Abbr *string `xorm:"varchar(50)" json:"abbr"` TenementQuantity decimal.NullDecimal `json:"tenement"`
Area decimal.NullDecimal `xorm:"numeric(14,2)" json:"area"` Capacity decimal.NullDecimal `json:"capacity"`
TenementQuantity decimal.NullDecimal `xorm:"numeric(8,0)" json:"tenement"` Category int16 `json:"category"`
Capacity decimal.NullDecimal `xorm:"numeric(16,2)" json:"capacity"` MeterType int16 `json:"meter04kvType" db:"meter_04kv_type"`
Category int8 `xorm:"smallint not null default 0" json:"category"` PricePolicy int16 `json:"pricePolicy"`
SubmeterType int8 `xorm:"'meter_04kv_type' smallint not null default 0" json:"meter04kvType"` BasicPooled int16 `json:"basicDiluted"`
Region *string `xorm:"varchar(10)" json:"region"` AdjustPooled int16 `json:"adjustDiluted"`
Address *string `xorm:"varchar(120)" json:"address"` LossPooled int16 `json:"lossDiluted"`
Contact *string `xorm:"varchar(100)" json:"contact"` PublicPooled int16 `json:"publicDiluted"`
Phone *string `xorm:"varchar(50)" json:"phone"` TaxRate decimal.NullDecimal `json:"taxRate"`
Enabled bool `xorm:"bool not null" json:"enabled"` Region *string `json:"region"`
} Address *string `json:"address"`
Contact *string `json:"contact"`
func (Park) TableName() string { Phone *string `json:"phone"`
return "park" Enabled bool `json:"enabled"`
} CreatedAt time.Time `json:"createdAt"`
LastModifiedAt time.Time `json:"lastModifiedAt"`
type ParkSimplified struct { DeletedAt *time.Time `json:"deletedAt"`
Id string `xorm:"varchar(120) pk not null" json:"id"` NormAuthorizedLossRate float64 `json:"normAuthorizedLoss"db:"norm_authorized_loss_rate"`
UserId string `xorm:"varchar(120) not null" json:"userId"`
Name string `xorm:"varchar(70) not null" json:"name"`
Abbr *string `xorm:"varchar(50)" json:"abbr"`
Area decimal.NullDecimal `xorm:"numeric(14,2)" json:"area"`
TenementQuantity decimal.NullDecimal `xorm:"numeric(8,0)" json:"tenement"`
Capacity decimal.NullDecimal `xorm:"numeric(16,2)" json:"capacity"`
Category int8 `xorm:"smallint not null" json:"category"`
SubmeterType int8 `xorm:"'meter_04kv_type' smallint not null" json:"meter04kvType"`
Region *string `xorm:"varchar(10)" json:"region"`
Address *string `xorm:"varchar(120)" json:"address"`
Contact *string `xorm:"varchar(100)" json:"contact"`
Phone *string `xorm:"varchar(50)" json:"phone"`
}
func (ParkSimplified) TableName() string {
return "park"
} }
type ParkPeriodStatistics struct { type ParkPeriodStatistics struct {
Id string `xorm:"varchar(120) not null" json:"id"` Id string `json:"id"`
Name string `xorm:"varchar(120) not null" json:"name"` Name string `json:"name"`
Period *time.Time `xorm:"date" json:"period" time_format:"simple_date" time_location:"shanghai"` Period *types.DateRange
} }

14
model/park_building.go Normal file
View File

@@ -0,0 +1,14 @@
package model
import "time"
type ParkBuilding struct {
Id string `json:"id"`
Park string `json:"parkId" db:"park_id"`
Name string `json:"name"`
Floors *string `json:"floors"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"createdAt"`
LastModifiedAt time.Time `json:"lastModifiedAt"`
DeletedAt *time.Time `json:"deletedAt"`
}

View File

@@ -1,113 +0,0 @@
package model
import "github.com/shopspring/decimal"
type PaidPart struct {
Overall decimal.Decimal `json:"overall"`
OverallPrice decimal.Decimal `json:"overallPrice"`
ConsumptionFee decimal.Decimal `json:"consumptionFee"`
OverallFee decimal.Decimal `json:"overallFee"`
Critical decimal.NullDecimal `json:"critical"`
CriticalPrice decimal.NullDecimal `json:"criticalPrice"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
Peak decimal.NullDecimal `json:"peak"`
PeakPrice decimal.NullDecimal `json:"peakPrice"`
PeakFee decimal.NullDecimal `json:"peakFee"`
Flat decimal.NullDecimal `json:"flat"`
FlatPrice decimal.NullDecimal `json:"flatPrice"`
FlatFee decimal.NullDecimal `json:"flatFee"`
Valley decimal.NullDecimal `json:"valley"`
ValleyPrice decimal.NullDecimal `json:"valleyPrice"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
BasicFee decimal.Decimal `json:"basicFee"`
AdjustFee decimal.Decimal `json:"adjustFee"`
}
type EndUserOverallPart struct {
Overall decimal.Decimal `json:"overall"`
OverallPrice decimal.Decimal `json:"overallPrice"`
OverallFee decimal.Decimal `json:"consumptionFee"`
Critical decimal.NullDecimal `json:"critical"`
CriticalPrice decimal.NullDecimal `json:"criticalPrice"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
Peak decimal.NullDecimal `json:"peak"`
PeakPrice decimal.NullDecimal `json:"peakPrice"`
PeakFee decimal.NullDecimal `json:"peakFee"`
Flat decimal.NullDecimal `json:"flat"`
FlatPrice decimal.NullDecimal `json:"flatPrice"`
FlatFee decimal.NullDecimal `json:"flatFee"`
Valley decimal.NullDecimal `json:"valley"`
ValleyPrice decimal.NullDecimal `json:"valleyPrice"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
}
type PublicConsumptionOverallPart struct {
Overall decimal.Decimal `json:"overall"`
OverallPrice decimal.Decimal `json:"overallPrice"`
ConsumptionFee decimal.Decimal `json:"consumptionFee"`
OverallFee decimal.Decimal `json:"overallFee"`
Critical decimal.NullDecimal `json:"critical"`
CriticalPrice decimal.NullDecimal `json:"criticalPrice"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
Peak decimal.NullDecimal `json:"peak"`
PeakPrice decimal.NullDecimal `json:"peakPrice"`
PeakFee decimal.NullDecimal `json:"peakFee"`
Flat decimal.NullDecimal `json:"flat"`
FlatPrice decimal.NullDecimal `json:"flatPrice"`
FlatFee decimal.NullDecimal `json:"flatFee"`
Valley decimal.NullDecimal `json:"valley"`
ValleyPrice decimal.NullDecimal `json:"valleyPrice"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
Proportion decimal.Decimal `json:"proportion"`
}
type LossPart struct {
Quantity decimal.Decimal `json:"quantity"`
Price decimal.Decimal `json:"price"`
ConsumptionFee decimal.Decimal `json:"consumptionFee"`
Proportion decimal.Decimal `json:"proportion"`
}
type OtherShouldCollectionPart struct {
MaintenanceFee decimal.NullDecimal `json:"maintenanceFee"`
BasicFees decimal.Decimal `json:"basicFees"`
}
type MaintenancePart struct {
BasicFees decimal.Decimal `json:"basicFees"`
LossFee decimal.Decimal `json:"lossFee"`
PublicConsumptionFee decimal.Decimal `json:"publicConsumptionFee"`
MaintenanceFee decimal.Decimal `json:"maintenanceFee"`
FinalMaintenance decimal.Decimal `json:"finalMaintenance"`
MaintenanceProportion decimal.Decimal `json:"maintenanceProportion"`
MaintenancePrice decimal.Decimal `json:"maintenancePrice"`
PriceRatio decimal.Decimal `json:"priceRatio"`
}
type EndUserSummary struct {
CustomerName *string `json:"customerName"`
Address *string `json:"address"`
MeterId string `json:"meterId"`
Overall decimal.Decimal `json:"overall"`
OverallFee decimal.Decimal `json:"overallFee"`
Critical decimal.NullDecimal `json:"critical"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
Peak decimal.NullDecimal `json:"peak"`
PeakFee decimal.NullDecimal `json:"peakFee"`
Valley decimal.NullDecimal `json:"valley"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
Maintenance decimal.Decimal `json:"maintenance"`
}
type Publicity struct {
Report Report `json:"index"`
User UserDetail `json:"enterprise"`
Park Park `json:"park"`
Paid PaidPart `json:"paid"`
EndUser EndUserOverallPart `json:"endUserSum"`
Loss LossPart `json:"loss"`
PublicConsumptionOverall PublicConsumptionOverallPart `json:"public"`
OtherCollections OtherShouldCollectionPart `json:"others"`
Maintenance MaintenancePart `json:"maintenance"`
EndUserDetails []EndUserSummary `json:"endUser"`
}

56
model/reading.go Normal file
View File

@@ -0,0 +1,56 @@
package model
import (
"electricity_bill_calc/types"
"github.com/shopspring/decimal"
)
type Reading struct {
Ratio decimal.Decimal `json:"ratio"`
Overall decimal.Decimal `json:"overall"`
Critical decimal.Decimal `json:"critical"`
Peak decimal.Decimal `json:"peak"`
Flat decimal.Decimal `json:"flat"`
Valley decimal.Decimal `json:"valley"`
}
func NewPVReading(ratio, overall, critical, peak, flat, valley decimal.Decimal) *Reading {
return &Reading{
Ratio: ratio,
Overall: overall,
Critical: critical,
Peak: peak,
Flat: flat,
Valley: valley,
}
}
func NewUnitaryReading(ratio, overall decimal.Decimal) *Reading {
return &Reading{
Ratio: ratio,
Overall: overall,
Critical: decimal.Zero,
Peak: decimal.Zero,
Flat: overall,
Valley: decimal.Zero,
}
}
type MeterReading struct {
ReadAt types.DateTime `json:"readAt"`
Park string `json:"parkId" db:"park_id"`
Meter string `json:"meterId" db:"meter_id"`
MeterType int16 `json:"meterType"`
Ratio decimal.Decimal `json:"ratio"`
Overall decimal.Decimal `json:"overall"`
Critical decimal.Decimal `json:"critical"`
Peak decimal.Decimal `json:"peak"`
Flat decimal.Decimal `json:"flat"`
Valley decimal.Decimal `json:"valley"`
}
type DetailedMeterReading struct {
Detail MeterDetail `json:"detail"`
Reading MeterReading `json:"reading"`
}

View File

@@ -1,12 +1,8 @@
package model package model
type Region struct { type Region struct {
Code string `xorm:"varchar(15) pk not null" json:"code"` Code string `json:"code"`
Name string `xorm:"varchar(50) not null" json:"name"` Name string `json:"name"`
Level int `xorm:"int not null default 0" json:"level"` Level int32 `json:"level"`
Parent string `xorm:"varchar(15) not null default '0'" json:"parent"` Parent string `json:"parent"`
}
func (Region) TableName() string {
return "region"
} }

View File

@@ -1,90 +1,140 @@
package model package model
import "time" import (
"electricity_bill_calc/types"
const ( "github.com/shopspring/decimal"
REPORT_NOT_WITHDRAW int8 = iota
REPORT_WITHDRAW_APPLIED
REPORT_WITHDRAW_DENIED
REPORT_WITHDRAW_GRANTED
) )
type Report struct { type ReportIndex struct {
CreatedAndModified `xorm:"extends"` Id string `json:"id"`
Id string `xorm:"varchar(120) pk not null" json:"id"` Park string `json:"parkId" db:"park_id"`
ParkId string `xorm:"varchar(120) not null" json:"parkId"` Period types.DateRange `json:"period"`
Period time.Time `xorm:"date not null" json:"period" time_format:"simple_date" time_location:"shanghai"` Category int16 `json:"category"`
Category int8 `xorm:"smallint not null default 0" json:"category"` MeterType int16 `json:"meter04kvType" db:"meter_04kv_type"`
SubmeterType int8 `xorm:"'meter_04kv_type' smallint not null default 0" json:"meter04kvType"` PricePolicy int16 `json:"pricePolicy"`
StepState Steps `xorm:"text not null json" json:"stepState"` BasisPooled int16 `json:"basisPooled"`
Published bool `xorm:"bool not null default false" json:"published"` AdjustPooled int16 `json:"adjustPooled"`
PublishedAt *time.Time `xorm:"timestampz" json:"publishedAt" time_format:"simple_datetime" time_location:"shanghai"` LossPooled int16 `json:"lossPooled"`
Withdraw int8 `xorm:"smallint not null default 0" json:"withdraw"` PublicPooled int16 `json:"publicPooled"`
LastWithdrawAppliedAt *time.Time `xorm:"timestampz" json:"lastWithdrawAppliedAt" time_format:"simple_datetime" time_location:"shanghai"` Published bool `json:"published"`
LastWithdrawAuditAt *time.Time `xorm:"timestampz" json:"lastWithdrawAuditAt" time_format:"simple_datetime" time_location:"shanghai"` PublishedAt *types.DateTime `json:"publishedAt" db:"published_at"`
Withdraw int16 `json:"withdraw"`
LastWithdrawAppliedAt *types.DateTime `json:"lastWithdrawAppliedAt" db:"last_withdraw_applied_at"`
LastWithdrawAuditAt *types.DateTime `json:"lastWithdrawAuditAt" db:"last_withdraw_audit_at"`
Status *int16 `json:"status"`
Message *string `json:"message"`
CreatedAt types.DateTime `json:"createdAt" db:"created_at"`
LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"`
AuthorizedLossRate float64 `json:"authorized_loss_rate" db:"authorized_loss_rate"`
AuthorizedLossRateIncrement float64 `json:"authorized_loss_rate_increment" db:"authorized_loss_rate_increment"`
} }
type Steps struct { type ReportSummary struct {
Summary bool `json:"summary"` ReportId string `json:"reportId" db:"report_id"`
WillDiluted bool `json:"willDiluted"` OverallArea decimal.Decimal `json:"overallArea" db:"overall_area"`
Submeter bool `json:"submeter"` Overall ConsumptionUnit `json:"overall"`
Calculate bool `json:"calculate"` ConsumptionFee decimal.NullDecimal `json:"consumptionFee" db:"consumption_fee"`
Preview bool `json:"preview"` Critical ConsumptionUnit `json:"critical"`
Publish bool `json:"publish"` Peak ConsumptionUnit `json:"peak"`
Flat ConsumptionUnit `json:"flat"`
Valley ConsumptionUnit `json:"valley"`
Loss decimal.NullDecimal `json:"loss"`
LossFee decimal.NullDecimal `json:"lossFee" db:"loss_fee"`
LossProportion decimal.NullDecimal `json:"lossProportion" db:"loss_proportion"`
AuthorizeLoss *ConsumptionUnit `json:"authorizeLoss" db:"authorize_loss"`
BasicFee decimal.Decimal `json:"basicFee" db:"basic_fee"`
BasicPooledPriceConsumption decimal.NullDecimal `json:"basicPooledPriceConsumption" db:"basic_pooled_price_consumption"`
BasicPooledPriceArea decimal.NullDecimal `json:"basicPooledPriceArea" db:"basic_pooled_price_area"`
AdjustFee decimal.Decimal `json:"adjustFee" db:"adjust_fee"`
AdjustPooledPriceConsumption decimal.NullDecimal `json:"adjustPooledPriceConsumption" db:"adjust_pooled_price_consumption"`
AdjustPooledPriceArea decimal.NullDecimal `json:"adjustPooledPriceArea" db:"adjust_pooled_price_area"`
LossDilutedPrice decimal.NullDecimal `json:"lossDilutedPrice" db:"loss_diluted_price"`
TotalConsumption decimal.Decimal `json:"totalConsumption" db:"total_consumption"`
FinalDilutedOverall decimal.NullDecimal `json:"finalDilutedOverall" db:"final_diluted_overall"`
} }
func (Report) TableName() string { func (rs ReportSummary) GetConsumptionFee() decimal.Decimal {
return "report" if !rs.ConsumptionFee.Valid {
} return rs.Overall.Fee.Sub(rs.BasicFee).Sub(rs.AdjustFee)
func NewSteps() Steps {
return Steps{
Summary: false,
WillDiluted: false,
Submeter: false,
Calculate: false,
Preview: false,
Publish: false,
} }
return rs.ConsumptionFee.Decimal
} }
type ParkNewestReport struct { type ReportPublicConsumption struct {
Park Park `xorm:"extends" json:"park"` ReportId string `json:"reportId" db:"report_id"`
Report *Report `xorm:"extends" json:"report"` MeterId string `json:"parkMeterId" db:"park_meter_id"`
Overall ConsumptionUnit `json:"overall"`
Critical ConsumptionUnit `json:"critical"`
Peak ConsumptionUnit `json:"peak"`
Flat ConsumptionUnit `json:"flat"`
Valley ConsumptionUnit `json:"valley"`
LossAdjust ConsumptionUnit `json:"lossAdjust"`
ConsumptionTotal decimal.Decimal `json:"consumptionTotal" db:"consumption_total"`
LossAdjustTotal decimal.Decimal `json:"lossAdjustTotal" db:"loss_adjust_total"`
FinalTotal decimal.Decimal `json:"finalTotal" db:"final_total"`
PublicPooled int16 `json:"publicPooled" db:"public_pooled"`
} }
func (ParkNewestReport) TableName() string { type ReportDetailedPublicConsumption struct {
return "park" MeterDetail
ReportPublicConsumption
} }
func (p *ParkNewestReport) AfterLoad() { type ReportPooledConsumption struct {
if p.Report != nil && len(p.Report.Id) == 0 { ReportId string `json:"reportId" db:"report_id"`
p.Report = nil MeterId string `json:"pooledMeterId" db:"pooled_meter_id"`
} Overall ConsumptionUnit `json:"overall"`
Critical ConsumptionUnit `json:"critical"`
Peak ConsumptionUnit `json:"peak"`
Flat ConsumptionUnit `json:"flat"`
Valley ConsumptionUnit `json:"valley"`
PooledArea decimal.Decimal `json:"pooledArea" db:"pooled_area"`
Diluted []NestedMeter `json:"diluted"`
} }
type ReportIndexSimplified struct { type ReportDetailedPooledConsumption struct {
Id string `xorm:"varchar(120) pk not null" json:"id"` MeterDetail
ParkId string `xorm:"varchar(120) not null" json:"parkId"` ReportPooledConsumption
Period time.Time `xorm:"date not null" json:"period" time_format:"simple_date" time_location:"shanghai"` PublicPooled int16 `json:"publicPooled"`
StepState Steps `xorm:"text not null json" json:"stepState"`
Published bool `xorm:"bool not null default false" json:"published"`
PublishedAt *time.Time `xorm:"timestampz" json:"publishedAt" time_format:"simple_datetime" time_location:"shanghai"`
Withdraw int8 `xorm:"smallint not null default 0" json:"withdraw"`
LastWithdrawAppliedAt *time.Time `xorm:"timestampz" json:"lastWithdrawAppliedAt" time_format:"simple_datetime" time_location:"shanghai"`
LastWithdrawAuditAt *time.Time `xorm:"timestampz" json:"lastWithdrawAuditAt" time_format:"simple_datetime" time_location:"shanghai"`
} }
func (ReportIndexSimplified) TableName() string { type ReportDetailNestedMeterConsumption struct {
return "report" Meter MeterDetail `json:"meter"`
Consumption NestedMeter `json:"consumption"`
} }
type JoinedReportForWithdraw struct { type ReportTenement struct {
Report Report `xorm:"extends" json:"report"` ReportId string `json:"reportId" db:"report_id"`
Park ParkSimplified `xorm:"extends" json:"park"` Tenement string `json:"tenementId" db:"tenement_id"`
User UserDetailSimplified `xorm:"extends" json:"user"` Detail Tenement `json:"tenementDetail" db:"tenement_detail"`
Period types.DateRange `json:"calcPeriod" db:"calc_period"`
Overall ConsumptionUnit `json:"overall"`
Critical ConsumptionUnit `json:"critical"`
Peak ConsumptionUnit `json:"peak"`
Flat ConsumptionUnit `json:"flat"`
Valley ConsumptionUnit `json:"valley"`
BasicFeePooled decimal.Decimal `json:"basicFeePooled" db:"basic_fee_pooled"`
AdjustFeePooled decimal.Decimal `json:"adjustFeePooled" db:"adjust_fee_pooled"`
LossFeePooled decimal.Decimal `json:"lossFeePooled" db:"loss_fee_pooled"`
FinalPooled decimal.Decimal `json:"finalPooled" db:"final_pooled"`
FinalCharge decimal.Decimal `json:"finalCharge" db:"final_charge"`
Invoice []string `json:"invoice" db:"invoice"`
Meters []NestedMeter `json:"meters" db:"meters"`
Pooled []NestedMeter `json:"pooled" db:"pooled"`
Loss ConsumptionUnit `json:"loss"` //TODO: 2023.08.11 测试时发现少一个字段(已补全)
} }
func (JoinedReportForWithdraw) TableName() string { type ReportTask struct {
return "report" Id string `json:"id"`
LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"`
Status int16 `json:"status"`
Message *string `json:"message"`
}
type SimplifiedTenementCharge struct {
ReportId string `json:"reportId" db:"report_id"`
Period types.DateRange `json:"period"`
TotalConsumption decimal.Decimal `json:"totalConsumption" db:"total_consumption"`
FinalCharge decimal.Decimal `json:"finalCharge" db:"final_charge"`
} }

View File

@@ -1,106 +0,0 @@
package model
import (
"errors"
"github.com/shopspring/decimal"
)
type ReportSummary struct {
ReportId string `xorm:"varchar(120) pk not null" json:"-"`
Overall decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"overall"`
OverallFee decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"overallFee"`
ConsumptionFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"consumptionFee"`
OverallPrice decimal.NullDecimal `xorm:"numeric(16,8)" json:"overallPrice"`
Critical decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"critical"`
CriticalFee decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"criticalFee"`
CriticalPrice decimal.NullDecimal `xorm:"numeric(16,8)" json:"criticalPrice"`
Peak decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"peak"`
PeakFee decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"peakFee"`
PeakPrice decimal.NullDecimal `xorm:"numeric(16,8)" json:"peakPrice"`
Flat decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"flat"`
FlatFee decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"flatFee"`
FlatPrice decimal.NullDecimal `xorm:"numeric(16,8)" json:"flatPrice"`
Valley decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"valley"`
ValleyFee decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"valleyFee"`
ValleyPrice decimal.NullDecimal `xorm:"numeric(16,8)" json:"valleyPrice"`
Loss decimal.NullDecimal `xorm:"numeric(14,2)" json:"loss"`
LossFee decimal.NullDecimal `xorm:"numeric(16,2)" json:"lossFee"`
LossProportion decimal.NullDecimal `xorm:"numeric(16,15)" json:"lossProportion"`
CustomerConsumption decimal.NullDecimal `xorm:"numeric(16,2)" json:"customerConsumption"`
CustomerConsumptionFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"customerConsumptionFee"`
CustomerConsumptionCritical decimal.NullDecimal `xorm:"numeric(16,2)" json:"customerConsumptionCritical"`
CustomerConsumptionCriticalFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"customerConsumptionCriticalFee"`
CustomerConsumptionPeak decimal.NullDecimal `xorm:"numeric(16,2)" json:"customerConsumptionPeak"`
CustomerConsumptionPeakFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"customerConsumptionPeakFee"`
CustomerConsumptionFlat decimal.NullDecimal `xorm:"numeric(16,2)" json:"customerConsumptionFlat"`
CustomerConsumptionFlatFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"customerConsumptionFlatFee"`
CustomerConsumptionValley decimal.NullDecimal `xorm:"numeric(16,2)" json:"customerConsumptionValley"`
CustomerConsumptionValleyFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"customerConsumptionValleyFee"`
PublicConsumption decimal.NullDecimal `xorm:"numeric(14,2)" json:"publicConsumption"`
PublicConsumptionFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"publicConsumptionFee"`
PublicConsumptionProportion decimal.NullDecimal `xorm:"numeric(16,15)" json:"publicConsumptionProportion"`
PublicConsumptionCritical decimal.NullDecimal `xorm:"numeric(16,2)" json:"publicConsumptionCritical"`
PublicConsumptionCriticalFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"publicConsumptionCriticalFee"`
PublicConsumptionPeak decimal.NullDecimal `xorm:"numeric(16,2)" json:"publicConsumptionPeak"`
PublicConsumptionPeakFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"publicConsumptionPeakFee"`
PublicConsumptionFlat decimal.NullDecimal `xorm:"numeric(16,2)" json:"publicConsumptionFlat"`
PublicConsumptionFlatFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"publicConsumptionFlatFee"`
PublicConsumptionValley decimal.NullDecimal `xorm:"numeric(16,2)" json:"publicConsumptionValley"`
PublicConsumptionValleyFee decimal.NullDecimal `xorm:"numeric(14,2)" json:"publicConsumptionValleyFee"`
BasicFee decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"basicFee"`
BasicDilutedPrice decimal.NullDecimal `xorm:"numeric(18,8)" json:"basicDilutedPrice"`
AdjustFee decimal.Decimal `xorm:"numeric(14,2) not null default 0" json:"adjustFee"`
AdjustDilutedPrice decimal.NullDecimal `xorm:"numeric(18,8)" json:"adjustDilutedPrice"`
MaintenanceDilutedPrice decimal.NullDecimal `xorm:"numeric(16,8)" json:"maintencanceDilutedPrice"`
LossDilutedPrice decimal.NullDecimal `xorm:"numeric(16,8)" json:"lossDilutedPrice"`
PublicConsumptionDilutedPrice decimal.NullDecimal `xorm:"numeric(16,8)" json:"publicConsumptionDilutedPrice"`
FinalDilutedOverall decimal.NullDecimal `xorm:"numeric(14,2)" json:"finalDilutedOverall"`
}
func (ReportSummary) TableName() string {
return "report_summary"
}
func (s ReportSummary) Validate() (bool, error) {
amountSum := decimal.Sum(s.Critical, s.Peak, s.Valley)
if amountSum.GreaterThan(s.Overall) {
return false, errors.New("峰谷计量总量大于总计电量")
}
feeSum := decimal.Sum(s.CriticalFee, s.PeakFee, s.ValleyFee)
if feeSum.GreaterThan(s.OverallFee) {
return false, errors.New("峰谷计量费用大于总计费用")
}
return true, nil
}
func (s *ReportSummary) CalculatePrices() {
s.ConsumptionFee = decimal.NewNullDecimal(s.OverallFee.Sub(s.BasicFee).Sub(s.AdjustFee))
if s.Overall.GreaterThan(decimal.Zero) {
s.OverallPrice = decimal.NewNullDecimal(s.ConsumptionFee.Decimal.Div(s.Overall).RoundBank(8))
} else {
s.OverallPrice = decimal.NewNullDecimal(decimal.Zero)
}
if s.Critical.GreaterThan(decimal.Zero) {
s.CriticalPrice = decimal.NewNullDecimal(s.CriticalFee.Div(s.Critical).RoundBank(8))
} else {
s.CriticalPrice = decimal.NewNullDecimal(decimal.Zero)
}
if s.Peak.GreaterThan(decimal.Zero) {
s.PeakPrice = decimal.NewNullDecimal(s.PeakFee.Div(s.Peak).RoundBank(8))
} else {
s.PeakPrice = decimal.NewNullDecimal(decimal.Zero)
}
if s.Valley.GreaterThan(decimal.Zero) {
s.ValleyPrice = decimal.NewNullDecimal(s.ValleyFee.Div(s.Valley).RoundBank(8))
} else {
s.ValleyPrice = decimal.NewNullDecimal(decimal.Zero)
}
s.Flat = s.Overall.Sub(s.Critical).Sub(s.Peak).Sub(s.Valley)
s.FlatFee = s.OverallFee.Sub(s.CriticalFee).Sub(s.PeakFee).Sub(s.ValleyFee)
if s.Flat.GreaterThan(decimal.Zero) {
s.FlatPrice = decimal.NewNullDecimal(s.FlatFee.Div(s.Flat).RoundBank(8))
} else {
s.FlatPrice = decimal.NewNullDecimal(decimal.Zero)
}
}

View File

@@ -5,7 +5,7 @@ import "time"
type Session struct { type Session struct {
Uid string `json:"uid"` Uid string `json:"uid"`
Name string `json:"name"` Name string `json:"name"`
Type int8 `json:"type"` Type int16 `json:"type"`
Token string `json:"token"` Token string `json:"token"`
ExpiresAt time.Time `json:"expiresAt" time_format:"simple_datetime" time_location:"shanghai"` ExpiresAt time.Time `json:"expiresAt" time_format:"simple_datetime" time_location:"shanghai"`
} }

View File

@@ -1,32 +0,0 @@
package model
import "time"
type Created struct {
CreatedAt time.Time `xorm:"timestampz not null created" json:"createdAt" time_format:"simple_datetime" time_location:"shanghai"`
}
type CreatedWithUser struct {
Created `xorm:"extends"`
CreatedBy *string `xorm:"varchar(100)" json:"createdBy"`
}
type Deleted struct {
DeletedAt *time.Time `xorm:"timestampz deleted" json:"deletedAt" time_format:"simple_datetime" time_location:"shanghai"`
}
type DeletedWithUser struct {
Deleted `xorm:"extends"`
DeletedBy *string `xorm:"varchar(120)" json:"deletedBy"`
}
type CreatedAndModified struct {
Created `xorm:"extends"`
LastModifiedAt *time.Time `xorm:"timestampz updated" json:"lastModifiedAt" time_format:"simple_datetime" time_location:"shanghai"`
}
type CreatedAndModifiedWithUser struct {
CreatedAndModified `xorm:"extends"`
CreatedBy *string `xorm:"varchar(100)" json:"createdBy"`
LastModifiedBy *string `xorm:"varchar(100)" json:"lastModifiedBy"`
}

38
model/synchronize.go Normal file
View File

@@ -0,0 +1,38 @@
package model
import (
"electricity_bill_calc/types"
_ "github.com/shopspring/decimal"
"time"
)
type SynchronizeConfiguration struct {
User string `json:"user" db:"user_id"`
Park string `json:"park" db:"park_id"`
MeterReadingType int16 `json:"meter_reading_type"`
ImrsType string `json:"imrs_type"`
AuthorizationAccount string `json:"authorization_account" db:"imrs_authorization_account"`
AuthorizationSecret string `json:"authorization_secret" db:"imrs_authorization_secret"`
AuthorizationKey []byte `json:"authorization_key,omitempty" db:"imrs_authorization_key"`
Interval int16 `json:"interval"`
CollectAt time.Time `json:"collect_at" db:"-"`
MaxRetries int16 `json:"max_retries"`
RetryInterval int16 `json:"retry_interval"`
RetryIntervalAlgorithm int16 `json:"retry_interval_algorithm"`
}
type SynchronizeSchedule struct {
User string `json:"userId" db:"user_id"`
UserName string `json:"userName" db:"user_name"`
Park string `json:"parkId" db:"park_id"`
ParkName string `json:"parkName" db:"park_name"`
TaskIdentity string `json:"taskIdentity" db:"task_identity"`
TaskName string `json:"taskName" db:"task_name"`
TaskDescription string `json:"taskDescription" db:"task_description"`
CreatedAt types.DateTime `json:"createdAt" db:"created_at"`
LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"`
LastDispatchedAt types.DateTime `json:"lastDispatchedAt" db:"last_dispatched_at"`
LastDispatchStatus int16 `json:"lastDispatchStatus" db:"last_dispatch_status"`
NextDispatchAt types.DateTime `json:"nextDispatchAt" db:"next_dispatch_at"`
CurrentRetries int16 `json:"currentRetries" db:"current_retries"`
}

33
model/tenement.go Normal file
View File

@@ -0,0 +1,33 @@
package model
import "electricity_bill_calc/types"
type Tenement struct {
Id string `json:"id"`
Park string `json:"parkId" db:"park_id"`
FullName string `json:"fullName" db:"full_name"`
ShortName *string `json:"shortName" db:"short_name"`
Abbr string `json:"abbr"`
Address string `json:"address"`
ContactName string `json:"contactName" db:"contact_name"`
ContactPhone string `json:"contactPhone" db:"contact_phone"`
Building *string `json:"building"`
BuildingName *string `json:"buildingName" db:"building_name"`
OnFloor *string `json:"onFloor" db:"on_floor"`
InvoiceInfo *InvoiceTitle `json:"invoiceInfo" db:"invoice_info"`
MovedInAt *types.DateTime `json:"movedInAt" db:"moved_in_at"`
MovedOutAt *types.DateTime `json:"movedOutAt" db:"moved_out_at"`
CreatedAt types.DateTime `json:"createdAt" db:"created_at"`
LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"`
DeletedAt *types.DateTime `json:"deletedAt" db:"deleted_at"`
}
type TenementMeter struct {
ParkId string `db:"park_id"`
TenementId string `db:"tenement_id"`
MeterId string `db:"meter_id"`
ForeignRelation bool `db:"foreign_relation"`
AssociatedAt types.DateTime `db:"associated_at"`
DisassociatedAt types.DateTime `db:"disassociated_at"`
SynchronizedAt types.DateTime `db:"synchronized_at"`
}

33
model/top_up.go Normal file
View File

@@ -0,0 +1,33 @@
package model
import (
"electricity_bill_calc/types"
"github.com/shopspring/decimal"
)
type TopUp struct {
TopUpCode string `json:"topUpCode" db:"top_up_code"`
Park string `json:"parkId" db:"park_id"`
Tenement string `json:"tenementId" db:"tenement_id"`
TenementName string `json:"tenementName" db:"tenement_name"`
Meter string `json:"meterId" db:"meter_id"`
MeterAddress *string `json:"meterAddress" db:"meter_address"`
ToppedUpAt types.DateTime `json:"toppedUpAt" db:"topped_up_at"`
Amount decimal.Decimal `json:"amount" db:"amount"`
PaymentType int16 `json:"paymentType" db:"payment_type"`
SuccessfulSynchronized bool `json:"successfulSynchronized" db:"successful_synchronized"`
SynchronizedAt *types.DateTime `json:"synchronizedAt" db:"synchronized_at"`
CancelledAt *types.DateTime `json:"cancelledAt" db:"cancelled_at"`
}
func (t TopUp) SyncStatus() int16 {
switch {
case t.SuccessfulSynchronized && t.SynchronizedAt != nil:
return 1
case !t.SuccessfulSynchronized && t.SynchronizedAt != nil:
return 2
default:
return 0
}
}

View File

@@ -1,35 +1,114 @@
package model package model
import (
"electricity_bill_calc/types"
"time"
"github.com/shopspring/decimal"
)
const ( const (
USER_TYPE_ENT int8 = iota USER_TYPE_ENT int16 = iota
USER_TYPE_SUP USER_TYPE_SUP
USER_TYPE_OPS USER_TYPE_OPS
) )
type ManagementAccountCreationForm struct {
Id *string `json:"id"`
Username string `json:"username"`
Name string `json:"name"`
Contact *string `json:"contact"`
Phone *string `json:"phone"`
Type int16 `json:"type"`
Enabled bool `json:"enabled"`
Expires types.Date `json:"expires"`
}
func (m ManagementAccountCreationForm) IntoUser() *User {
return &User{
Id: *m.Id,
Username: m.Username,
Password: "",
ResetNeeded: false,
UserType: m.Type,
Enabled: m.Enabled,
CreatedAt: nil,
}
}
func (m ManagementAccountCreationForm) IntoUserDetail() *UserDetail {
return &UserDetail{
Id: *m.Id,
Name: &m.Name,
Abbr: nil,
Region: nil,
Address: nil,
Contact: m.Contact,
Phone: m.Phone,
UnitServiceFee: decimal.Zero,
ServiceExpiration: m.Expires,
CreatedAt: types.Now(),
CreatedBy: nil,
LastModifiedAt: types.Now(),
LastModifiedBy: nil,
DeletedAt: nil,
DeletedBy: nil,
}
}
type UserModificationForm struct {
Name string `json:"name"`
Region *string `json:"region"`
Address *string `json:"address"`
Contact *string `json:"contact"`
Phone *string `json:"phone"`
UnitServiceFee *decimal.Decimal `json:"unitServiceFee"`
}
type User struct { type User struct {
Created `xorm:"extends"` Id string `json:"id"`
Id string `xorm:"varchar(120) pk not null" json:"id"` Username string `json:"username"`
Username string `xorm:"varchar(30) not null" json:"username"` Password string `json:"password"`
Password string `xorm:"varchar(256) not null" json:"-"` ResetNeeded bool `json:"resetNeeded"`
ResetNeeded bool `xorm:"bool not null" json:"resetNeeded"` UserType int16 `db:"type"`
Type int8 `xorm:"smallint not null" json:"type"` Enabled bool `json:"enabled"`
Enabled bool `xorm:"bool not null" json:"enabled"` CreatedAt *time.Time `json:"createdAt"`
} }
func (User) TableName() string { type UserDetail struct {
return "user" Id string `json:"id"`
Name *string `json:"name"`
Abbr *string `json:"abbr"`
Region *string `json:"region"`
Address *string `json:"address"`
Contact *string `json:"contact"`
Phone *string `json:"phone"`
UnitServiceFee decimal.Decimal `db:"unit_service_fee" json:"unitServiceFee"`
ServiceExpiration types.Date `json:"serviceExpiration"`
CreatedAt types.DateTime `json:"createdAt"`
CreatedBy *string `json:"createdBy"`
LastModifiedAt types.DateTime `json:"lastModifiedAt"`
LastModifiedBy *string `json:"lastModifiedBy"`
DeletedAt *types.DateTime `json:"deletedAt"`
DeletedBy *string `json:"deletedBy"`
} }
type UserWithCredentials struct { type UserWithDetail struct {
Created `xorm:"extends"` Id string `json:"id"`
Id string `xorm:"varchar(120) pk not null" json:"id"` Username string `json:"username"`
Username string `xorm:"varchar(30) not null" json:"username"` ResetNeeded bool `json:"resetNeeded"`
Password string `xorm:"varchar(256) not null" json:"credential"` UserType int16 `db:"type" json:"type"`
ResetNeeded bool `xorm:"bool not null" json:"resetNeeded"` Enabled bool `json:"enabled"`
Type int8 `xorm:"smallint not null" json:"type"` Name *string `json:"name"`
Enabled bool `xorm:"bool not null" json:"enabled"` Abbr *string `json:"abbr"`
} Region *string `json:"region"`
Address *string `json:"address"`
func (UserWithCredentials) TableName() string { Contact *string `json:"contact"`
return "user" Phone *string `json:"phone"`
UnitServiceFee decimal.Decimal `db:"unit_service_fee" json:"unitServiceFee"`
ServiceExpiration types.Date `json:"serviceExpiration"`
CreatedAt types.DateTime `json:"createdAt"`
CreatedBy *string `json:"createdBy"`
LastModifiedAt types.DateTime `json:"lastModifiedAt"`
LastModifiedBy *string `json:"lastModifiedBy"`
} }

View File

@@ -1,36 +0,0 @@
package model
import (
"time"
"github.com/shopspring/decimal"
)
type UserCharge struct {
Created `xorm:"extends"`
Seq int64 `xorm:"bigint pk not null autoincr" json:"seq"`
UserId string `xorm:"varchar(120) not null" json:"userId"`
Fee decimal.NullDecimal `xorm:"numeric(12,2)" json:"fee"`
Discount decimal.NullDecimal `xorm:"numeric(5,4)" json:"discount"`
Amount decimal.NullDecimal `xorm:"numeric(12,2)" json:"amount"`
ChargeTo time.Time `xorm:"date not null" json:"chargeTo" time_format:"simple_date" time_location:"shanghai"`
Settled bool `xorm:"bool not null default false" json:"settled"`
SettledAt *time.Time `xorm:"timestampz" json:"settledAt" time_format:"simple_datetime" time_location:"shanghai"`
Cancelled bool `xorm:"bool not null default false" json:"cancelled"`
CancelledAt *time.Time `xorm:"timestampz" json:"cancelledAt" time_format:"simple_datetime" time_location:"shanghai"`
Refunded bool `xorm:"bool not null default false" json:"refunded"`
RefundedAt *time.Time `xorm:"timestampz" json:"refundedAt" time_format:"simple_datetime" time_location:"shanghai"`
}
func (UserCharge) TableName() string {
return "user_charge"
}
type ChargeWithName struct {
UserDetail `xorm:"extends"`
UserCharge `xorm:"extends"`
}
func (ChargeWithName) TableName() string {
return "user_detail"
}

View File

@@ -1,60 +0,0 @@
package model
import (
"time"
"github.com/shopspring/decimal"
)
type UserDetail struct {
CreatedAndModifiedWithUser `xorm:"extends"`
DeletedWithUser `xorm:"extends"`
Id string `xorm:"varchar(120) pk not null" json:"-"`
Name *string `xorm:"varchar(100)" json:"name"`
Abbr *string `xorm:"varchar(50)" json:"abbr"`
Region *string `xorm:"varchar(10)" json:"region"`
Address *string `xorm:"varchar(120)" json:"address"`
Contact *string `xorm:"varchar(100)" json:"contact"`
Phone *string `xorm:"varchar(50)" json:"phone"`
UnitServiceFee decimal.Decimal `xorm:"numeric(8,2) not null" json:"unitServiceFee"`
ServiceExpiration time.Time `xorm:"date not null" json:"serviceExpiration" time_format:"simple_date" time_location:"shanghai"`
}
func (UserDetail) TableName() string {
return "user_detail"
}
type JoinedUserDetail struct {
UserDetail `xorm:"extends"`
Id string `json:"id"`
Username string `json:"username"`
Type int8 `json:"type"`
Enabled bool `json:"enabled"`
}
func (JoinedUserDetail) TableName() string {
return "user"
}
type FullJoinedUserDetail struct {
UserDetail `xorm:"extends"`
User `xorm:"extends"`
}
func (FullJoinedUserDetail) TableName() string {
return "user_detail"
}
type UserDetailSimplified struct {
Id string `xorm:"varchar(120) pk not null" json:"id"`
Name *string `xorm:"varchar(100)" json:"name"`
Abbr *string `xorm:"varchar(50)" json:"abbr"`
Region *string `xorm:"varchar(10)" json:"region"`
Address *string `xorm:"varchar(120)" json:"address"`
Contact *string `xorm:"varchar(100)" json:"contact"`
Phone *string `xorm:"varchar(50)" json:"phone"`
}
func (UserDetailSimplified) TableName() string {
return "user_detail"
}

View File

@@ -1,17 +0,0 @@
package model
import "github.com/shopspring/decimal"
type WillDilutedFee struct {
CreatedAndModified `xorm:"extends"`
Id string `xorm:"varchar(120) pk not null" json:"id"`
ReportId string `xorm:"varchar(120) not null" json:"reportId"`
SourceId *string `xorm:"varchar(120)" json:"sourceId"`
Name string `xorm:"varchar(50) not null" json:"name"`
Fee decimal.Decimal `xorm:"numeric(8,2) not null default 0" json:"fee"`
Memo *string `xorm:"text" json:"memo"`
}
func (WillDilutedFee) TableName() string {
return "will_diluted_fee"
}

84
model/withdraw.go Normal file
View File

@@ -0,0 +1,84 @@
package model
import (
"electricity_bill_calc/types"
"github.com/shopspring/decimal"
"time"
)
type Withdraw struct {
Park SimplifiedPark `json:"park"`
Report SimplifiedReport `json:"report"`
User UserInfos `json:"user"` // 简易用户详细信息
}
// 简易园区信息
type SimplifiedPark struct {
Address *string `json:"address"` // 园区地址
Area *string `json:"area"` // 园区面积
Capacity *string `json:"capacity"` // 供电容量
Category int16 `json:"category"` // 用电分类0两部制1单一峰谷2单一单一
Contact *string `json:"contact"` // 园区联系人
ID string `json:"id"` // 园区ID
Meter04KvType int16 `json:"meter04kvType"` // 户表计量类型0非峰谷1峰谷
Name string `json:"name"` // 园区名称
Phone *string `json:"phone"` // 园区联系人电话
Region *string `json:"region"` // 园区所在行政区划
Tenement *string `json:"tenement"` // 园区住户数量
UserID string `json:"userId"` // 园区所属用户ID
}
// 简易核算报表信息
type SimplifiedReport struct {
ID string `json:"id"` // 报表ID
LastWithdrawAppliedAt *string `json:"lastWithdrawAppliedAt"` // 最后一次申请撤回的时间,格式为 yyyy-MM-dd HH:mm:ss
LastWithdrawAuditAt *string `json:"lastWithdrawAuditAt"` // 最后一次申请审核的时间,格式为 yyyy-MM-dd HH:mm:ss
Message *string `json:"message"` // 当前状态的错误提示
ParkID string `json:"parkId"` // 所属园区ID
PeriodBegin string `json:"periodBegin"` // 核算起始日期,格式为 yyyy-MM-dd
PeriodEnd string `json:"periodEnd"` // 核算结束日期,格式为 yyyy-MM-dd
Published bool `json:"published"` // 是否已发布
PublishedAt *string `json:"publishedAt"` // 发布时间
Status float64 `json:"status,omitempty"` // 当前状态0计算任务已队列1计算任务已完成2计算数据不足
Withdraw int16 `json:"withdraw"` // 报表撤回状态0未撤回1申请撤回中2申请拒绝3申请批准
}
// 简易用户信息
type UserInfos struct {
Address *string `json:"address"` // 用户地址
Contact *string `json:"contact"` // 用户联系人
ID string `json:"id"` // 用户ID
Name *string `json:"name"` // 用户名称
Phone *string `json:"phone"` // 用户联系人电话
Region *string `json:"region"` // 用户所在行政区划
}
//用于映射数据库的报表结构体
type ReportRes struct {
ReportId string `db:"report_id"`
LastWithdrawAppliedAt *time.Time `db:"last_withdraw_applied_at"`
LastWithdrawAuditAt *time.Time `db:"last_withdraw_audit_at"`
ParkID string `db:"report_park_id"`
Period types.DateRange `db:"period"`
Published bool `db:"published"`
PublishedAt *time.Time `db: "published_at"`
Withdraw int16 `db:"withdraw"`
ParkAddress *string `db:"park_address"`
Area decimal.NullDecimal `db:"area"`
Capacity decimal.NullDecimal `db:"capacity"`
Category int16
ParkContact *string `db:"park_contact"`
ParkId string `db:"park_id"`
Meter04KVType int16 `db:"meter_04kv_type"`
ParkName string `db:"park_name"`
ParkPhone *string `db:"park_phone"`
ParkRegion string `db:"park_region"`
TenementQuantity decimal.NullDecimal `db:"tenement_quantity"`
UserID string `db:"user_id"`
Address *string
Contact string `db:"user_detail_contact"`
ID string `db:"ud_id"`
Name *string `db:"user_detail_name"`
Phone string `db:"user_detail_phone"`
Region *string `db:"user_detail_region"`
}

576
repository/calculate.go Normal file
View File

@@ -0,0 +1,576 @@
package repository
import (
"context"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/model/calculate"
"electricity_bill_calc/types"
"encoding/json"
"errors"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/shopspring/decimal"
"golang.org/x/sync/errgroup"
"time"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/georgysavva/scany/v2/pgxscan"
"go.uber.org/zap"
)
type _CalculateRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var CalculateRepository = _CalculateRepository{
log: logger.Named("Repository", "Calculate"),
ds: goqu.Dialect("postgres"),
}
// 更新当前报表的核算状态
func (cr _CalculateRepository) UpdateReportCalculateStatus(rid string, status string,
message string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
var atio int
var err error
currentTime := time.Now()
if status == "success" {
atio = 1 //创建报表成功
} else {
atio = 2 // 数据不足
}
updateResultSql, updateResultArgs, _ := cr.ds.
Update(goqu.T("report_task")).
Set(goqu.Record{
"status": int16(atio),
"last_modified_at": currentTime,
"message": message,
}).Where(goqu.I("id").Eq(rid)).
ToSQL()
res, err := global.DB.Exec(ctx, updateResultSql, updateResultArgs...)
if err != nil {
cr.log.Error("未能更新当前报表的核算状态", zap.Error(err))
return false, err
}
if res.RowsAffected() == 0 {
cr.log.Warn("未能保存当前报表的核算状态", zap.String("Report", rid))
return false, nil
}
return true, nil
}
// 获取当前正在等待计算的核算任务ID列表
func (cr _CalculateRepository) ListPendingTasks() ([]string, error) {
cr.log.Info("获取当前正在等待计算的核算任务ID列表")
ctx, cancel := global.TimeoutContext()
defer cancel()
var ids []string
querySql, queryArgs, _ := cr.ds.
From("report_task").
Select("id").
Where(goqu.C("status").Eq(model.REPORT_CALCULATE_TASK_STATUS_PENDING)).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &ids, querySql, queryArgs...); err != nil {
cr.log.Error("未能获取到当前正在等待计算的核算任务ID列表", zap.Error(err))
return nil, err
}
return ids, nil
}
// 更新指定报表的核算状态
func (cr _CalculateRepository) UpdateReportTaskStatus(rid string, status int16, message *string) (bool, error) {
cr.log.Info("更新指定报表的核算状态", zap.String("Report", rid), zap.Int16("Status", status))
ctx, cancel := global.TimeoutContext()
defer cancel()
currentTime := types.Now()
updateSql, updateArgs, _ := cr.ds.
Update("report_task").
Set(goqu.Record{
"status": status,
"last_modified_at": currentTime,
"message": message,
}).
Where(goqu.C("id").Eq(rid)).
Prepared(true).ToSQL()
res, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
cr.log.Error("未能更新指定报表的核算状态", zap.Error(err))
return false, err
}
if res.RowsAffected() == 0 {
cr.log.Warn("未能保存指定报表的核算状态", zap.String("Report", rid))
return false, nil
}
return res.RowsAffected() > 0, nil
}
// 获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的
func (cr _CalculateRepository) GetAllPoolingMeterRelations(pid string, revokedAfter time.Time) ([]model.MeterRelation, error) {
cr.log.Info("获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的", zap.String("pid", pid), zap.Time("revokedAfter", revokedAfter))
ctx, cancel := global.TimeoutContext()
defer cancel()
relationsSql, relationsArgs, _ := cr.ds.
From(goqu.T("meter_relations")).
Where(goqu.I("park_id").Eq(pid)).
Where(goqu.Or(
goqu.I("revoked_at").IsNull(),
goqu.I("revoked_at").Gte(revokedAfter),
)).ToSQL()
var meterRelation []model.MeterRelation
err := pgxscan.Select(ctx, global.DB, &meterRelation, relationsSql, relationsArgs...)
if err != nil {
cr.log.Error("获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的出错", zap.Error(err))
return nil, err
}
return meterRelation, nil
}
// 获取当前园区中所有的商户与表计的关联关系,包括已经解除的
func (cr _CalculateRepository) GetAllTenementMeterRelations(pid string, associatedBefore time.Time, disassociatedAfter time.Time) ([]model.TenementMeter, error) {
cr.log.Info("获取当前园区中所有的商户与表计的关联关系,包括已经解除的", zap.String("pid", pid), zap.Time("associatedBefore", associatedBefore), zap.Time("disassociatedAfter", disassociatedAfter))
ctx, cancel := global.TimeoutContext()
defer cancel()
relationsQuerySql, relationsQueryArgs, _ := cr.ds.
From(goqu.T("tenement_meter")).
Where(goqu.I("park_id").Eq(pid)).
Where(goqu.And(
goqu.I("associated_at").IsNotNull(),
goqu.I("associated_at").Lte(associatedBefore),
)).
Where(goqu.And(
goqu.Or(
goqu.I("disassociated_at").IsNull(),
goqu.I("disassociated_at").Gte(disassociatedAfter),
),
)).ToSQL()
var tenementMeter []model.TenementMeter
err := pgxscan.Select(ctx, global.DB, &tenementMeter, relationsQuerySql, relationsQueryArgs...)
if err != nil {
cr.log.Error("获取当前园区中所有的商户与表计的关联关系,包括已经解除的", zap.Error(err))
return nil, err
}
fmt.Println("==", tenementMeter)
return tenementMeter, nil
}
// 获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据
func (cr _CalculateRepository) GetMeterReadings(rid string, meterType int16) ([]model.MeterReading, error) {
cr.log.Info("获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据", zap.String("rid", rid), zap.Int16("meterType", meterType))
ctx, cancel := global.TimeoutContext()
defer cancel()
readingsQuerySql, readingsQueryArgs, _ := cr.ds.
From(goqu.T("meter_reading").As(goqu.I("mr"))).
Join(
goqu.T("report").As("r"),
goqu.On(goqu.I("r.park_id").Eq(goqu.I("mr.park_id"))),
).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("mr.meter_type").Eq(meterType),
// TODO2023.08.02 此方法出错优先查看是否这里出问题
goqu.L("?::date <@ ?", goqu.I("mr.read_at"), goqu.I("r.period")),
).
Order(goqu.I("mr.read_at").Asc()).Select(goqu.I("mr.*")).ToSQL()
var readings []model.MeterReading
err := pgxscan.Select(ctx, global.DB, &readings, readingsQuerySql, readingsQueryArgs...)
if err != nil {
cr.log.Error("获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据出错", zap.Error(err))
return nil, err
}
return readings, nil
}
// 获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数
func (cr _CalculateRepository) GetLastPeriodReadings(rid string, meterType int16) ([]model.MeterReading, error) {
cr.log.Info("获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数", zap.String("rid", rid), zap.Int16("meterType", meterType))
ctx, cancel := global.TimeoutContext()
defer cancel()
readingsSql, readingsArgs, _ := cr.ds.
From(goqu.T("meter_reading").As("mr")).
Select(
goqu.MAX("mr.read_at").As("read_at"),
goqu.I("mr.park_id"),
goqu.I("mr.meter_id"),
goqu.I("mr.meter_type"),
goqu.I("mr.ratio"),
goqu.I("mr.overall"),
goqu.I("mr.critical"),
goqu.I("mr.peak"),
goqu.I("mr.flat"),
goqu.I("mr.valley"),
).
Join(
goqu.T("report").As("r"),
goqu.On(goqu.I("r.park_id").Eq(goqu.I("mr.park_id"))),
).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("mr.meter_type").Eq(meterType),
goqu.L(" read_at <= lower(r.period)"),
).
GroupBy(
goqu.I("mr.park_id"),
goqu.I("mr.meter_id"),
goqu.I("mr.meter_type"),
goqu.I("mr.ratio"),
goqu.I("mr.overall"),
goqu.I("mr.critical"),
goqu.I("mr.peak"),
goqu.I("mr.flat"),
goqu.I("mr.valley"),
goqu.I("r.period"),
).ToSQL()
var readings []model.MeterReading
err := pgxscan.Select(ctx, global.DB, &readings, readingsSql, readingsArgs...)
if err != nil {
cr.log.Error("获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数出错", zap.Error(err))
return nil, err
}
return readings, nil
}
// 取得指定报表所涉及的所有商户信息
func (cr _CalculateRepository) GetAllTenements(rid string) ([]model.Tenement, error) {
cr.log.Info("取得指定报表所涉及的所有商户信息", zap.String("rid", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
tenementQuerySql, tenementQueryArgs, _ := cr.ds.
From(goqu.T("tenement").As("t")).
LeftJoin(
goqu.T("park_building").As("b"),
goqu.On(goqu.I("b.id").Eq(goqu.I("t.building"))),
).
Join(
goqu.T("report").As("r"),
goqu.On(goqu.I("r.park_id").Eq(goqu.I("t.park_id"))),
).
Select(
goqu.I("t.*"),
goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("r.id").Eq(rid),
goqu.L("t.moved_in_at <= upper(r.period)"),
).ToSQL()
fmt.Println(tenementQuerySql)
var tenements []model.Tenement
err := pgxscan.Select(ctx, global.DB, &tenements, tenementQuerySql, tenementQueryArgs...)
if err != nil {
cr.log.Error("取得指定报表所涉及的所有商户信息出错", zap.Error(err))
return nil, err
}
return tenements, nil
}
func (cr _CalculateRepository) ClearReportContent(tx pgx.Tx, rid string) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
querysql, querarg, _ := cr.ds.Delete("report_summary").
Where(goqu.C("report_id").Eq(rid)).ToSQL()
_, err := tx.Exec(ctx, querysql, querarg...)
if err != nil {
return err
}
querysql, querarg, _ = cr.ds.Delete("report_public_consumption").
Where(goqu.C("report_id").Eq(rid)).ToSQL()
_, err = tx.Exec(ctx, querysql, querarg...)
if err != nil {
return err
}
querysql, querarg, _ = cr.ds.Delete("report_pooled_consumption").
Where(goqu.C("report_id").Eq(rid)).ToSQL()
_, err = tx.Exec(ctx, querysql, querarg...)
if err != nil {
return err
}
querysql, querarg, _ = cr.ds.Delete("report_tenement").
Where(goqu.C("report_id").Eq(rid)).ToSQL()
_, err = tx.Exec(ctx, querysql, querarg...)
if err != nil {
return err
}
return nil
}
func (cr _CalculateRepository) SaveReportPublics(tx pgx.Tx, ctx context.Context, rid string, meters []calculate.Meter) error {
if len(meters) == 0 {
// 如果没有公共表计则直接返回
return nil
}
for _, meter := range meters {
// 准备插入表达式
insertExpr := cr.ds.Insert("report_public_consumption").
Cols(
"report_id", "park_meter_id", "overall", "critical", "peak", "flat", "valley",
"loss_adjust", "consumption_total", "loss_adjust_total", "final_total",
)
// 添加值到插入表达式中
overall, _ := json.Marshal(meter.Overall)
criyical, _ := json.Marshal(meter.Critical)
peak, _ := json.Marshal(meter.Peak)
flat, _ := json.Marshal(meter.Flat)
valley, _ := json.Marshal(meter.Valley)
adjustLoss, _ := json.Marshal(meter.AdjustLoss)
insertExpr = insertExpr.Vals(goqu.Vals{
rid,
meter.Code,
overall,
criyical,
peak,
flat,
valley,
adjustLoss,
meter.Overall.Fee,
meter.AdjustLoss.Fee,
meter.Overall.Fee.Add(meter.AdjustLoss.Fee),
})
// 执行插入语句
inserSql, insertArgs, _ := insertExpr.ToSQL()
_, err := tx.Exec(ctx, inserSql, insertArgs...)
if err != nil {
_ = tx.Rollback(ctx)
return fmt.Errorf("保存报表核算概要失败: %w", err)
}
}
return nil
}
func (cr _CalculateRepository) SaveReportSummary(tx pgx.Tx, ctx context.Context, summary calculate.Summary) error {
// 构建插入表达式
Overall, _ := json.Marshal(summary.Overall)
Critical, _ := json.Marshal(summary.Critical)
Peak, _ := json.Marshal(summary.Peak)
Flat, _ := json.Marshal(summary.Flat)
Valley, _ := json.Marshal(summary.Valley)
AuthoizeLoss, _ := json.Marshal(summary.AuthoizeLoss)
insertsql, insertArgs, err := cr.ds.Insert(goqu.T("report_summary")).
Cols(
"report_id", "overall", "critical", "peak", "flat", "valley",
"loss", "loss_fee", "basic_fee", "basic_pooled_price_consumption", "basic_pooled_price_area",
"adjust_fee", "adjust_pooled_price_consumption", "adjust_pooled_price_area",
"loss_diluted_price", "loss_proportion", "final_diluted_overall",
"consumption_fee", "authorize_loss", "overall_area", "total_consumption",
).
Vals(goqu.Vals{
summary.ReportId, Overall, Critical, Peak, Flat,
Valley, summary.Loss, summary.LossFee, summary.BasicFee,
summary.BasicPooledPriceConsumption, summary.BasicPooledPriceArea,
summary.AdjustFee, summary.AdjustPooledPriceConsumption, summary.AdjustPooledPriceArea,
summary.LossDilutedPrice, summary.LossProportion, summary.FinalDilutedOverall,
summary.ConsumptionFee, AuthoizeLoss, summary.OverallArea, summary.TotalConsumption,
}).ToSQL()
if err != nil {
fmt.Println(err)
return err
}
// 执行插入语句
if _, err := tx.Exec(ctx, insertsql, insertArgs...); err != nil {
cr.log.Error("保存报表核算概要失败。")
return err
}
return nil
}
type NestedMeter struct {
Overall model.ConsumptionUnit
Critical model.ConsumptionUnit
Peak model.ConsumptionUnit
Flat model.ConsumptionUnit
Valley model.ConsumptionUnit
CoveredArea decimal.Decimal
// Add other fields here as needed
}
func (cr _CalculateRepository) SaveReportPoolings(tx pgx.Tx,
rid string,
meters []calculate.Meter,
relations []model.MeterRelation,
tenements []calculate.Meter) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
if len(meters) == 0 {
return nil
}
relationsSlaves := make(map[string]bool)
for _, r := range relations {
relationsSlaves[r.SlaveMeter] = true
}
tenementCodes := make(map[string]bool)
for _, t := range tenements {
tenementCodes[t.Code] = true
}
for _, r := range relations {
if _, ok := tenementCodes[r.SlaveMeter]; !ok {
return errors.New("unknown tenement meter in active meter relations")
}
}
var insertQueries []goqu.InsertDataset
for _, meter := range meters {
submeters := make([]NestedMeter, 0)
for _, r := range relations {
if r.MasterMeter == meter.Code {
for _, t := range tenements {
if t.Code == r.SlaveMeter {
submeters = append(submeters, NestedMeter{
Overall: t.Overall,
Critical: t.Critical,
Peak: t.Peak,
Flat: t.Flat,
Valley: t.Valley,
})
}
}
}
}
submetersJSON, err := json.Marshal(submeters)
if err != nil {
return err
}
overall, _ := json.Marshal(meter.Overall)
criyical, _ := json.Marshal(meter.Critical)
peak, _ := json.Marshal(meter.Peak)
flat, _ := json.Marshal(meter.Flat)
valley, _ := json.Marshal(meter.Valley)
insertQuery := goqu.Insert("report_pooled_consumption").
Cols("report_id", "pooled_meter_id", "overall", "critical", "peak", "flat", "valley", "pooled_area", "diluted").
Vals(goqu.Vals{rid, meter.Code, overall, criyical, peak, flat, valley, meter.CoveredArea, submetersJSON})
insertQueries = append(insertQueries, *insertQuery)
}
eg, _ := errgroup.WithContext(ctx)
for _, insertQuery := range insertQueries {
insertQuery := insertQuery // Capture loop variable
eg.Go(func() error {
sql, args, err := insertQuery.ToSQL()
if err != nil {
return err
}
_, err = tx.Exec(ctx, sql, args...)
return err
})
}
return eg.Wait()
}
func (cr _CalculateRepository) SaveReportTenement(tx pgx.Tx, report model.ReportIndex, tenements []model.Tenement, tenementCharges []calculate.TenementCharge) error {
if len(tenements) == 0 {
// 如果没有商户则直接返回
return nil
}
cr.log.Info("保存商户报表。")
ctx, cancel := global.TimeoutContext()
defer cancel()
insertQuery := cr.ds.
Insert("report_tenement")
var rows []goqu.Record
for _, tenement := range tenements {
tenementCharge := findTenementCharge(tenementCharges, tenement.Id)
tenementDetail, _ := json.Marshal(tenement)
overallJSON, _ := json.Marshal(tenementCharge.Overall)
criticalJSON, _ := json.Marshal(tenementCharge.Critical)
peakJSON, _ := json.Marshal(tenementCharge.Peak)
flatJSON, _ := json.Marshal(tenementCharge.Flat)
valleyJSON, _ := json.Marshal(tenementCharge.Valley)
lossJSON, _ := json.Marshal(tenementCharge.Loss)
submetersJSON, _ := json.Marshal(convertToNestedMeters(tenementCharge.Submeters))
poolingsJSON, _ := json.Marshal(convertToNestedMeters(tenementCharge.Poolings))
row := goqu.Record{
"report_id": report.Id,
"tenement_id": tenement.Id,
"tenement_detail": tenementDetail,
"calc_period": report.Period,
"overall": overallJSON,
"critical": criticalJSON,
"peak": peakJSON,
"flat": flatJSON,
"valley": valleyJSON,
"loss": lossJSON,
"basic_fee_pooled": tenementCharge.BasicFee,
"adjust_fee_pooled": tenementCharge.AdjustFee,
"loss_fee_pooled": tenementCharge.LossPooled,
"final_pooled": tenementCharge.PublicPooled,
"final_charge": tenementCharge.FinalCharges,
"meters": submetersJSON,
"pooled": poolingsJSON,
}
rows = append(rows, row)
}
sql, params, err := insertQuery.Rows(rows).Prepared(true).ToSQL()
if err != nil {
fmt.Println(err)
}
_, err = tx.Exec(ctx, sql, params...)
if err != nil {
fmt.Println(err.Error())
return err
}
return nil
}
// findTenementCharge 在 TenementCharges 切片中查找指定商户的核算内容
func findTenementCharge(charges []calculate.TenementCharge, tenementID string) calculate.TenementCharge {
for _, charge := range charges {
if charge.Tenement == tenementID {
return charge
}
}
return calculate.TenementCharge{}
}
// convertToNestedMeters 将 Meter 切片转换为 NestedMeter 切片
func convertToNestedMeters(meters []*calculate.Meter) []NestedMeter {
nestedMeters := []NestedMeter{}
for _, meter := range meters {
nestedMeters = append(nestedMeters, NestedMeter{
Overall: meter.Overall,
Critical: meter.Critical,
Peak: meter.Peak,
Flat: meter.Flat,
Valley: meter.Valley,
CoveredArea: meter.CoveredArea,
})
}
return nestedMeters
}

152
repository/charge.go Normal file
View File

@@ -0,0 +1,152 @@
package repository
import (
"context"
"electricity_bill_calc/config"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/types"
"fmt"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5"
"github.com/samber/lo"
"go.uber.org/zap"
)
type _ChargeRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var ChargeRepository = &_ChargeRepository{
log: logger.Named("Repository", "Charge"),
ds: goqu.Dialect("postgres"),
}
// 分页查询用户的充值记录
func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *types.Date, keyword *string) ([]*model.UserChargeDetail, int64, error) {
cr.log.Info("查询用户的充值记录。", logger.DateFieldp("beginTime", beginTime), logger.DateFieldp("endTime", endTime), zap.Stringp("keyword", keyword), zap.Uint("page", page))
ctx, cancel := global.TimeoutContext()
defer cancel()
chargeQuery := cr.ds.
From(goqu.T("user_charge").As("c")).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("c.user_id").Eq(goqu.I("ud.id")))).
Join(goqu.T("user").As("u"), goqu.On(goqu.I("ud.id").Eq(goqu.I("u.id")))).
Select(
"c.seq", "c.user_id", "ud.name", "c.fee", "c.discount", "c.amount", "c.charge_to",
"c.settled", "c.settled_at", "c.cancelled", "c.cancelled_at", "c.refunded", "c.refunded_at", "c.created_at",
)
countQuery := cr.ds.
From(goqu.T("user_charge").As("c")).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("c.user_id").Eq(goqu.I("ud.id")))).
Join(goqu.T("user").As("u"), goqu.On(goqu.I("ud.id").Eq(goqu.I("u.id")))).
Select(goqu.COUNT("*"))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
chargeQuery = chargeQuery.Where(goqu.Or(
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("u.username").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("u.username").ILike(pattern),
))
}
if beginTime != nil {
chargeQuery = chargeQuery.Where(goqu.I("c.created_at").Gte(beginTime.ToBeginningOfDate()))
countQuery = countQuery.Where(goqu.I("c.created_at").Gte(beginTime.ToBeginningOfDate()))
}
if endTime != nil {
chargeQuery = chargeQuery.Where(goqu.I("c.created_at").Lte(endTime.ToEndingOfDate()))
countQuery = countQuery.Where(goqu.I("c.created_at").Lte(endTime.ToEndingOfDate()))
}
chargeQuery = chargeQuery.Order(goqu.I("c.created_at").Desc())
currentPostion := (page - 1) * config.ServiceSettings.ItemsPageSize
chargeQuery = chargeQuery.Offset(currentPostion).Limit(config.ServiceSettings.ItemsPageSize)
chargeSql, chargeArgs, _ := chargeQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
var (
charges []*model.UserChargeDetail = make([]*model.UserChargeDetail, 0)
total int64
)
if err := pgxscan.Select(ctx, global.DB, &charges, chargeSql, chargeArgs...); err != nil {
cr.log.Error("查询用户的充值记录失败。", zap.Error(err))
return make([]*model.UserChargeDetail, 0), 0, err
}
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
cr.log.Error("查询用户的充值记录总数失败。", zap.Error(err))
return make([]*model.UserChargeDetail, 0), 0, err
}
return charges, total, nil
}
// 在用户充值记录中创建一条新的记录
func (cr _ChargeRepository) CreateChargeRecord(tx pgx.Tx, ctx context.Context, uid string, fee, discount, amount *float64, chargeTo types.Date) (bool, error) {
createQuery, createArgs, _ := cr.ds.
Insert(goqu.T("user_charge")).
Cols("user_id", "fee", "discount", "amount", "charge_to", "created_at").
Vals(goqu.Vals{uid, fee, discount, amount, chargeTo, types.Now()}).
Prepared(true).ToSQL()
rs, err := tx.Exec(ctx, createQuery, createArgs...)
if err != nil {
cr.log.Error("创建用户充值记录失败。", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 撤销用户的充值记录
func (cr _ChargeRepository) CancelCharge(tx pgx.Tx, ctx context.Context, uid string, seq int64) (bool, error) {
updateQuerySql, updateArgs, _ := cr.ds.
Update(goqu.T("user_charge")).
Set(goqu.Record{"cancelled": true, "cancelled_at": types.Now()}).
Where(goqu.I("user_id").Eq(uid), goqu.I("seq").Eq(seq)).
Prepared(true).ToSQL()
rs, err := tx.Exec(ctx, updateQuerySql, updateArgs...)
if err != nil {
cr.log.Error("撤销用户的充值记录失败。", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 检索用户最近有效的服务期限
func (cr _ChargeRepository) LatestValidChargeTo(tx pgx.Tx, ctx context.Context, uid string) (*types.Date, error) {
searchSql, searchArgs, _ := cr.ds.
From(goqu.T("user_charge")).
Select("charge_to").
Where(
goqu.I("settled").Eq(true),
goqu.I("cancelled").Eq(false),
goqu.I("refunded").Eq(false),
goqu.I("user_id").Eq(uid),
).
Prepared(true).ToSQL()
var chargeTo []*types.Date
if err := pgxscan.Select(ctx, tx, &chargeTo, searchSql, searchArgs...); err != nil {
cr.log.Error("检索用户有效服务期限列表失败。", zap.Error(err))
return nil, err
}
if len(chargeTo) == 0 {
return nil, fmt.Errorf("无法找到用户最近的有效服务期限。")
}
lastCharge := lo.MaxBy(chargeTo, func(a, b *types.Date) bool { return a.Time.After(b.Time) })
return lastCharge, nil
}

19
repository/god.go Normal file
View File

@@ -0,0 +1,19 @@
package repository
import (
"electricity_bill_calc/logger"
"github.com/doug-martin/goqu/v9"
"go.uber.org/zap"
)
type _GodModRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var GodModRepository = _GodModRepository{
log: logger.Named("Repository", "GodMod"),
ds: goqu.Dialect("postgres"),
}
// 删除指定园区中的表计和商户的绑定关系

449
repository/god_mode.go Normal file
View File

@@ -0,0 +1,449 @@
package repository
import (
"context"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"fmt"
"github.com/doug-martin/goqu/v9"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5"
"go.uber.org/zap"
)
type _GMRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var GMRepository = &_GMRepository{
log: logger.Named("Repository", "GM"),
ds: goqu.Dialect("postgres"),
}
func (gm _GMRepository) DeleteMeterBinding(ctx context.Context, tx pgx.Tx, pid string, tenements []string, meterCodes ...[]string) error {
DeleteQuery := gm.ds.From(goqu.T("tenement_meter")).
Where(goqu.I("park_id").Eq(pid)).
Delete()
if len(tenements) > 0 {
DeleteQuery = DeleteQuery.
Where(goqu.I("tenement_id").In(tenements))
}
if len(meterCodes) > 0 {
DeleteQuery = DeleteQuery.
Where(goqu.I("meter_id").In(meterCodes))
}
DeleteQuerySql, DeleteQueryArgs, _ := DeleteQuery.ToSQL()
_, err := tx.Exec(ctx, DeleteQuerySql, DeleteQueryArgs...)
if err != nil {
gm.log.Error("数据库在删除tenement_meter表数据中出错", zap.Error(err))
tx.Rollback(ctx)
return err
}
return nil
}
func (gm _GMRepository) DeleteTenements(ctx context.Context, tx pgx.Tx, pid string, tenements ...[]string) error {
DeleteTenements := gm.ds.
From("tenement").
Where(goqu.I("park_id").Eq(pid)).
Delete()
fmt.Println(len(tenements))
if len(tenements) > 0 {
DeleteTenements = DeleteTenements.
Where(goqu.I("id").In(tenements))
}
DeleteTenementsSql, DeleteTenementsArgs, _ := DeleteTenements.ToSQL()
_, err := tx.Exec(ctx, DeleteTenementsSql, DeleteTenementsArgs...)
if err != nil {
tx.Rollback(ctx)
gm.log.Error("删除商户信息出错", zap.Error(err))
return err
}
return nil
}
func (gm _GMRepository) DeleteInvoices(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error {
if len(val) > 0 {
updateQuery, updateQueryArgs, _ := gm.ds.
Update(goqu.T("report_tenement")).
Set(goqu.Record{"invoice": nil}).
Where(goqu.I("invoice").In(val)).
Where(
goqu.I("report_id").
Eq(
gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)),
),
).ToSQL()
_, err := tx.Exec(ctx, updateQuery, updateQueryArgs...)
if err != nil {
tx.Rollback(ctx)
gm.log.Error("更新发票记录出错", zap.Error(err))
return err
}
} else {
updateQuery, updateQueryArgs, _ := gm.ds.
Update(goqu.T("report_tenement")).
Set(goqu.Record{"invoice": nil}).
Where(
goqu.I("report_id").
Eq(gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)),
)).ToSQL()
_, err := tx.Exec(ctx, updateQuery, updateQueryArgs...)
if err != nil {
tx.Rollback(ctx)
gm.log.Error("更新发票记录出错", zap.Error(err))
return err
}
}
deleteQuery := gm.ds.
From(goqu.T("invoices")).
Where(goqu.I("park_id").Eq(parks)).
Delete()
if len(val) > 0 {
deleteQuery.Where(goqu.I("invoice_code").In(val))
}
deleteQuerySql, deleteQueryArgs, _ := deleteQuery.ToSQL()
_, err := tx.Exec(ctx, deleteQuerySql, deleteQueryArgs...)
if err != nil {
tx.Rollback(ctx)
gm.log.Error("删除指定园区发票记录出错", zap.Error(err))
return err
}
return nil
}
func (gm _GMRepository) DeleteMeterPoolings(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error {
deleteQuery := gm.ds.
Delete(goqu.T("meter_relations")).
Where(goqu.I("park_id").Eq(parks))
if len(val) > 0 {
deleteQuery = deleteQuery.
Where(
goqu.I("master_meter_id").In(val),
goqu.Or(goqu.I("slave_meter_id").In(val)),
)
}
deleteQuerySql, deleteQueryArgs, _ := deleteQuery.ToSQL()
_, err := tx.Exec(ctx, deleteQuerySql, deleteQueryArgs...)
if err != nil {
tx.Rollback(ctx)
gm.log.Error("删除指定园区中的表计分摊关系失败", zap.Error(err))
return err
}
return nil
}
func (gm _GMRepository) DeleteMeters(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error {
deleteQuery := gm.ds.
Delete(goqu.T("meter_04kv")).
Where(goqu.I("park_id").Eq(parks))
if len(val) > 0 {
deleteQuery = deleteQuery.Where(goqu.I("code").In(val))
}
deleteQuerySql, deleteQueryArgs, _ := deleteQuery.ToSQL()
_, err := tx.Exec(ctx, deleteQuerySql, deleteQueryArgs...)
if err != nil {
tx.Rollback(ctx)
gm.log.Error("删除指定园区的符合条件的标记出错", zap.Error(err))
return err
}
return nil
}
func (gm _GMRepository) DeleteReports(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error {
var err error
if len(val) > 0 {
deleteReportTenementQuerySql, deleteReportTenementQueryArgs, _ := gm.ds.
Delete(goqu.T("report_tenement")).
Where(goqu.I("report_id").In(
gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)).
Where(goqu.I("id").In(val)),
)).ToSQL()
_, err = tx.Exec(ctx, deleteReportTenementQuerySql, deleteReportTenementQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
deleteReportPooledConsumptionQuerySql, deleteReportPooledConsumptionQueryArgs, _ := gm.ds.
Delete(goqu.T("report_pooled_consumption")).
Where(goqu.I("report_id").In(
gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)).
Where(goqu.I("id").In(val)),
)).ToSQL()
_, err = tx.Exec(ctx, deleteReportPooledConsumptionQuerySql, deleteReportPooledConsumptionQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
deleteReportPublicConsumptionQuerySql, deleteReportPublicConsumptionQueryArgs, _ := gm.ds.
Delete(goqu.T("report_public_consumption")).
Where(goqu.I("report_id").In(
gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)).
Where(goqu.I("id").In(val)),
)).ToSQL()
_, err = tx.Exec(ctx, deleteReportPublicConsumptionQuerySql, deleteReportPublicConsumptionQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
deleteReportSummaryQuerySql, deleteReportSummaryQueryArgs, _ := gm.ds.
Delete(goqu.T("report_summary")).
Where(goqu.I("report_id").In(
gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)).
Where(goqu.I("id").In(val)),
)).ToSQL()
_, err = tx.Exec(ctx, deleteReportSummaryQuerySql, deleteReportSummaryQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
deleteReportTaskQuerySql, deleteReportTaskQueryArgs, _ := gm.ds.
Delete(goqu.T("report_task")).
Where(goqu.I("report_id").In(
gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)).
Where(goqu.I("id").In(val)),
)).ToSQL()
_, err = tx.Exec(ctx, deleteReportTaskQuerySql, deleteReportTaskQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
deleteReportQuerySql, deleteReportQueryArgs, _ := gm.ds.
Delete(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)).
Where(goqu.I("id").In(val)).ToSQL()
_, err = tx.Exec(ctx, deleteReportQuerySql, deleteReportQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
} else {
deleteReportTenementQuerySql, deleteReportTenementQueryArgs, _ := gm.ds.
Delete(goqu.T("report_tenement")).
Where(goqu.I("report_id").In(
gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)),
)).ToSQL()
_, err = tx.Exec(ctx, deleteReportTenementQuerySql, deleteReportTenementQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
deleteReportPooledConsumptionQuerySql, deleteReportPooledConsumptionQueryArgs, _ := gm.ds.
Delete(goqu.T("report_pooled_consumption")).
Where(goqu.I("report_id").In(
gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)),
)).ToSQL()
_, err = tx.Exec(ctx, deleteReportPooledConsumptionQuerySql, deleteReportPooledConsumptionQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
deleteReportPublicConsumptionQuerySql, deleteReportPublicConsumptionQueryArgs, _ := gm.ds.
Delete(goqu.T("report_public_consumption")).
Where(goqu.I("report_id").In(
gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)),
)).ToSQL()
_, err = tx.Exec(ctx, deleteReportPublicConsumptionQuerySql, deleteReportPublicConsumptionQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
deleteReportSummaryQuerySql, deleteReportSummaryQueryArgs, _ := gm.ds.
Delete(goqu.T("report_summary")).
Where(goqu.I("report_id").In(
gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)),
)).ToSQL()
_, err = tx.Exec(ctx, deleteReportSummaryQuerySql, deleteReportSummaryQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
deleteReportTaskQuerySql, deleteReportTaskQueryArgs, _ := gm.ds.
Delete(goqu.T("report_task")).
Where(goqu.I("report_id").In(
gm.ds.
From(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)),
)).ToSQL()
_, err = tx.Exec(ctx, deleteReportTaskQuerySql, deleteReportTaskQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
deleteReportQuerySql, deleteReportQueryArgs, _ := gm.ds.
Delete(goqu.T("report")).
Where(goqu.I("park_id").Eq(parks)).ToSQL()
_, err = tx.Exec(ctx, deleteReportQuerySql, deleteReportQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
}
return nil
}
func (gm _GMRepository) DeleteBuildings(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error {
if len(val) > 0 {
updateBulidingSql, updateBlidingArgs, _ := gm.ds.
Update(goqu.T("tenement")).
Set(goqu.Record{"building": nil}).
Where(goqu.I("park_id").Eq(parks)).
Where(goqu.I("building").In(
gm.ds.
From(goqu.I("park_building")).
Where(goqu.I("park_id").Eq(parks)).
Where(goqu.I("id").In(val)).
Select(goqu.I("id")),
)).ToSQL()
_, err := tx.Exec(ctx, updateBulidingSql, updateBlidingArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
} else {
updateBulidingSql, updateBlidingArgs, _ := gm.ds.
Update(goqu.T("tenement")).
Set(goqu.Record{"building": nil}).
Where(goqu.I("park_id").Eq(parks)).ToSQL()
_, err := tx.Exec(ctx, updateBulidingSql, updateBlidingArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
}
deleteQuery := gm.ds.
Delete(goqu.I("park_building")).
Where(goqu.I("park_id").Eq(parks))
if len(val) > 0 {
deleteQuery = deleteQuery.
Where(goqu.I("id").In(val))
}
deleteQuerySql, deleteQueryArgs, _ := deleteQuery.ToSQL()
_, err := tx.Exec(ctx, deleteQuerySql, deleteQueryArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
return nil
}
func (gm _GMRepository) DeleteParks(ctx context.Context, tx pgx.Tx, park []string) error {
deleteParksSql, deleteParksArgs, _ := gm.ds.
Delete(goqu.T("park")).
Where(goqu.I("id").In(park)).ToSQL()
_, err := tx.Exec(ctx, deleteParksSql, deleteParksArgs...)
if err != nil {
tx.Rollback(ctx)
return err
}
return nil
}
func (gm _GMRepository) ListAllParkIdsInUser(ctx context.Context, tx pgx.Tx, uid string) ([]string, error) {
SearchParkIdsSql, SearchParkIdsArgs, _ := gm.ds.
From(goqu.T("park")).
Where(goqu.I("user_id").Eq(uid)).
Select(goqu.I("id")).ToSQL()
var pids []string
err := pgxscan.Select(ctx, global.DB, &pids, SearchParkIdsSql, SearchParkIdsArgs...)
if err != nil {
gm.log.Error("查询["+uid+"]用户下的所有园区失败", zap.Error(err))
tx.Rollback(ctx)
return nil, err
}
return pids, nil
}
func (gm _GMRepository) DeleteUsers(ctx context.Context, tx pgx.Tx, uid string) error {
var err error
//删除用户关联
DeleteUserChargeSql, DeleteUserChargeArgs, _ := gm.ds.
Delete(goqu.T("user_charge")).
Where(goqu.I("id").Eq(uid)).ToSQL()
_, err = tx.Exec(ctx,DeleteUserChargeSql,DeleteUserChargeArgs...)
if err != nil {
gm.log.Error("user_charge表关联出错",zap.Error(err))
tx.Rollback(ctx)
return err
}
//删除用户详细信息
DeleteUserDetailSql, DeleteUserDetailArgs,_ := gm.ds.
Delete(goqu.T("user_detail")).
Where(goqu.I("id").Eq(uid)).ToSQL()
_, err = tx.Exec(ctx,DeleteUserDetailSql,DeleteUserDetailArgs...)
if err != nil {
gm.log.Error("user_detail表详细信息出错",zap.Error(err))
tx.Rollback(ctx)
return err
}
//删除用户基础信息
DeleteUserSql, DeleteUserArgs,_ := gm.ds.
Delete(goqu.T("users")).
Where(goqu.I("id").Eq(uid)).ToSQL()
_, err = tx.Exec(ctx,DeleteUserSql,DeleteUserArgs...)
if err != nil {
gm.log.Error("user表基础信息出错",zap.Error(err))
tx.Rollback(ctx)
return err
}
return nil
}

348
repository/invoice.go Normal file
View File

@@ -0,0 +1,348 @@
package repository
import (
"context"
"electricity_bill_calc/config"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/types"
"errors"
"fmt"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
type _InvoiceRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var InvoiceRepository = _InvoiceRepository{
log: logger.Named("Repository", "Invoice"),
ds: goqu.Dialect("postgres"),
}
// 查询指定园区中符合条件的发票
func (ir _InvoiceRepository) ListInvoice(pid *string, startDate, endDate *types.Date, keyword *string, page uint) ([]*model.Invoice, int64, error) {
ir.log.Info("查询指定园区的发票。", zap.Stringp("Park", pid), logger.DateFieldp("StartDate", startDate), logger.DateFieldp("EndDate", endDate), zap.Stringp("Keyword", keyword), zap.Uint("Page", page))
ctx, cancel := global.TimeoutContext()
defer cancel()
invoiceQuery := ir.ds.
From(goqu.T("invoice").As("i")).
Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("i.tenement_id").Eq(goqu.I("t.id")))).
Select("i.*")
countQuery := ir.ds.
From(goqu.T("invoice").As("i")).
Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("i.tenement_id").Eq(goqu.I("t.id")))).
Select(goqu.COUNT("*"))
if pid != nil && len(*pid) > 0 {
invoiceQuery = invoiceQuery.Where(goqu.I("t.park_id").Eq(*pid))
countQuery = countQuery.Where(goqu.I("t.park_id").Eq(*pid))
}
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
invoiceQuery = invoiceQuery.Where(goqu.Or(
goqu.I("i.invoice_no").ILike(pattern),
goqu.I("t.full_name").ILike(pattern),
goqu.I("t.short_name").ILike(pattern),
goqu.I("t.abbr").ILike(pattern),
goqu.I("t.contact_name").ILike(pattern),
goqu.I("t.contact_phone").ILike(pattern),
goqu.L("t.invoice_info->>'usci'").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("i.invoice_no").ILike(pattern),
goqu.I("t.full_name").ILike(pattern),
goqu.I("t.short_name").ILike(pattern),
goqu.I("t.abbr").ILike(pattern),
goqu.I("t.contact_name").ILike(pattern),
goqu.I("t.contact_phone").ILike(pattern),
goqu.L("t.invoice_info->>'usci'").ILike(pattern),
))
}
var queryRange = types.NewEmptyDateTimeRange()
if startDate != nil {
queryRange.SetLower(startDate.ToBeginningOfDate())
}
if endDate != nil {
queryRange.SetUpper(endDate.ToEndingOfDate())
}
if !queryRange.IsEmptyOrWild() {
invoiceQuery = invoiceQuery.Where(goqu.L("i.issued_at <@ ?", queryRange))
countQuery = countQuery.Where(goqu.L("i.issued_at <@ ?", queryRange))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
invoiceQuery = invoiceQuery.
Order(goqu.I("i.issued_at").Desc()).
Offset(startRow).
Limit(config.ServiceSettings.ItemsPageSize)
var (
invoices []*model.Invoice = make([]*model.Invoice, 0)
total int64
)
querySql, queryArgs, _ := invoiceQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &invoices, querySql, queryArgs...); err != nil {
ir.log.Error("查询发票记录失败。", zap.Error(err))
return invoices, 0, err
}
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
ir.log.Error("查询发票记录数失败。", zap.Error(err))
return invoices, 0, err
}
return invoices, total, nil
}
// 查询指定商户未开票的核算记录,改记录将只包括商户整体核算,不包括商户各个表计的详细
func (ir _InvoiceRepository) ListUninvoicedTenementCharges(tid string) ([]*model.SimplifiedTenementCharge, error) {
ir.log.Info("查询指定商户的未开票核算记录", zap.String("Tenement", tid))
ctx, cancel := global.TimeoutContext()
defer cancel()
chargeSql, chargeArgs, _ := ir.ds.
From(goqu.T("report_tenement").As("t")).
Join(goqu.T("report").As("r"), goqu.On(goqu.I("t.report_id").Eq(goqu.I("r.id")))).
Select(
goqu.I("t.report_id"),
goqu.I("r.period"),
goqu.L("(t.overall->>'amount')::numeric").As("amount"),
goqu.I("t.final_charge"),
).
Where(
goqu.I("t.tenement_id").Eq(tid),
goqu.I("t.invoice").IsNull(),
).
Prepared(true).ToSQL()
var charges []*model.SimplifiedTenementCharge
if err := pgxscan.Select(ctx, global.DB, &charges, chargeSql, chargeArgs...); err != nil {
ir.log.Error("查询未开票核算记录失败。", zap.Error(err))
return charges, err
}
return charges, nil
}
// 更新指定核算中指定商户的开票状态以及对应发票号。
// 如果给定了发票号,那么指定记录状态为已开票,如果给定的发票号为`nil`,啊么指定记录为未开票。
func (ir _InvoiceRepository) UpdateTenementInvoicedState(tx pgx.Tx, ctx context.Context, rid, tid string, invoiceNo *string) error {
ir.log.Info("更新指定核算中指定商户的开票状态和记录", zap.String("Report", rid), zap.String("Tenement", tid), zap.Stringp("InvoiceNo", invoiceNo))
updateSql, updateArgs, _ := ir.ds.
Update(goqu.T("report_tenement")).
Set(goqu.Record{
"invoice": invoiceNo,
}).
Where(
goqu.I("report_id").Eq(rid),
goqu.I("tenement_id").Eq(tid),
).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil {
ir.log.Error("更新核算记录的开票状态失败。", zap.Error(err))
return err
}
return nil
}
// 查询指定发票的详细记录信息
func (ir _InvoiceRepository) GetInvoiceDetail(invoiceNo string) (*model.Invoice, error) {
ir.log.Info("查询指定发票的详细信息", zap.String("InvoiceNo", invoiceNo))
ctx, cancel := global.TimeoutContext()
defer cancel()
invoiceSql, invoiceArgs, _ := ir.ds.
From(goqu.T("invoice")).
Select("*").
Where(goqu.I("invoice_no").Eq(invoiceNo)).
Prepared(true).ToSQL()
var invoice model.Invoice
if err := pgxscan.Get(ctx, global.DB, &invoice, invoiceSql, invoiceArgs...); err != nil {
ir.log.Error("查询发票记录失败。", zap.Error(err))
return nil, err
}
return &invoice, nil
}
// 获取指定商户的简化核算记录
func (ir _InvoiceRepository) GetSimplifiedTenementCharges(tid string, rids []string) ([]*model.SimplifiedTenementCharge, error) {
ir.log.Info("查询庄园商户的简化核算记录", zap.String("Tenement", tid), zap.Strings("Reports", rids))
ctx, cancel := global.TimeoutContext()
defer cancel()
chargeSql, chargeArgs, _ := ir.ds.
From(goqu.T("report_tenement").As("t")).
Join(goqu.T("report").As("r"), goqu.On(goqu.I("t.report_id").Eq(goqu.I("r.id")))).
Select(
goqu.I("t.report_id"),
goqu.I("r.period"),
goqu.L("(t.overall->>'amount')::numeric").As("amount"),
goqu.I("t.final_charge"),
).
Where(
goqu.I("t.tenement_id").Eq(tid),
goqu.I("t.report_id").In(rids),
).
Prepared(true).ToSQL()
var charges []*model.SimplifiedTenementCharge
if err := pgxscan.Select(ctx, global.DB, &charges, chargeSql, chargeArgs...); err != nil {
ir.log.Error("查询简化核算记录失败。", zap.Error(err))
return charges, err
}
return charges, nil
}
// 查询发票号码对应的商户 ID
// ! 这个方法不能被加入缓存,这个方法存在的目的就是为了清除缓存。
func (ir _InvoiceRepository) GetInvoiceBelongs(invoiceNo string) ([]string, error) {
ir.log.Info("查询发票号码对应的商户 ID", zap.String("InvoiceNo", invoiceNo))
ctx, cancel := global.TimeoutContext()
defer cancel()
tenementSql, tenementArgs, _ := ir.ds.
From(goqu.T("invoice")).
Select("tenement_id").
Where(goqu.I("i.invoice_no").Eq(invoiceNo)).
Prepared(true).ToSQL()
var tenementIds []string
if err := pgxscan.Select(ctx, global.DB, &tenementIds, tenementSql, tenementArgs...); err != nil {
ir.log.Error("查询发票号码对应的商户 ID 失败。", zap.Error(err))
return tenementIds, err
}
return tenementIds, nil
}
// 删除指定的发票记录
func (ir _InvoiceRepository) Delete(tx pgx.Tx, ctx context.Context, invoiceNo string) error {
ir.log.Info("删除指定的发票记录", zap.String("InvoiceNo", invoiceNo))
deleteSql, deleteArgs, _ := ir.ds.
Delete(goqu.T("invoice")).
Where(goqu.I("invoice_no").Eq(invoiceNo)).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, deleteSql, deleteArgs...); err != nil {
ir.log.Error("删除发票记录失败。", zap.Error(err))
return err
}
return nil
}
// 删除指定发票记录与指定核算记录之间的关联
func (ir _InvoiceRepository) DeleteInvoiceTenementRelation(tx pgx.Tx, ctx context.Context, invoiceNo string) error {
ir.log.Info("删除指定发票记录与指定核算记录之间的关联", zap.String("InvoiceNo", invoiceNo))
updateSql, updateArgs, _ := ir.ds.
Update(goqu.T("report_tenement")).
Set(goqu.Record{
"invoice": nil,
}).
Where(goqu.I("invoice").Eq(invoiceNo)).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil {
ir.log.Error("删除发票记录与核算记录之间的关联失败。", zap.Error(err))
return err
}
return nil
}
// 确认发票的归属
func (ir _InvoiceRepository) IsBelongsTo(invoiceNo, uid string) (bool, error) {
ir.log.Info("确认发票的归属", zap.String("InvoiceNo", invoiceNo), zap.String("User", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryArgs, _ := ir.ds.
From(goqu.T("invoice").As("i")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("i.park_id")))).
Select(goqu.COUNT("i.*")).
Where(
goqu.I("i.invoice_no").Eq(invoiceNo),
goqu.I("p.user_id").Eq(uid),
).
Prepared(true).ToSQL()
var count int64
if err := pgxscan.Get(ctx, global.DB, &count, querySql, queryArgs...); err != nil {
ir.log.Error("查询发票归属失败", zap.Error(err))
return false, err
}
return count > 0, nil
}
// 创建一条新的发票记录
func (ir _InvoiceRepository) Create(pid, tid, invoiceNo string, invoiceType *string, amount decimal.Decimal, issuedAt types.DateTime, taxMethod int16, taxRate decimal.Decimal, cargos *[]*model.InvoiceCargo, covers *[]string) error {
ir.log.Info("记录一个新的发票", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Invoice", invoiceNo))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
ir.log.Error("开启事务失败。", zap.Error(err))
return err
}
tenemenetSql, tenementArgs, _ := ir.ds.
From(goqu.T("tenement").As("t")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("t.building").Eq(goqu.I("b.id")))).
Select(
"t.*", goqu.I("b.name").As("building_name"),
).
Where(goqu.I("t.id").Eq(tid)).
Prepared(true).ToSQL()
var tenement model.Tenement
if err := pgxscan.Get(ctx, global.DB, &tenement, tenemenetSql, tenementArgs...); err != nil {
ir.log.Error("查询商户信息失败。", zap.Error(err))
tx.Rollback(ctx)
return err
}
if tenement.InvoiceInfo == nil {
ir.log.Error("尚未设定商户的发票抬头信息")
tx.Rollback(ctx)
return errors.New("尚未设定商户的发票抬头信息")
}
createSql, createArgs, _ := ir.ds.
Insert(goqu.T("invoice")).
Cols(
"invoice_no", "park_id", "tenement_id", "invoice_type", "amount", "issued_at", "tax_method", "tax_rate", "cargos", "covers",
).
Vals(goqu.Vals{
invoiceNo, pid, tid, invoiceType, amount, issuedAt, taxMethod, taxRate, cargos, covers,
}).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, createSql, createArgs...); err != nil {
ir.log.Error("创建发票记录失败。", zap.Error(err))
tx.Rollback(ctx)
return err
}
updateSql, updateArgs, _ := ir.ds.
Update(goqu.T("report_tenement")).
Set(goqu.Record{
"invoice": invoiceNo,
}).
Where(
goqu.I("tenement_id").Eq(tid),
goqu.I("report_id").In(*covers),
).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil {
ir.log.Error("更新核算记录的开票状态失败。", zap.Error(err))
tx.Rollback(ctx)
return err
}
err = tx.Commit(ctx)
if err != nil {
ir.log.Error("提交事务失败。", zap.Error(err))
tx.Rollback(ctx)
return err
}
return nil
}

1050
repository/meter.go Normal file

File diff suppressed because it is too large Load Diff

418
repository/park.go Normal file
View File

@@ -0,0 +1,418 @@
package repository
import (
"context"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"electricity_bill_calc/tools/serial"
"electricity_bill_calc/types"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5"
"go.uber.org/zap"
)
type _ParkRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var ParkRepository = _ParkRepository{
log: logger.Named("Repository", "Park"),
ds: goqu.Dialect("postgres"),
}
// 列出指定用户下的所有园区
func (pr _ParkRepository) ListAllParks(uid string) ([]*model.Park, error) {
pr.log.Info("列出指定用户下的所有园区", zap.String("uid", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var parks = make([]*model.Park, 0)
parkQuerySql, parkParams, _ := pr.ds.
From("park").
Select(
"id", "user_id", "name", "area", "tenement_quantity", "capacity", "category",
"meter_04kv_type", "region", "address", "contact", "phone", "enabled", "price_policy", "tax_rate",
"basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at",
"deleted_at","norm_authorized_loss_rate",
).
Where(
goqu.I("user_id").Eq(uid),
goqu.I("deleted_at").IsNull(),
).
Order(goqu.I("created_at").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &parks, parkQuerySql, parkParams...); err != nil {
pr.log.Error("列出指定用户下的所有园区失败!", zap.Error(err))
return make([]*model.Park, 0), err
}
return parks, nil
}
// 检查并确定指定园区的归属情况
func (pr _ParkRepository) IsParkBelongs(pid, uid string) (bool, error) {
pr.log.Info("检查并确定指定园区的归属情况", zap.String("pid", pid), zap.String("uid", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var count int64
parkQuerySql, parkParams, _ := pr.ds.
From("park").
Select(goqu.COUNT("*")).
Where(
goqu.I("id").Eq(pid),
goqu.I("user_id").Eq(uid),
).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &count, parkQuerySql, parkParams...); err != nil {
pr.log.Error("检查并确定指定园区的归属情况失败!", zap.Error(err))
return false, err
}
return count > 0, nil
}
// 创建一个属于指定用户的新园区。该创建功能不会对园区的名称进行检查。
func (pr _ParkRepository) CreatePark(ownerId string, park *model.Park) (bool, error) {
pr.log.Info("创建一个属于指定用户的新园区", zap.String("ownerId", ownerId))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
serial.StringSerialRequestChan <- 1
code := serial.Prefix("P", <-serial.StringSerialResponseChan)
createSql, createArgs, _ := pr.ds.
Insert("park").
Cols(
"id", "user_id", "name", "abbr", "area", "tenement_quantity", "capacity", "category",
"meter_04kv_type", "region", "address", "contact", "phone", "enabled", "price_policy", "tax_rate",
"basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at",
).
Vals(goqu.Vals{
code,
ownerId, park.Name, tools.PinyinAbbr(park.Name),
park.Area, park.TenementQuantity, park.Capacity, park.Category,
park.MeterType, park.Region, park.Address, park.Contact, park.Phone, park.Enabled, park.PricePolicy, park.TaxRate,
park.BasicPooled, park.AdjustPooled, park.LossPooled, park.PublicPooled, timeNow, timeNow,
}).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, createSql, createArgs...)
if err != nil {
pr.log.Error("创建一个属于指定用户的新园区失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 获取指定园区的详细信息
func (pr _ParkRepository) RetrieveParkDetail(pid string) (*model.Park, error) {
pr.log.Info("获取指定园区的详细信息", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var park model.Park
parkSql, parkArgs, _ := pr.ds.
From("park").
Select(
"id", "user_id", "name", "area", "tenement_quantity", "capacity", "category",
"meter_04kv_type", "region", "address", "contact", "phone", "enabled", "price_policy", "tax_rate",
"basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at",
"deleted_at",
).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &park, parkSql, parkArgs...); err != nil {
pr.log.Error("获取指定园区的详细信息失败!", zap.Error(err))
return nil, err
}
return &park, nil
}
// 获取园区对应的用户ID
func (pr _ParkRepository) RetrieveParkBelongs(pid string) (string, error) {
pr.log.Info("获取园区对应的用户ID", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var uid string
parkSql, parkArgs, _ := pr.ds.
From("park").
Select(goqu.I("user_id")).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &uid, parkSql, parkArgs...); err != nil {
pr.log.Error("获取园区对应的用户ID失败", zap.Error(err))
return "", err
}
return uid, nil
}
// 更新指定园区的信息
func (pr _ParkRepository) UpdatePark(pid string, park *model.Park) (bool, error) {
pr.log.Info("更新指定园区的信息", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park").
Set(goqu.Record{
"name": park.Name,
"abbr": tools.PinyinAbbr(park.Name),
"area": park.Area,
"tenement_quantity": park.TenementQuantity,
"capacity": park.Capacity,
"category": park.Category,
"meter_04kv_type": park.MeterType,
"region": park.Region,
"address": park.Address,
"contact": park.Contact,
"phone": park.Phone,
"price_policy": park.PricePolicy,
"tax_rate": park.TaxRate,
"basic_pooled": park.BasicPooled,
"adjust_pooled": park.AdjustPooled,
"loss_pooled": park.LossPooled,
"public_pooled": park.PublicPooled,
"last_modified_at": timeNow,
}).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
ok, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("更新指定园区的信息失败!", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 设定园区的可用状态
func (pr _ParkRepository) EnablingPark(pid string, enabled bool) (bool, error) {
pr.log.Info("设定园区的可用状态", zap.String("pid", pid), zap.Bool("enabled", enabled))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park").
Set(goqu.Record{
"enabled": enabled,
"last_modified_at": timeNow,
}).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
ok, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("设定园区的可用状态失败!", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 删除指定园区(软删除)
func (pr _ParkRepository) DeletePark(pid string) (bool, error) {
pr.log.Info("删除指定园区(软删除)", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park").
Set(goqu.Record{
"deleted_at": timeNow,
"last_modified_at": timeNow,
}).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
ok, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("删除指定园区(软删除)失败!", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 检索给定的园区详细信息列表
func (pr _ParkRepository) RetrieveParks(pids []string) ([]*model.Park, error) {
pr.log.Info("检索给定的园区详细信息列表", zap.Strings("pids", pids))
if len(pids) == 0 {
pr.log.Info("给定要检索的园区ID列表为空执行快速返回。")
return make([]*model.Park, 0), nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var parks []*model.Park
parkSql, parkArgs, _ := pr.ds.
From("park").
Where(goqu.I("id").In(pids)).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &parks, parkSql, parkArgs...); err != nil {
pr.log.Error("检索给定的园区详细信息列表失败!", zap.Error(err))
return nil, err
}
return parks, nil
}
// 获取指定园区中的建筑
func (pr _ParkRepository) RetrieveParkBuildings(pid string) ([]*model.ParkBuilding, error) {
pr.log.Info("获取指定园区中的建筑", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var buildings []*model.ParkBuilding
buildingSql, buildingArgs, _ := pr.ds.
From("park_building").
Where(
goqu.I("park_id").Eq(pid),
goqu.I("deleted_at").IsNull(),
).
Order(goqu.I("created_at").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &buildings, buildingSql, buildingArgs...); err != nil {
pr.log.Error("获取指定园区中的建筑失败!", zap.Error(err))
return nil, err
}
return buildings, nil
}
// 在指定园区中创建一个新建筑
func (pr _ParkRepository) CreateParkBuilding(pid, name string, floor *string) (bool, error) {
pr.log.Info("在指定园区中创建一个新建筑", zap.String("pid", pid), zap.String("name", name), zap.Stringp("floor", floor))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
serial.StringSerialRequestChan <- 1
code := serial.Prefix("B", <-serial.StringSerialResponseChan)
createSql, createArgs, _ := pr.ds.
Insert("park_building").
Cols(
"id", "park_id", "name", "floors", "enabled", "created_at", "last_modified_at",
).
Vals(goqu.Vals{
code,
pid, name, floor, true, timeNow, timeNow,
}).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, createSql, createArgs...)
if err != nil {
pr.log.Error("在指定园区中创建一个新建筑失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 在指定园区中创建一个建筑,这个方法会使用事务
func (pr _ParkRepository) CreateParkBuildingWithTransaction(tx pgx.Tx, ctx context.Context, pid, name string, floor *string) (bool, error) {
timeNow := types.Now()
serial.StringSerialRequestChan <- 1
code := serial.Prefix("B", <-serial.StringSerialResponseChan)
createSql, createArgs, _ := pr.ds.
Insert("park_building").
Cols(
"id", "park_id", "name", "floors", "enabled", "created_at", "last_modified_at",
).
Vals(goqu.Vals{
code,
pid, name, floor, true, timeNow, timeNow,
}).
Prepared(true).ToSQL()
rs, err := tx.Exec(ctx, createSql, createArgs...)
if err != nil {
pr.log.Error("在指定园区中创建一个新建筑失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 修改指定园区中指定建筑的信息
func (pr _ParkRepository) ModifyParkBuilding(id, pid, name string, floor *string) (bool, error) {
pr.log.Info("修改指定园区中指定建筑的信息", zap.String("id", id), zap.String("pid", pid), zap.String("name", name), zap.Stringp("floor", floor))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park_building").
Set(goqu.Record{
"name": name,
"floors": floor,
"last_modified_at": timeNow,
}).
Where(
goqu.I("id").Eq(id),
goqu.I("park_id").Eq(pid),
).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("修改指定园区中指定建筑的信息失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 修改指定建筑的可以状态
func (pr _ParkRepository) EnablingParkBuilding(id, pid string, enabled bool) (bool, error) {
pr.log.Info("修改指定建筑的可以状态", zap.String("id", id), zap.String("pid", pid), zap.Bool("enabled", enabled))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park_building").
Set(goqu.Record{
"enabled": enabled,
"last_modified_at": timeNow,
}).
Where(
goqu.I("id").Eq(id),
goqu.I("park_id").Eq(pid),
).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("修改指定建筑的可以状态失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 删除指定建筑(软删除)
func (pr _ParkRepository) DeleteParkBuilding(id, pid string) (bool, error) {
pr.log.Info("删除指定建筑(软删除)", zap.String("id", id), zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park_building").
Set(goqu.Record{
"deleted_at": timeNow,
"last_modified_at": timeNow,
}).
Where(
goqu.I("id").Eq(id),
goqu.I("park_id").Eq(pid),
).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("删除指定建筑(软删除)失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}

79
repository/region.go Normal file
View File

@@ -0,0 +1,79 @@
package repository
import (
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/georgysavva/scany/v2/pgxscan"
"go.uber.org/zap"
)
type _RegionRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var RegionRepository = _RegionRepository{
log: logger.Named("Repository", "Region"),
ds: goqu.Dialect("postgres"),
}
// 获取指定行政区划下所有直接子级行政区划
func (r *_RegionRepository) FindSubRegions(parent string) ([]model.Region, error) {
r.log.Info("获取指定行政区划下所有直接子级行政区划", zap.String("parent", parent))
ctx, cancel := global.TimeoutContext()
defer cancel()
var regions []model.Region
regionQuerySql, regionParams, _ := r.ds.
From("region").
Where(goqu.Ex{"parent": parent}).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &regions, regionQuerySql, regionParams...); err != nil {
r.log.Error("获取指定行政区划下所有直接子级行政区划失败!", zap.Error(err))
return nil, err
}
return regions, nil
}
// 获取一个指定编号的行政区划详细信息
func (r *_RegionRepository) FindRegion(code string) (*model.Region, error) {
r.log.Info("获取指定行政区划信息", zap.String("code", code))
ctx, cancel := global.TimeoutContext()
defer cancel()
var region model.Region
regionQuerySql, regionParams, _ := r.ds.
From("region").
Where(goqu.Ex{"code": code}).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &region, regionQuerySql, regionParams...); err != nil {
r.log.Error("获取指定行政区划信息失败!", zap.Error(err))
return nil, err
}
return &region, nil
}
// 获取指定行政区划的所有直接和非直接父级
func (r *_RegionRepository) FindParentRegions(code string) ([]*model.Region, error) {
r.log.Info("获取指定行政区划的所有直接和非直接父级", zap.String("code", code))
var (
regionsScanTask = []string{code}
regions = make([]*model.Region, 0)
)
for len(regionsScanTask) > 0 {
region, err := r.FindRegion(regionsScanTask[0])
regionsScanTask = append([]string{}, regionsScanTask[1:]...)
if err == nil && region != nil {
regions = append(regions, region)
if region.Parent != "0" {
regionsScanTask = append(regionsScanTask, region.Parent)
}
}
}
return regions, nil
}

878
repository/report.go Normal file
View File

@@ -0,0 +1,878 @@
package repository
import (
"electricity_bill_calc/config"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/tools/serial"
"electricity_bill_calc/types"
"electricity_bill_calc/vo"
"encoding/json"
"errors"
"fmt"
"log"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/samber/lo"
"go.uber.org/zap"
)
type _ReportRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var ReportRepository = _ReportRepository{
log: logger.Named("Repository", "Report"),
ds: goqu.Dialect("postgres"),
}
// 检查指定核算报表的归属情况
func (rr _ReportRepository) IsBelongsTo(rid, uid string) (bool, error) {
rr.log.Info("检查指定核算报表的归属", zap.String("User", uid), zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Select(goqu.COUNT("r.*")).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("p.user_id").Eq(uid),
).
Prepared(true).ToSQL()
var count int64
if err := pgxscan.Get(ctx, global.DB, &count, querySql, queryParams...); err != nil {
rr.log.Error("检查指定核算报表的归属出现错误", zap.Error(err))
return false, err
}
return count > 0, nil
}
// 获取指定用户下所有园区的尚未发布的简易核算报表索引内容
func (rr _ReportRepository) ListDraftReportIndicies(uid string) ([]*model.ReportIndex, error) {
rr.log.Info("获取指定用户下的所有尚未发布的报表索引", zap.String("User", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Select("r.*", goqu.I("t.status"), goqu.I("t.message")).
Where(
goqu.I("p.user_id").Eq(uid),
goqu.I("r.published").IsFalse(),
).
Order(goqu.I("r.created_at").Desc()).
Prepared(true).ToSQL()
var indicies []*model.ReportIndex = make([]*model.ReportIndex, 0)
if err := pgxscan.Select(ctx, global.DB, &indicies, querySql, queryParams...); err != nil {
rr.log.Error("获取指定用户下的所有尚未发布的报表索引出现错误", zap.Error(err))
return indicies, err
}
return indicies, nil
}
// 获取指定报表的详细索引内容
func (rr _ReportRepository) GetReportIndex(rid string) (*model.ReportIndex, error) {
rr.log.Info("获取指定报表的详细索引", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Select("r.*", goqu.I("t.status"), goqu.I("t.message")).
Where(goqu.I("r.id").Eq(rid)).
Prepared(true).ToSQL()
var index model.ReportIndex
if err := pgxscan.Get(ctx, global.DB, &index, querySql, queryParams...); err != nil {
rr.log.Error("获取指定报表的详细索引出现错误", zap.Error(err))
return nil, err
}
return &index, nil
}
// 为指园区创建一个新的核算报表
func (rr _ReportRepository) CreateReport(form *vo.ReportCreationForm) (bool, string, error) {
rr.log.Info("为指定园区创建一个新的核算报表", zap.String("Park", form.Park))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
rr.log.Error("未能开始一个数据库事务", zap.Error(err))
return false, "", err
}
park, err := ParkRepository.RetrieveParkDetail(form.Park)
if err != nil || park == nil {
rr.log.Error("未能获取指定园区的详细信息", zap.Error(err))
tx.Rollback(ctx)
return false, "", exceptions.NewNotFoundErrorFromError("未能获取指定园区的详细信息", err)
}
createTime := types.Now()
periodRange := types.NewDateRange(&form.PeriodBegin, &form.PeriodEnd)
serial.StringSerialRequestChan <- 1
reportId := serial.Prefix("R", <-serial.StringSerialResponseChan)
createSql, createArgs, _ := rr.ds.
Insert(goqu.T("report")).
Cols(
"id", "park_id", "period", "category", "meter_04kv_type", "price_policy",
"basis_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at",
"last_modified_at",
).
Vals(goqu.Vals{
reportId, park.Id, periodRange, park.Category, park.MeterType, park.PricePolicy,
park.BasicPooled, park.AdjustPooled, park.LossPooled, park.PublicPooled, createTime,
createTime,
}).
Prepared(true).ToSQL()
critical := model.ConsumptionUnit{
Amount: form.Critical,
Fee: form.CriticalFee,
}
criticalData, _ := json.Marshal(critical)
overall := model.ConsumptionUnit{
Amount: form.Overall,
Fee: form.OverallFee,
}
overallData, _ := json.Marshal(overall)
peak := model.ConsumptionUnit{
Amount: form.Peak,
Fee: form.PeakFee,
}
peakData, _ := json.Marshal(peak)
flat := model.ConsumptionUnit{
Amount: form.Flat,
Fee: form.FlatFee,
}
flatData, _ := json.Marshal(flat)
valley := model.ConsumptionUnit{
Amount: form.Valley,
Fee: form.ValleyFee,
}
valleyData, _ := json.Marshal(valley)
summarySql, summaryArgs, err5 := rr.ds.
Insert(goqu.T("report_summary")).
//Cols("report_id", "overall", "critical", "peak", "flat", "valley", "basic_fee", "adjust_fee").
Rows(goqu.Record{
"report_id": reportId,
"overall": string(overallData),
"critical": string(criticalData),
"peak": string(peakData),
"flat": string(flatData),
"valley": string(valleyData),
"basic_fee": form.BasicFee,
"adjust_fee": form.AdjustFee,
}).Prepared(true).ToSQL()
log.Println("errrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr:", err5)
taskSql, taskArgs, _ := rr.ds.
Insert(goqu.T("report_task")).
Cols("id", "status", "last_modified_at").
Vals(goqu.Vals{reportId, model.REPORT_CALCULATE_TASK_STATUS_PENDING, createTime}).
Prepared(true).ToSQL()
resIndex, err := tx.Exec(ctx, createSql, createArgs...)
if err != nil {
rr.log.Error("创建核算报表索引时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, "", err
}
if resIndex.RowsAffected() == 0 {
rr.log.Error("保存核算报表索引时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, "", exceptions.NewUnsuccessCreateError("创建核算报表索引时出现错误")
}
resSummary, err := tx.Exec(ctx, summarySql, summaryArgs...)
if err != nil {
rr.log.Error("创建核算报表汇总时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, "", err
}
log.Println("?????????", summarySql, summaryArgs)
log.Println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", resSummary.RowsAffected())
if resSummary.RowsAffected() == 0 {
rr.log.Error("保存核算报表汇总时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, "", exceptions.NewUnsuccessCreateError("创建核算报表汇总时出现错误")
}
resTask, err := tx.Exec(ctx, taskSql, taskArgs...)
if err != nil {
rr.log.Error("创建核算报表任务时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, "", err
}
if resTask.RowsAffected() == 0 {
rr.log.Error("保存核算报表任务时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, "", exceptions.NewUnsuccessCreateError("创建核算报表任务时出现错误")
}
err = tx.Commit(ctx)
if err != nil {
rr.log.Error("提交核算报表创建事务时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, "", err
}
return resIndex.RowsAffected() > 0 && resSummary.RowsAffected() > 0 && resTask.RowsAffected() > 0, reportId, nil
}
// 更新报表的基本信息
func (rr _ReportRepository) UpdateReportSummary(rid string, form *vo.ReportModifyForm) (bool, error) {
rr.log.Info("更新指定报表的基本信息", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
rr.log.Error("未能开始一个数据库事务", zap.Error(err))
return false, err
}
updateTime := types.Now()
newPeriod := types.NewDateRange(&form.PeriodBegin, &form.PeriodEnd)
udpateIndexSql, updateIndexArgs, _ := rr.ds.
Update(goqu.T("report")).
Set(goqu.Record{
"period": newPeriod,
"last_modified_at": updateTime,
}).
Where(goqu.I("id").Eq(rid)).
Prepared(true).ToSQL()
updateSummarySql, updateSummaryArgs, _ := rr.ds.
Update(goqu.T("report_summary")).
Set(goqu.Record{
"overall": model.ConsumptionUnit{Amount: form.Overall, Fee: form.OverallFee},
"critical": model.ConsumptionUnit{Amount: form.Critical, Fee: form.CriticalFee},
"peak": model.ConsumptionUnit{Amount: form.Peak, Fee: form.PeakFee},
"flat": model.ConsumptionUnit{Amount: form.Flat, Fee: form.FlatFee},
"valley": model.ConsumptionUnit{Amount: form.Valley, Fee: form.ValleyFee},
"basic_fee": form.BasicFee,
"adjust_fee": form.AdjustFee,
}).
Where(goqu.I("report_id").Eq(rid)).
Prepared(true).ToSQL()
resIndex, err := tx.Exec(ctx, udpateIndexSql, updateIndexArgs...)
if err != nil {
rr.log.Error("更新核算报表索引时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
if resIndex.RowsAffected() == 0 {
rr.log.Error("保存核算报表索引时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, exceptions.NewUnsuccessUpdateError("更新核算报表索引时出现错误")
}
resSummary, err := tx.Exec(ctx, updateSummarySql, updateSummaryArgs...)
if err != nil {
rr.log.Error("更新核算报表汇总时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
if resSummary.RowsAffected() == 0 {
rr.log.Error("保存核算报表汇总时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, exceptions.NewUnsuccessUpdateError("更新核算报表汇总时出现错误")
}
err = tx.Commit(ctx)
if err != nil {
rr.log.Error("提交核算报表更新事务时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
return resIndex.RowsAffected() > 0 && resSummary.RowsAffected() > 0, nil
}
// 获取指定报表的总览信息
func (rr _ReportRepository) RetrieveReportSummary(rid string) (*model.ReportSummary, error) {
rr.log.Info("获取指定报表的总览信息", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report_summary")).
Select("*").
Where(goqu.I("report_id").Eq(rid)).
Prepared(true).ToSQL()
var summary model.ReportSummary
if err := pgxscan.Get(ctx, global.DB, &summary, querySql, queryParams...); err != nil {
rr.log.Error("获取指定报表的总览信息时出现错误", zap.Error(err))
return nil, err
}
return &summary, nil
}
// 获取指定用户的尚未发布的核算报表的计算状态
func (rr _ReportRepository) GetReportTaskStatus(uid string) ([]*model.ReportTask, error) {
rr.log.Info("获取指定用户的尚未发布的核算报表的计算状态", zap.String("User", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report_task").As("t")).
Join(goqu.T("report").As("r"), goqu.On(goqu.I("r.id").Eq(goqu.I("t.id")))).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Select(
goqu.I("t.*"),
).
Where(
goqu.I("p.user_id").Eq(uid),
goqu.I("r.published").IsFalse(),
).
Prepared(true).ToSQL()
var tasks []*model.ReportTask = make([]*model.ReportTask, 0)
if err := pgxscan.Select(ctx, global.DB, &tasks, querySql, queryParams...); err != nil {
rr.log.Error("获取指定用户的尚未发布的核算报表的计算状态时出现错误", zap.Error(err))
return tasks, err
}
return tasks, nil
}
// 检索指定核算报表中园区公共表计的核算记录
func (rr _ReportRepository) ListPublicMetersInReport(rid string, page uint, keyword *string) ([]*vo.ReportPublishResponse, int64, error) {
rr.log.Info("检索指定核算报表中园区公共表计的核算记录", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
reportQuery := rr.ds.
From(goqu.T("report_public_consumption").As("r")).
Join(goqu.T("report").As("ri"), goqu.On(goqu.I("ri.id").Eq(goqu.I("r.report_id")))).
Join(goqu.T("meter_04kv").As("m"), goqu.On(
goqu.I("m.code").Eq(goqu.I("r.park_meter_id")),
goqu.I("m.park_id").Eq(goqu.I("ri.park_id")),
)).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))).
Select(
goqu.I("r.*"), goqu.I("m.*"), goqu.I("b.name").As("building_name"),
).
Where(goqu.I("r.report_id").Eq(rid))
countQuery := rr.ds.
From(goqu.T("report_public_consumption").As("r")).
Join(goqu.T("report").As("ri"), goqu.On(goqu.I("ri.id").Eq(goqu.I("r.report_id")))).
Join(goqu.T("meter_04kv").As("m"), goqu.On(
goqu.I("m.code").Eq(goqu.I("r.park_meter_id")),
goqu.I("m.park_id").Eq(goqu.I("ri.park_id")),
)).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))).
Select(goqu.COUNT(goqu.I("r.*"))).
Where(goqu.I("r.report_id").Eq(rid))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
reportQuery = reportQuery.Where(goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
reportQuery = reportQuery.
Order(goqu.I("m.code").Asc()).
Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
var (
consumptions = make([]*vo.ReportPublishResponse, 0)
count int64
)
querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &consumptions, querySql, queryArgs...); err != nil {
rr.log.Error("检索指定核算报表中园区公共表计的核算记录时出现错误", zap.Error(err))
return consumptions, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
rr.log.Error("检索指定核算报表中园区公共表计的核算记录时出现错误", zap.Error(err))
return consumptions, 0, err
}
return consumptions, count, nil
}
// 检索指定核算报表中公摊表计的核算记录
func (rr _ReportRepository) ListPooledMetersInReport(rid string, page uint, keyword *string) ([]*model.ReportDetailedPooledConsumption, int64, error) {
rr.log.Info("检索指定核算报表中公摊表计的核算记录", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
reportQuery := rr.ds.
From(goqu.T("report_pooled_consumption").As("r")).
Join(
goqu.T("report").As("ri"), goqu.On(goqu.I("ri.id").Eq(goqu.I("r.report_id")))).
Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.pooled_meter_id")))).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("ri.park_id")))).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))).
Select(
goqu.I("r.*"), goqu.I("m.*"), goqu.I("b.name").As("building_name"), goqu.I("p.public_pooled"),
).
Where(goqu.I("r.report_id").Eq(rid))
reportQuery = reportQuery.Where(goqu.I("m.park_id").Eq(goqu.I("p.id")))
countQuery := rr.ds.
From(goqu.T("report_pooled_consumption").As("r")).
Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.pooled_meter_id")))).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))).
Select(goqu.COUNT(goqu.I("r.*"))).
Where(goqu.I("r.report_id").Eq(rid))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
reportQuery = reportQuery.Where(goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
reportQuery = reportQuery.
Order(goqu.I("m.code").Asc()).
Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
var (
consumptions []*model.ReportDetailedPooledConsumption = make([]*model.ReportDetailedPooledConsumption, 0)
count int64
)
querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &consumptions, querySql, queryArgs...); err != nil {
rr.log.Error("检索指定核算报表中公摊表计的核算记录时出现错误", zap.Error(err))
return consumptions, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
rr.log.Error("检索指定核算报表中公摊表计的核算记录时出现错误", zap.Error(err))
return consumptions, 0, err
}
return consumptions, count, nil
}
// 列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计及分摊详细
func (rr _ReportRepository) ListPooledMeterDetailInReport(rid, mid string) ([]*model.ReportDetailNestedMeterConsumption, error) {
rr.log.Info("列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计及分摊详细", zap.String("Report", rid), zap.String("Meter", mid))
ctx, cancel := global.TimeoutContext()
defer cancel()
meterSql, meterArgs, _ := rr.ds.
From(goqu.T("report_pooled_consumption")).
Select("*").
Where(goqu.I("report_id").Eq(rid), goqu.I("pooled_meter_id").Eq(mid)).
Prepared(true).ToSQL()
var meter model.ReportPooledConsumption
if err := pgxscan.Get(ctx, global.DB, &meter, meterSql, meterArgs...); err != nil {
rr.log.Error("列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计编号时出现错误", zap.Error(err))
return make([]*model.ReportDetailNestedMeterConsumption, 0), err
}
meterCodes := lo.Map(meter.Diluted, func(m model.NestedMeter, _ int) string {
return m.MeterId
})
meterSql, meterArgs, _ = rr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))).
Select(
goqu.I("m.*"), goqu.I("b.name").As("building_name"),
).
Where(goqu.I("m.code").In(meterCodes)).
Prepared(true).ToSQL()
var meterDetails []*model.MeterDetail = make([]*model.MeterDetail, 0)
if err := pgxscan.Select(ctx, global.DB, &meterDetails, meterSql, meterArgs...); err != nil {
rr.log.Error("列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计详细时出现错误", zap.Error(err))
return make([]*model.ReportDetailNestedMeterConsumption, 0), err
}
if len(meterDetails) <= 0 {
return make([]*model.ReportDetailNestedMeterConsumption, 0), errors.New("暂无分摊关系")
}
assembled := lo.Map(meter.Diluted, func(m model.NestedMeter, _ int) *model.ReportDetailNestedMeterConsumption {
meterDetail, _ := lo.Find(meterDetails, func(elem *model.MeterDetail) bool {
return elem.Code == m.MeterId
})
return &model.ReportDetailNestedMeterConsumption{
Meter: *meterDetail,
Consumption: m,
}
})
return assembled, nil
}
// 列出指定核算报表下商户的简要计费信息
func (rr _ReportRepository) ListTenementInReport(rid string, page uint, keyword *string) ([]*model.ReportTenement, int64, error) {
rr.log.Info("查询指定核算报表下的商户简要计费信息", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
reportQuery := rr.ds.
From(goqu.T("report_tenement").As("rt")).
Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("rt.tenement_id")))).
Select("rt.*").
Where(goqu.I("rt.report_id").Eq(rid))
countQuery := rr.ds.
From(goqu.T("report_tenement").As("rt")).
Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("rt.tenement_id")))).
Select(goqu.COUNT(goqu.I("rt.*"))).
Where(goqu.I("rt.report_id").Eq(rid))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
reportQuery = reportQuery.Where(goqu.Or(
goqu.I("t.full_name").ILike(pattern),
goqu.T("t.short_name").ILike(pattern),
goqu.I("t.abbr").ILike(pattern),
goqu.I("t.address").ILike(pattern),
goqu.I("t.contact_name").ILike(pattern),
goqu.I("t.contact_phone").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("t.full_name").ILike(pattern),
goqu.T("t.short_name").ILike(pattern),
goqu.I("t.abbr").ILike(pattern),
goqu.I("t.address").ILike(pattern),
goqu.I("t.contact_name").ILike(pattern),
goqu.I("t.contact_phone").ILike(pattern),
))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
reportQuery = reportQuery.
Order(goqu.I("t.moved_in_at").Asc()).
Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
var (
tenements = make([]*model.ReportTenement, 0)
count int64
)
querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
fmt.Println(querySql, rid)
if err := pgxscan.Select(ctx, global.DB, &tenements, querySql, queryArgs...); err != nil {
rr.log.Error("查询指定核算报表下的商户简要计费信息时出现错误", zap.Error(err))
return tenements, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
rr.log.Error("查询指定核算报表下的商户简要计费信息总数量时出现错误", zap.Error(err))
return tenements, 0, err
}
return tenements, count, nil
}
// 获取指定核算报表中指定商户的详细核算信息
func (rr _ReportRepository) GetTenementDetailInReport(rid, tid string) (*model.ReportTenement, error) {
rr.log.Info("获取指定核算报表中指定商户的详细核算信息", zap.String("Report", rid), zap.String("Tenement", tid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report_tenement").As("rt")).
Select("rt.*").
Where(goqu.I("rt.report_id").Eq(rid), goqu.I("rt.tenement_id").Eq(tid)).
Prepared(true).ToSQL()
var tenement model.ReportTenement
if err := pgxscan.Get(ctx, global.DB, &tenement, querySql, queryParams...); err != nil {
rr.log.Error("获取指定核算报表中指定商户的详细核算信息时出现错误", zap.Error(err))
return nil, err
}
return &tenement, nil
}
// 更改指定核算报表的状态为已发布
func (rr _ReportRepository) PublishReport(rid string) (bool, error) {
rr.log.Info("更改指定核算报表的状态为已发布", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
currentTime := types.Now()
updateSql, updateArgs, _ := rr.ds.
Update(goqu.T("report")).
Set(goqu.Record{
"published": true,
"published_at": currentTime,
}).
Where(goqu.I("id").Eq(rid)).
Prepared(true).ToSQL()
res, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
rr.log.Error("更改指定核算报表的状态为已发布时出现错误", zap.Error(err))
return false, err
}
return res.RowsAffected() > 0, nil
}
// 对指定核算报表进行综合检索
func (rr _ReportRepository) ComprehensiveReportSearch(uid, pid *string, page uint, keyword *string, start, end *types.Date) ([]*model.ReportIndex, int64, error) {
rr.log.Info("对指定核算报表进行综合检索", zap.Stringp("User", uid), zap.Stringp("Park", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
reportQuery := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))).
LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Select("r.*", goqu.I("t.status"), goqu.I("t.message"))
countQuery := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))).
LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Select(goqu.COUNT(goqu.I("r.*")))
if uid != nil && len(*uid) > 0 {
reportQuery = reportQuery.Where(goqu.I("ud.id").Eq(*uid))
countQuery = countQuery.Where(goqu.I("ud.id").Eq(*uid))
}
if pid != nil && *pid != "" {
reportQuery = reportQuery.Where(goqu.I("p.id").Eq(*pid))
countQuery = countQuery.Where(goqu.I("p.id").Eq(*pid))
}
queryDateRange := types.NewDateRange(start, end)
reportQuery = reportQuery.Where(goqu.L("r.period <@ ?", queryDateRange))
countQuery = countQuery.Where(goqu.L("r.period <@ ?", queryDateRange))
reportQuery = reportQuery.Where(goqu.I("r.published").Eq(true))
countQuery = countQuery.Where(goqu.I("r.published").Eq(true))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
reportQuery = reportQuery.Where(goqu.Or(
goqu.I("p.name").ILike(pattern),
goqu.I("p.abbr").ILike(pattern),
goqu.I("p.address").ILike(pattern),
goqu.I("p.contact").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("ud.contact").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("p.name").ILike(pattern),
goqu.I("p.abbr").ILike(pattern),
goqu.I("p.address").ILike(pattern),
goqu.I("p.contact").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("ud.contact").ILike(pattern),
))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
reportQuery = reportQuery.
Order(goqu.I("r.created_at").Desc()).
Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
var (
reports []*model.ReportIndex = make([]*model.ReportIndex, 0)
count int64
)
querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &reports, querySql, queryArgs...); err != nil {
rr.log.Error("对指定核算报表进行综合检索时出现错误", zap.Error(err))
return reports, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
rr.log.Error("对指定核算报表进行综合检索总数量时出现错误", zap.Error(err))
return reports, 0, err
}
return reports, count, nil
}
// 判断指定报表是否是当前园区的最后一张报表
func (rr _ReportRepository) IsLastReport(rid string) (bool, error) {
rr.log.Info("判断指定报表是否是当前园区的最后一张报表", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
checkSql, checkArgs, _ := rr.ds.
From(goqu.T("report").As("r")).
Select(goqu.COUNT("*")).
Where(
goqu.I("r.id").Eq(rid),
goqu.Func("lower", goqu.I("r.period")).Gte(rr.ds.
From(goqu.T("report").As("ri")).
Select(goqu.Func("max", goqu.Func("upper", goqu.I("ri.period")))).
Where(
goqu.I("ri.park_id").Eq(goqu.I("r.park_id")),
goqu.I("ri.id").Neq(rid),
),
),
goqu.I("r.published").IsTrue(),
).
Prepared(true).ToSQL()
var count int64
if err := pgxscan.Get(ctx, global.DB, &count, checkSql, checkArgs...); err != nil {
rr.log.Error("判断指定报表是否是当前园区的最后一张报表时出现错误", zap.Error(err))
return false, err
}
return count > 0, nil
}
// 申请撤回指定的核算报表
func (rr _ReportRepository) ApplyWithdrawReport(rid string) (bool, error) {
rr.log.Info("申请撤回指定的核算报表", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
currentTime := types.Now()
updateSql, updateArgs, _ := rr.ds.
Update(goqu.T("report")).
Set(goqu.Record{
"withdraw": model.REPORT_WITHDRAW_APPLYING,
"last_withdraw_applied_at": currentTime,
}).
Where(goqu.I("id").Eq(rid)).
Prepared(true).ToSQL()
res, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
rr.log.Error("申请撤回指定的核算报表时出现错误", zap.Error(err))
return false, err
}
return res.RowsAffected() > 0, nil
}
// 批准核算报表的撤回申请
func (rr _ReportRepository) ApproveWithdrawReport(rid string) (bool, error) {
rr.log.Info("批准核算报表的撤回申请", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
currentTime := types.Now()
updateSql, updateArgs, _ := rr.ds.
Update(goqu.T("report")).
Set(goqu.Record{
"withdraw": model.REPORT_WITHDRAW_GRANTED,
"last_withdraw_audit_at": currentTime,
"published": false,
"published_at": nil,
}).
Where(goqu.I("id").Eq(rid)).
Prepared(true).ToSQL()
res, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
rr.log.Error("批准核算报表的撤回申请时出现错误", zap.Error(err))
return false, err
}
return res.RowsAffected() > 0, nil
}
// 拒绝核算报表的撤回申请
func (rr _ReportRepository) RejectWithdrawReport(rid string) (bool, error) {
rr.log.Info("拒绝核算报表的撤回申请", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
currentTime := types.Now()
updateSql, updateArgs, _ := rr.ds.
Update(goqu.T("report")).
Set(goqu.Record{
"withdraw": model.REPORT_WITHDRAW_DENIED,
"last_withdraw_audit_at": currentTime,
}).
Where(goqu.I("id").Eq(rid)).
Prepared(true).ToSQL()
res, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
rr.log.Error("拒绝核算报表的撤回申请时出现错误", zap.Error(err))
return false, err
}
return res.RowsAffected() > 0, nil
}
// 列出当前正在等待审核的已经申请撤回的核算报表
func (rr _ReportRepository) ListWithdrawAppliedReports(page uint, keyword *string) ([]*model.ReportIndex, int64, error) {
rr.log.Info("列出当前正在等待审核的已经申请撤回的核算报表")
ctx, cancel := global.TimeoutContext()
defer cancel()
reportQuery := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))).
LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Select("r.*", goqu.I("t.status"), goqu.I("t.message")).
Where(goqu.I("r.withdraw").Eq(model.REPORT_WITHDRAW_APPLYING))
countQuery := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))).
LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Select(goqu.COUNT(goqu.I("r.*"))).
Where(goqu.I("r.withdraw").Eq(model.REPORT_WITHDRAW_APPLYING))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
reportQuery = reportQuery.Where(goqu.Or(
goqu.I("p.name").ILike(pattern),
goqu.I("p.abbr").ILike(pattern),
goqu.I("p.address").ILike(pattern),
goqu.I("p.contact").ILike(pattern),
goqu.I("p.phone").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("ud.contact").ILike(pattern),
goqu.I("ud.phone").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("p.name").ILike(pattern),
goqu.I("p.abbr").ILike(pattern),
goqu.I("p.address").ILike(pattern),
goqu.I("p.contact").ILike(pattern),
goqu.I("p.phone").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("ud.contact").ILike(pattern),
goqu.I("ud.phone").ILike(pattern),
))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
reportQuery = reportQuery.
Order(goqu.I("r.last_withdraw_applied_at").Desc()).
Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
var (
reports []*model.ReportIndex = make([]*model.ReportIndex, 0)
count int64
)
querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &reports, querySql, queryArgs...); err != nil {
rr.log.Error("列出当前正在等待审核的已经申请撤回的核算报表时出现错误", zap.Error(err))
return reports, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
rr.log.Error("列出当前正在等待审核的已经申请撤回的核算报表总数量时出现错误", zap.Error(err))
return reports, 0, err
}
return reports, count, nil
}

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