Compare commits

...

138 Commits

Author SHA1 Message Date
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
147 changed files with 13737 additions and 7506 deletions

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>

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

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

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 GOPROXY="https://goproxy.io"
@ -9,7 +9,7 @@ RUN go mod download && go mod verify
ADD . /app
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 apk add --no-cache tzdata
RUN apk update \

Binary file not shown.

40
cache/abstract.go vendored
View File

@ -40,7 +40,7 @@ func Cache[T interface{}](key string, value *T, expires time.Duration) error {
}
// 从Redis缓存中获取一个数据
func Retreive[T interface{}](key string) (*T, error) {
func Retrieve[T interface{}](key string) (*T, error) {
getCmd := global.Rd.B().Get().Key(key).Build()
result := global.Rd.Do(global.Ctx, getCmd)
if result.Error() != nil {
@ -81,23 +81,23 @@ func Delete(key string) (bool, error) {
return count > 0, err
}
func dissembleScan(result rueidis.RedisResult) (int64, []string, error) {
func dissembleScan(result rueidis.RedisResult) (uint64, []string, error) {
var (
err error
cursor int64
cursor uint64
keys = make([]string, 0)
)
results, err := result.ToArray()
if err != nil {
return -1, keys, err
return 0, keys, err
}
cursor, err = results[0].AsInt64()
cursor, err = results[0].AsUint64()
if err != nil {
return -1, keys, err
return 0, keys, err
}
keys, err = results[1].AsStrSlice()
if err != nil {
return -1, keys, err
return 0, keys, err
}
return cursor, keys, err
}
@ -106,7 +106,7 @@ func dissembleScan(result rueidis.RedisResult) (int64, []string, error) {
func DeleteAll(pattern string) error {
var (
err error
cursor int64
cursor uint64
keys = make([]string, 0)
sKeys []string
)
@ -137,3 +137,27 @@ func CacheKey(category string, ids ...string) 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
}

10
cache/count.go vendored
View File

@ -10,7 +10,7 @@ type _CountRecord struct {
Count int64
}
func assembleCountKey(entityName string, additional ...string) string {
func AssembleCountKey(entityName string, additional ...string) string {
var keys = make([]string, 0)
keys = append(keys, strings.ToUpper(entityName))
keys = append(keys, additional...)
@ -24,7 +24,7 @@ func assembleCountKey(entityName string, additional ...string) string {
// 向缓存中缓存模型名称明确的包含指定条件的实体记录数量
func CacheCount(relationNames []string, entityName string, count int64, conditions ...string) error {
countKey := assembleCountKey(entityName, conditions...)
countKey := AssembleCountKey(entityName, conditions...)
cacheInstance := &_CountRecord{Count: count}
err := Cache(countKey, cacheInstance, 5*time.Minute)
for _, relationName := range relationNames {
@ -34,8 +34,8 @@ func CacheCount(relationNames []string, entityName string, count int64, conditio
}
// 从缓存中获取模型名称明确的,包含指定条件的实体记录数量
func RetreiveCount(entityName string, condtions ...string) (int64, error) {
countKey := assembleCountKey(entityName, condtions...)
func RetrieveCount(entityName string, condtions ...string) (int64, error) {
countKey := AssembleCountKey(entityName, condtions...)
exist, err := Exists(countKey)
if err != nil {
return -1, err
@ -43,7 +43,7 @@ func RetreiveCount(entityName string, condtions ...string) (int64, error) {
if !exist {
return -1, nil
}
instance, err := Retreive[_CountRecord](countKey)
instance, err := Retrieve[_CountRecord](countKey)
if instance != nil && err == nil {
return instance.Count, nil
} else {

12
cache/entity.go vendored
View File

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

6
cache/exists.go vendored
View File

@ -8,7 +8,7 @@ import (
"github.com/samber/lo"
)
func assembleExistsKey(entityName string, additional ...string) string {
func AssembleExistsKey(entityName string, additional ...string) string {
var keys = make([]string, 0)
keys = append(keys, strings.ToUpper(entityName))
keys = append(keys, additional...)
@ -22,7 +22,7 @@ func assembleExistsKey(entityName string, additional ...string) string {
// 缓存模型名称明确的、包含指定ID以及一些附加条件的记录
func CacheExists(relationNames []string, entityName string, conditions ...string) error {
existskey := assembleExistsKey(entityName, conditions...)
existskey := AssembleExistsKey(entityName, conditions...)
err := Cache(existskey, lo.ToPtr(true), 5*time.Minute)
for _, relationName := range relationNames {
CacheRelation(relationName, STORE_TYPE_KEY, existskey)
@ -32,7 +32,7 @@ func CacheExists(relationNames []string, entityName string, conditions ...string
// 从缓存中获取模型名称明确、包含指定ID以及一些附加条件的实体是否存在的标记函数在返回false时不保证数据库中相关记录也不存在
func CheckExists(entityName string, condtions ...string) (bool, error) {
existsKey := assembleExistsKey(entityName, condtions...)
existsKey := AssembleExistsKey(entityName, condtions...)
return Exists(existsKey)
}

12
cache/relation.go vendored
View File

@ -15,13 +15,13 @@ const (
STORE_TYPE_HASH = "HASH"
)
func assembleRelationKey(relationName string) string {
func AssembleRelationKey(relationName string) string {
var keys = make([]string, 0)
keys = append(keys, strings.ToUpper(relationName))
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)
identity = append(identity, storeType, key)
identity = append(identity, field...)
@ -30,8 +30,8 @@ func assembleRelationIdentity(storeType, key string, field ...string) string {
// 向缓存中保存与指定关联名称相关联的键的名称以及键的类型和子字段的组成。
func CacheRelation(relationName, storeType, key string, field ...string) error {
relationKey := assembleRelationKey(relationName)
relationIdentity := assembleRelationIdentity(storeType, key, field...)
relationKey := AssembleRelationKey(relationName)
relationIdentity := AssembleRelationIdentity(storeType, key, field...)
cmd := global.Rd.B().Sadd().Key(relationKey).Member(relationIdentity).Build()
result := global.Rd.Do(global.Ctx, cmd)
return result.Error()
@ -39,7 +39,7 @@ func CacheRelation(relationName, storeType, key string, field ...string) error {
// 从缓存中清理指定的关联键
func AbolishRelation(relationName string) error {
relationKey := assembleRelationKey(relationName)
relationKey := AssembleRelationKey(relationName)
cmd := global.Rd.B().Smembers().Key(relationKey).Build()
relationItems, err := global.Rd.Do(global.Ctx, cmd).AsStrSlice()
if err != nil {
@ -74,7 +74,7 @@ func AbolishRelation(relationName string) error {
func ClearOrphanRelationItems() error {
var (
err error
cursor int64
cursor uint64
keys = make([]string, 0)
sKeys []string
)

62
cache/search.go vendored
View File

@ -1,12 +1,17 @@
package cache
import (
"electricity_bill_calc/logger"
"fmt"
"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)
keys = append(keys, strings.ToUpper(entityName))
keys = append(keys, additional...)
@ -20,7 +25,7 @@ func assembleSearchKey(entityName string, additional ...string) string {
// 缓存模型名称明确的使用或者包含非ID检索条件的实体内容。
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, 5*time.Minute)
for _, relationName := range relationNames {
CacheRelation(relationName, STORE_TYPE_KEY, searchKey)
@ -29,9 +34,9 @@ func CacheSearch[T any](instance T, relationNames []string, entityName string, c
}
// 从缓存中取得模型名称明确的使用或者包含非ID检索条件的实体内容。
func RetreiveSearch[T any](entityName string, conditions ...string) (*T, error) {
searchKey := assembleSearchKey(entityName, conditions...)
instance, err := Retreive[T](searchKey)
func RetrieveSearch[T any](entityName string, conditions ...string) (*T, error) {
searchKey := AssembleSearchKey(entityName, conditions...)
instance, err := Retrieve[T](searchKey)
return instance, err
}
@ -40,3 +45,50 @@ func AbolishSearch(entityName string) error {
pattern := fmt.Sprintf("%s:%s:*", TAG_SEARCH, strings.ToUpper(entityName))
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)
}
func RetreiveSession(token string) (*model.Session, error) {
func RetrieveSession(token string) (*model.Session, error) {
key := SessionKey(token)
return Retreive[model.Session](key)
return Retrieve[model.Session](key)
}
func HasSession(token string) (bool, error) {

View File

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

View File

@ -3,8 +3,12 @@ package controller
import (
"electricity_bill_calc/exceptions"
"electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"net/http"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
func _retreiveSession(c *fiber.Ctx) (*model.Session, error) {
@ -18,3 +22,26 @@ func _retreiveSession(c *fiber.Ctx) (*model.Session, error) {
}
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,93 +1,97 @@
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/types"
"net/http"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
func InitializeChargesController(app *fiber.App) {
app.Get("/charges", security.OPSAuthorize, listAllCharges)
app.Post("/charge", security.OPSAuthorize, recordNewCharge)
app.Put("/charge/:uid/:seq", security.OPSAuthorize, modifyChargeState)
var chargeLog = logger.Named("Handler", "Charge")
func InitializeChargeHandlers(router *fiber.App) {
router.Get("/charge", searchCharges)
router.Post("/charge", createNewUserChargeRecord)
router.Put("/charge/:uid/:seq", modifyUserChargeState)
}
func listAllCharges(c *fiber.Ctx) error {
// 检索用户的充值记录列表
func searchCharges(c *fiber.Ctx) error {
chargeLog.Info("检索用户的充值记录列表。")
result := response.NewResult(c)
requestPage, err := strconv.Atoi(c.Query("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 {
return result.NotAccept("查询参数[page]格式不正确。")
chargeLog.Error("检索用户的充值记录列表失败。", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
requestKeyword := c.Query("keyword", "")
requestBeginDate := c.Query("begin", "")
requestEndDate := c.Query("end", "")
charges, total, err := service.ChargeService.ListPagedChargeRecord(requestKeyword, requestBeginDate, requestEndDate, requestPage)
if err != nil {
return result.NotFound(err.Error())
}
return result.Json(
http.StatusOK, "已获取到符合条件的计费记录。",
response.NewPagedResponse(requestPage, total).ToMap(),
return result.Success(
"已经获取到符合条件的计费记录。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"records": charges},
)
}
type _NewChargeFormData struct {
UserId string `json:"userId" form:"userId"`
Fee decimal.NullDecimal `json:"fee" form:"fee"`
Discount decimal.NullDecimal `json:"discount" form:"discount"`
Amount decimal.NullDecimal `json:"amount" form:"amount"`
ChargeTo model.Date `json:"chargeTo" form:"chargeTo"`
}
func recordNewCharge(c *fiber.Ctx) error {
// 创建一条新的用户充值记录
func createNewUserChargeRecord(c *fiber.Ctx) error {
chargeLog.Info("创建一条新的用户充值记录。")
result := response.NewResult(c)
formData := new(_NewChargeFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
createionForm := new(model.ChargeRecordCreationForm)
if err := c.BodyParser(createionForm); err != nil {
chargeLog.Error("无法解析创建充值记录的请求数据。", zap.Error(err))
return result.Error(http.StatusBadRequest, err.Error())
}
currentTime := time.Now()
newRecord := &model.UserCharge{
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 {
chargeLog.Error("创建用户充值记录失败。", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("指定用户的服务已延期。")
if !ok {
chargeLog.Error("创建用户充值记录失败。")
return result.NotAccept("创建用户充值记录失败。")
} else {
return result.Success("创建用户充值记录成功, 指定用户的服务已延期。")
}
}
type _StateChangeFormData struct {
Cancelled bool `json:"cancelled"`
}
func modifyChargeState(c *fiber.Ctx) error {
// 改变用户充值记录的状态
func modifyUserChargeState(c *fiber.Ctx) error {
chargeLog.Info("改变用户充值记录的状态。")
result := response.NewResult(c)
formData := new(_StateChangeFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
requestUserID := c.Params("uid")
requestChargeSeq, err := strconv.Atoi(c.Params("seq", "-1"))
if err != nil || requestChargeSeq == -1 {
return result.Error(http.StatusNotAcceptable, "参数[记录流水号]解析错误。")
}
err = service.ChargeService.CancelCharge(int64(requestChargeSeq), requestUserID)
uid := c.Params("uid")
seq, err := c.ParamsInt("seq")
if err != nil {
chargeLog.Error("无法解析请求参数。", zap.Error(err))
return result.Error(http.StatusBadRequest, err.Error())
}
ok, err := service.ChargeService.CancelUserCharge(uid, int64(seq))
if err != nil {
chargeLog.Error("取消用户充值记录失败。", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定用户服务延期记录状态已经更新。")
if !ok {
chargeLog.Error("取消用户充值记录失败。")
return result.NotAccept("取消用户充值记录失败。")
} else {
return result.Success("取消用户充值记录成功。")
}
}

View File

@ -1,237 +0,0 @@
package controller
import (
"database/sql"
"electricity_bill_calc/excel"
"electricity_bill_calc/global"
"electricity_bill_calc/model"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"fmt"
"net/http"
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
"github.com/shopspring/decimal"
)
func InitializeEndUserController(router *fiber.App) {
router.Get("/report/:rid/submeter", security.EnterpriseAuthorize, fetchEndUserInReport)
router.Get("/report/:rid/meter/template", downloadEndUserRegisterTemplate)
router.Post("/report/:rid/meter/batch", security.EnterpriseAuthorize, uploadEndUserRegisterTemplate)
router.Put("/report/:rid/submeter/:pid/:mid", security.EnterpriseAuthorize, modifyEndUserRegisterRecord)
router.Get("/end/user/adjusts", security.MustAuthenticated, statEndUserInPeriod)
}
func fetchEndUserInReport(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
keyword := c.Query("keyword")
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return result.NotAccept("查询参数[page]格式不正确。")
}
endUsers, totalItem, err := service.EndUserService.SearchEndUserRecord(requestReportId, keyword, requestPage)
if err != nil {
return result.NotFound(err.Error())
}
return result.Json(
http.StatusOK,
"已获取到符合条件的终端用户集合",
response.NewPagedResponse(requestPage, totalItem).ToMap(),
fiber.Map{"meters": endUsers},
)
}
func downloadEndUserRegisterTemplate(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
users, err := service.EndUserService.AllEndUserRecord(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
reportIndex, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
park, err := service.ParkService.FetchParkDetail(reportIndex.ParkId)
if err != nil {
return result.NotFound(err.Error())
}
meterType, err := service.ReportService.RetreiveParkEndUserMeterType(requestReportId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if meterType == -1 {
return result.NotFound("未能确定用户表计类型。")
}
c.Status(http.StatusOK)
c.Set("Content-Type", "application/octet-stream")
c.Set("Content-Transfer-Encoding", "binary")
c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=抄表记录-%s-%s.xlsx", park.Name, reportIndex.Period.Format("2006-01")))
gen := lo.Ternary[excel.ExcelTemplateGenerator](
meterType == 0,
excel.NewMeterNonPVExcelTemplateGenerator(),
excel.NewMeterPVExcelTemplateGenerator(),
)
defer gen.Close()
gen.WriteMeterData(users)
gen.WriteTo(c.Response().BodyWriter())
return nil
}
func uploadEndUserRegisterTemplate(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
meterType, err := service.ReportService.RetreiveParkEndUserMeterType(requestReportId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if meterType == -1 {
return result.NotFound("未能确定用户表计类型。")
}
uploadedFile, err := c.FormFile("data")
if err != nil {
return result.NotAccept("没有接收到上传的档案文件。")
}
archiveFile, err := uploadedFile.Open()
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if meterType == 0 {
errs := service.EndUserService.BatchImportNonPVRegister(requestReportId, archiveFile)
if errs.Len() > 0 {
return result.Json(http.StatusInternalServerError, "上传抄表文件存在解析错误", fiber.Map{"errors": errs.Errs})
}
} else {
errs := service.EndUserService.BatchImportPVRegister(requestReportId, archiveFile)
if errs.Len() > 0 {
return result.Json(http.StatusInternalServerError, "上传抄表文件存在解析错误", fiber.Map{"errors": errs.Errs})
}
}
return result.Json(http.StatusOK, "已经成功完成抄表记录的导入。", fiber.Map{"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 *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
meterType, err := service.ReportService.RetreiveParkEndUserMeterType(requestReportId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if meterType == -1 {
return result.NotFound("未能确定用户表计类型。")
}
requestParkId := c.Params("pid")
requestMeterId := c.Params("mid")
formData := new(ModifyEndUserRegisterFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
meter, err := service.EndUserService.FetchSpecificEndUserRecord(requestReportId, requestParkId, requestMeterId)
if err != nil {
return result.NotFound(err.Error())
}
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 {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !valid {
return result.NotAccept("抄表数据合法性验证失败。")
}
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
err = service.EndUserService.UpdateEndUserRegisterRecord(&tx, &ctx, *meter)
if err != nil {
tx.Rollback()
return result.Error(http.StatusInternalServerError, err.Error())
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("指定终端用户抄表记录已经更新。")
}
func statEndUserInPeriod(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
requestUser := lo.
If(session.Type == model.USER_TYPE_ENT, session.Uid).
Else(c.Query("user"))
requestPark := c.Query("park")
if len(requestPark) > 0 && session.Type == model.USER_TYPE_ENT {
if ensure, err := ensureParkBelongs(c, &result, requestPark); !ensure {
return err
}
}
startDate := c.Query("start")
endDate := c.Query("end")
stat, err := service.EndUserService.StatEndUserRecordInPeriod(requestUser, requestPark, startDate, endDate)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success(
"已经完成终端用户的费用统计",
fiber.Map{"details": stat},
)
}

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},
)
}

View File

@ -1,176 +1,132 @@
package controller
import (
"electricity_bill_calc/exceptions"
"electricity_bill_calc/logger"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"net/http"
"errors"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"net/http"
)
func InitializeGodModeController(router *fiber.App) {
gmR := router.Group("/gm")
{
gmR.Delete("/report/:rid/summary", security.SingularityAuthorize, gmResetReportSummary)
gmR.Delete("/report/:rid/maintenance", security.SingularityAuthorize, gmResetReportMaintenance)
gmR.Delete("/report/:rid/meters", security.SingularityAuthorize, gmResetReportEndUserRecord)
gmR.Post("/report/:rid/meters", security.SingularityAuthorize, gmResynchronizeReportEndUserRecord)
gmR.Delete("/report/:rid", security.SingularityAuthorize, gmResetReport)
gmR.Delete("/report/:rid/force", security.SingularityAuthorize, gmDeleteReport)
gmR.Delete("/park/:pid/maintenance/:mid", security.SingularityAuthorize, gmDeleteSpecificMaintenance)
gmR.Delete("/park/:pid/maintenance", security.SingularityAuthorize, gmDeleteAllMaintenance)
gmR.Delete("/park/:pid/meters", security.SingularityAuthorize, gmDeleteAllMeters)
gmR.Delete("/park/:pid/force", security.SingularityAuthorize, gmDeletePark)
gmR.Delete("/enterprise/:uid/force", security.SingularityAuthorize, gmDeleteUser)
}
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 gmResetReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ClearReportSummary(requestReportId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
//用于将参数转化为切片
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)
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的园区总览部分。")
}
return result.Success("指定报表的园区总览已经重置。")
return result
}
func gmResetReportMaintenance(c *fiber.Ctx) error {
func deleteTenement(c *fiber.Ctx) error {
park := c.Query("park", "")
tenements := getQueryValues(c, "tenements")
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ClearReportMaintenances(requestReportId)
GmLog.Info("[天神模式]删除指定园区中的商户", zap.String("park", park), zap.Strings("tenements", tenements))
err := service.GMService.DeleteTenements(park, tenements)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
GmLog.Error("[天神模式]删除指定园区中的商户失败", zap.Error(err))
return result.Error(500, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的配电维护费部分。")
}
return result.Success("指定报表的配电维护费已经重置。")
return result.Success("指定商户已经删除。")
}
func gmResynchronizeReportEndUserRecord(c *fiber.Ctx) error {
func deletePark(c *fiber.Ctx) error {
parks := getQueryValues(c, "parks")
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ResynchronizeEndUser(requestReportId)
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 {
return result.Error(http.StatusInternalServerError, err.Error())
GmLog.Error("[天神模式]删除指定园区失败", zap.Error(err))
return result.Error(500, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的抄表记录基本档案。")
}
return result.Success("指定报表的抄表记录基本档案已经重新同步。")
return result.Success("指定园区已经删除。")
}
func gmResetReportEndUserRecord(c *fiber.Ctx) error {
func deleteReports(c *fiber.Ctx) error {
pid := c.Query("park")
reports := getQueryValues(c, "reports")
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ResetEndUserRegisterRecords(requestReportId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的抄表记录部分。")
}
return result.Success("指定报表的抄表记录已经重置。")
}
GmLog.Info("[天神模式]删除符合条件的报表。", zap.Strings("reports", reports))
func gmResetReport(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ResetReport(requestReportId)
err := service.GMService.DeleteReports(pid, reports)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表。")
}
return result.Success("指定报表已经重置。")
}
func gmDeleteReport(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.DeleteReport(requestReportId)
if err != nil {
if ipErr, ok := err.(exceptions.ImproperOperateError); ok {
return result.NotAccept(ipErr.Message)
} else {
return result.Error(http.StatusInternalServerError, err.Error())
}
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除指定报表。")
GmLog.Error("[天神模式]删除指定园区中的报表失败。", zap.Error(err))
return result.Error(500, err.Error())
}
return result.Success("指定报表已经删除。")
}
func gmDeleteSpecificMaintenance(c *fiber.Ctx) error {
func deleteEnterprise(c *fiber.Ctx) error {
uid := c.Query("uid")
result := response.NewResult(c)
requestParkId := c.Params("pid")
requestMaintenanceId := c.Params("mid")
done, err := service.GodModeService.RemoveSpecificMaintenance(requestParkId, requestMaintenanceId)
GmLog.Info("[天神模式]删除指定企业用户", zap.String("uid", uid))
err := service.GMService.DeleteEnterprises(uid)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
GmLog.Error("[天神模式]删除指定企业用户失败", zap.Error(err))
return result.Error(500, "删除指定企业用户失败。")
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除指定的维护费用记录。")
}
return result.Success("指定维护费用记录已经删除。")
return result.Success("指定企业用户已经删除。")
}
func gmDeleteAllMaintenance(c *fiber.Ctx) error {
func deleteTenementMeterRelations(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
done, err := service.GodModeService.RemoveAllMaintenance(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
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())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除全部维护费用记录。")
}
return result.Success("全部维护费用记录已经删除。")
return result.Success("删除成功")
}
func gmDeleteAllMeters(c *fiber.Ctx) error {
func deleteMeterPoolingRelations(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
done, err := service.GodModeService.RemoveAllMeters(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
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, "删除指定园区中的表计公摊关系失败。")
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除全部终端表计档案记录。")
}
return result.Success("全部终端表计档案记录已经删除。")
return result.Success("指定表计公摊关系已经删除。")
}
func gmDeletePark(c *fiber.Ctx) error {
func deleteMeters(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
done, err := service.GodModeService.RemovePark(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
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, "删除指定园区中的表计失败。")
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除指定的园区。")
}
return result.Success("指定的园区已经删除。")
}
func gmDeleteUser(c *fiber.Ctx) error {
result := response.NewResult(c)
requestUserId := c.Params("uid")
done, err := service.GodModeService.DeleteUser(requestUserId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除指定的用户。")
}
return result.Success("指定的用户及其关联信息已经删除。")
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,202 +0,0 @@
package controller
import (
"electricity_bill_calc/model"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"net/http"
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
"github.com/samber/lo"
"github.com/shopspring/decimal"
)
func InitializeMaintenanceFeeController(router *fiber.App) {
router.Get("/maintenance/fee", security.MustAuthenticated, 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)
router.Get("/additional/charges", security.MustAuthenticated, statAdditionalCharges)
}
func ensureMaintenanceFeeBelongs(c *fiber.Ctx, result *response.Result, requestMaintenanceFeeId string) (bool, error) {
userSession, err := _retreiveSession(c)
if err != nil {
return false, result.Unauthorized(err.Error())
}
sure, err := service.MaintenanceFeeService.EnsureFeeBelongs(userSession.Uid, requestMaintenanceFeeId)
if err != nil {
return false, result.Error(http.StatusInternalServerError, err.Error())
}
if !sure {
return false, result.Unauthorized("所操作维护费记录不属于当前用户。")
}
return true, nil
}
func listMaintenanceFees(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
requestPark := c.Query("park")
requestPeriod := c.Query("period")
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return result.Error(http.StatusInternalServerError, "不能解析给定的参数[page]。")
}
if len(requestPark) > 0 {
if userSession.Type == model.USER_TYPE_ENT {
if ensure, err := ensureParkBelongs(c, &result, requestPark); !ensure {
return err
}
}
fees, total, err := service.MaintenanceFeeService.ListMaintenanceFees([]string{requestPark}, requestPeriod, requestPage)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(
http.StatusOK,
"已获取指定园区下的维护费记录",
response.NewPagedResponse(requestPage, total).ToMap(),
fiber.Map{"fees": fees},
)
} else {
parkIds, err := service.ParkService.AllParkIds(userSession.Uid)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
fees, total, err := service.MaintenanceFeeService.ListMaintenanceFees(parkIds, requestPeriod, requestPage)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(
http.StatusOK,
"已获取指定用户下的所有维护费记录。",
response.NewPagedResponse(requestPage, total).ToMap(),
fiber.Map{"fees": fees},
)
}
}
type _FeeCreationFormData struct {
ParkId string `json:"parkId" form:"parkId"`
Name string `json:"name" form:"name"`
Period string `json:"period" form:"period"`
Fee decimal.Decimal `json:"fee" form:"fee"`
Memo *string `json:"memo" form:"memo"`
}
func createMaintenanceFeeRecord(c *fiber.Ctx) error {
result := response.NewResult(c)
formData := new(_FeeCreationFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
if ensure, err := ensureParkBelongs(c, &result, formData.ParkId); !ensure {
return err
}
newMaintenanceFee := &model.MaintenanceFee{}
copier.Copy(newMaintenanceFee, formData)
err := service.MaintenanceFeeService.CreateMaintenanceFeeRecord(*newMaintenanceFee)
if err != nil {
return 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 *fiber.Ctx) error {
result := response.NewResult(c)
requestFee := c.Params("mid")
formData := new(_FeeModificationFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
if ensure, err := ensureMaintenanceFeeBelongs(c, &result, requestFee); !ensure {
return err
}
newFeeState := new(model.MaintenanceFee)
copier.Copy(newFeeState, formData)
newFeeState.Id = requestFee
err := service.MaintenanceFeeService.ModifyMaintenanceFee(*newFeeState)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定维护费条目已更新。")
}
type _FeeStateFormData struct {
Enabled bool `json:"enabled" form:"enabled"`
}
func changeMaintenanceFeeState(c *fiber.Ctx) error {
result := response.NewResult(c)
requestFee := c.Params("mid")
formData := new(_FeeStateFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
if ensure, err := ensureMaintenanceFeeBelongs(c, &result, requestFee); !ensure {
return err
}
err := service.MaintenanceFeeService.ChangeMaintenanceFeeState(requestFee, formData.Enabled)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定维护费条目状态已更新。")
}
func deleteMaintenanceFee(c *fiber.Ctx) error {
result := response.NewResult(c)
requestFee := c.Params("mid")
if ensure, err := ensureMaintenanceFeeBelongs(c, &result, requestFee); !ensure {
return err
}
err := service.MaintenanceFeeService.DeleteMaintenanceFee(requestFee)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Deleted("指定维护费条目已删除。")
}
func statAdditionalCharges(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
requestUser := lo.
If(session.Type == model.USER_TYPE_ENT, session.Uid).
Else(c.Query("user"))
requestPark := c.Query("park")
if len(requestPark) > 0 && session.Type == model.USER_TYPE_ENT {
if ensure, err := ensureParkBelongs(c, &result, requestPark); !ensure {
return err
}
}
period := c.Query("period", "")
keyword := c.Query("keyword", "")
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return result.Error(http.StatusInternalServerError, "不能解析给定的参数[page]。")
}
fees, total, err := service.MaintenanceFeeService.QueryAdditionalCharges(requestUser, requestPark, period, keyword, requestPage)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success(
"已经成功获取到物业附加费的统计记录。",
response.NewPagedResponse(requestPage, total).ToMap(),
fiber.Map{"charges": fees},
)
}

502
controller/meter.go Normal file
View File

@ -0,0 +1,502 @@
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"
"net/http"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
"github.com/samber/lo"
"go.uber.org/zap"
)
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)
}
// 查询指定园区下的表计信息
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)
meters, total, err := repository.MeterRepository.MetersIn(parkId, uint(page), &keyword)
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())
}
return nil
}
// 列出指定公摊表计下的所有关联表计
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")
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)
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})
}

View File

@ -1,188 +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"
"net/http"
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
"github.com/samber/lo"
"github.com/shopspring/decimal"
)
func InitializeMeter04kVController(router *fiber.App) {
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.Post("/park/:pid/meter/batch", security.EnterpriseAuthorize, batchImport04kVMeterArchive)
router.Put("/park/:pid/meter/:code", security.EnterpriseAuthorize, modifySingle04kVMeter)
}
func download04kvMeterArchiveTemplate(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
parkDetail, err := service.ParkService.FetchParkDetail(requestParkId)
if err != nil {
return result.NotFound("未找到指定的园区信息。")
}
return c.Download("./assets/meter_04kv_template.xlsx", fmt.Sprintf("%s-户表档案.xlsx", parkDetail.Name))
}
func ListPaged04kVMeter(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
}
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return result.NotAccept("查询参数[page]格式不正确。")
}
requestKeyword := c.Query("keyword", "")
meters, totalItem, err := service.Meter04kVService.ListMeterDetail(requestParkId, requestKeyword, requestPage)
if err != nil {
return result.NotFound(err.Error())
}
return result.Json(
http.StatusOK,
"已获取到符合条件的0.4kV表计集合。",
response.NewPagedResponse(requestPage, totalItem).ToMap(),
fiber.Map{"meters": meters},
)
}
func fetch04kVMeterDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
}
requestMeterCode := c.Params("code")
meter, err := service.Meter04kVService.Get04kVMeterDetail(requestParkId, requestMeterCode)
if err != nil {
return result.NotFound(err.Error())
}
if meter == nil {
return result.Json(http.StatusNotFound, "指定的表计信息未能找到。", fiber.Map{"meter": nil})
}
return result.Json(http.StatusOK, "指定的表计信息已找到。", fiber.Map{"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"`
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"`
Enabled bool `json:"enabled" form:"enabled"`
}
func createSingle04kVMeter(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
}
formData := new(_MeterCreationFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
newMeter := new(model.Meter04KV)
copier.Copy(newMeter, formData)
newMeter.ParkId = requestParkId
err := service.Meter04kVService.CreateSingleMeter(*newMeter)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("新0.4kV表计已经添加完成。")
}
func modifySingle04kVMeter(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
}
requestMeterCode := c.Params("code")
meterDetail, err := service.Meter04kVService.Get04kVMeterDetail(requestParkId, requestMeterCode)
if err != nil {
return result.NotFound(err.Error())
}
if meterDetail == nil {
return result.NotFound("指定表计的信息为找到,不能修改。")
}
formData := new(_MeterModificationFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
copier.Copy(meterDetail, formData)
err = service.Meter04kVService.UpdateSingleMeter(meterDetail)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定0.4kV表计信息已经更新。")
}
func batchImport04kVMeterArchive(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
}
uploadedFile, err := c.FormFile("data")
if err != nil {
return result.NotAccept("没有接收到上传的档案文件。")
}
archiveFile, err := uploadedFile.Open()
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
analyzer, err := excel.NewMeterArchiveExcelAnalyzer(archiveFile)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
records, errs := analyzer.Analysis(*new(model.Meter04KV))
if len(errs) > 0 {
return result.Json(http.StatusNotAcceptable, "上传的表计档案文件存在错误。", fiber.Map{"errors": errs})
}
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 {
return result.Json(http.StatusNotAcceptable, "上传的表计档案文件存在错误。", fiber.Map{"errors": errs})
}
err = service.Meter04kVService.BatchCreateMeter(mergedMeters)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(
http.StatusOK,
"上传的表计档案已经全部导入。",
fiber.Map{"errors": make([]excel.ExcelAnalysisError, 0)},
)
}

View File

@ -1,185 +1,331 @@
package controller
import (
"electricity_bill_calc/model"
"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/vo"
"net/http"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/jinzhu/copier"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
func InitializeParkController(router *fiber.App) {
router.Get("/parks", security.EnterpriseAuthorize, listAllParksUnderSessionUser)
router.Get("/parks/:uid", security.MustAuthenticated, listAllParksUnderSpecificUser)
router.Post("/park", security.EnterpriseAuthorize, createNewPark)
router.Put("/park/:pid", security.EnterpriseAuthorize, modifyPark)
var parkLog = logger.Named("Handler", "Park")
func InitializeParkHandlers(router *fiber.App) {
router.Get("/park", security.EnterpriseAuthorize, listParksBelongsToCurrentUser)
router.Post("/park", security.EnterpriseAuthorize, createPark)
router.Get("/park/belongs/:uid", security.OPSAuthorize, listParksBelongsTo)
router.Get("/park/:pid", security.EnterpriseAuthorize, fetchParkDetail)
router.Put("/park/:pid/enabled", security.EnterpriseAuthorize, changeParkEnableState)
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 *fiber.Ctx, result *response.Result, requestParkId string) (bool, error) {
userSession, err := _retreiveSession(c)
if err != nil {
return false, result.Unauthorized(err.Error())
}
sure, err := service.ParkService.EnsurePark(userSession.Uid, requestParkId)
if err != nil {
return false, result.Error(http.StatusInternalServerError, err.Error())
}
if !sure {
return false, result.Unauthorized("不能访问不属于自己的园区。")
}
return true, nil
}
func listAllParksUnderSessionUser(c *fiber.Ctx) error {
// 列出隶属于当前用户的全部园区
func listParksBelongsToCurrentUser(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("列出当前用的全部园区,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
keyword := c.Query("keyword")
parks, err := service.ParkService.ListAllParkBelongsTo(userSession.Uid, keyword)
parkLog.Info("列出当前用户下的全部园区", zap.String("user id", session.Uid))
parks, err := repository.ParkRepository.ListAllParks(session.Uid)
if err != nil {
parkLog.Error("无法获取园区列表。", zap.String("user id", session.Uid))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "已获取到指定用户下的园区。", fiber.Map{"parks": parks})
return result.Success("已获取到指定用户的下的园区", fiber.Map{"parks": parks})
}
func listAllParksUnderSpecificUser(c *fiber.Ctx) error {
// 列出隶属于指定用户的全部园区
func listParksBelongsTo(c *fiber.Ctx) error {
result := response.NewResult(c)
requestUserId := c.Params("uid")
keyword := c.Query("keyword")
parks, err := service.ParkService.ListAllParkBelongsTo(requestUserId, keyword)
userId := c.Params("uid")
parkLog.Info("列出指定用户下的全部园区", zap.String("user id", userId))
parks, err := repository.ParkRepository.ListAllParks(userId)
if err != nil {
parkLog.Error("无法获取园区列表。", zap.String("user id", userId))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "已获取到指定用户下的园区。", fiber.Map{"parks": parks})
}
type _ParkInfoFormData 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" from:"phone"`
Area decimal.NullDecimal `json:"area" from:"area"`
Capacity decimal.NullDecimal `json:"capacity" from:"capacity"`
TenementQuantity decimal.NullDecimal `json:"tenement" from:"tenement"`
Category int8 `json:"category" form:"category"`
SubmeterType int8 `json:"submeter" form:"submeter"`
}
func createNewPark(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
formData := new(_ParkInfoFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
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 {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("新园区完成创建。")
}
func modifyPark(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
requestParkId := c.Params("pid")
formData := new(_ParkInfoFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
park, err := service.ParkService.FetchParkDetail(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if userSession.Uid != park.UserId {
return result.Unauthorized("不能修改不属于自己的园区。")
}
copier.Copy(park, formData)
nameAbbr := tools.PinyinAbbr(formData.Name)
park.Abbr = &nameAbbr
err = service.ParkService.UpdateParkInfo(park)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定园区资料已更新。")
return result.Success("已获取到指定用户的下的园区", fiber.Map{"parks": parks})
}
// 获取指定园区的详细信息
func fetchParkDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
}
park, err := service.ParkService.FetchParkDetail(requestParkId)
parkId := c.Params("pid")
parkLog.Info("获取指定园区的详细信息", zap.String("park id", parkId))
park, err := repository.ParkRepository.RetrieveParkDetail(parkId)
if err != nil {
parkLog.Error("无法获取园区信息。", zap.String("park id", parkId))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "已经获取到指定园区的信息。", fiber.Map{"park": park})
return result.Success("已获取到指定园区的详细信息", fiber.Map{"park": park})
}
type _ParkStateFormData struct {
Enabled bool `json:"enabled" form:"enabled"`
}
func changeParkEnableState(c *fiber.Ctx) error {
// 创建一个新的园区
func createPark(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("创建一个新的园区,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
requestParkId := c.Params("pid")
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
parkLog.Info("创建一个新的园区", zap.String("user id", session.Uid))
creationForm := new(vo.ParkInformationForm)
if err := c.BodyParser(creationForm); err != nil {
parkLog.Error("无法解析园区表单数据。", zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
formData := new(_ParkStateFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
err = service.ParkService.ChangeParkState(userSession.Uid, requestParkId, formData.Enabled)
park, err := creationForm.TryIntoPark()
if err != nil {
parkLog.Error("无法将园区表单数据转换为园区对象。", zap.String("user id", session.Uid), zap.Error(err))
return result.NotAccept(err.Error())
}
ok, err := repository.ParkRepository.CreatePark(session.Uid, park)
switch {
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())
}
return result.Updated("指定园区的可用性状态已成功更新。")
return result.Created("已创建一个新的园区")
}
// 修改指定园区的信息
func modifySpecificPark(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
}
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("已更新指定园区的详细信息")
}
// 修改指定园区的可用性
func modifyParkEnabling(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
}
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.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 *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
parkId := c.Params("pid")
session, err := _retreiveSession(c)
if err != nil {
parkLog.Error("删除指定的园区,无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
requestParkId := c.Params("pid")
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass {
return err
}
err = service.ParkService.DeletePark(userSession.Uid, requestParkId)
if err != nil {
ok, err := repository.ParkRepository.DeletePark(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("指定园区已成功删除。")
return result.Deleted("已删除指定的园区")
}
// 列出指定园区中已经登记的建筑
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,22 +1,22 @@
package controller
import (
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"electricity_bill_calc/service"
"net/http"
"github.com/gofiber/fiber/v2"
)
func InitializeRegionController(router *fiber.App) {
router.Get("/region/:rid", fetchRegions)
router.Get("/regions/:rid", fetchAllLeveledRegions)
func InitializeRegionHandlers(router *fiber.App) {
router.Get("/region/:rid", getSubRegions)
router.Get("/regions/:rid", getParentRegions)
}
func fetchRegions(c *fiber.Ctx) error {
func getSubRegions(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParentId := c.Params("rid")
regions, err := service.RegionService.FetchSubRegions(requestParentId)
regions, err := repository.RegionRepository.FindSubRegions(requestParentId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
@ -26,10 +26,10 @@ func fetchRegions(c *fiber.Ctx) error {
return result.Json(http.StatusOK, "已经获取到相关的行政区划。", fiber.Map{"regions": regions})
}
func fetchAllLeveledRegions(c *fiber.Ctx) error {
func getParentRegions(c *fiber.Ctx) error {
result := response.NewResult(c)
requestRegionCode := c.Params("rid")
regions, err := service.RegionService.FetchAllParentRegions(requestRegionCode)
regions, err := repository.RegionRepository.FindParentRegions(requestRegionCode)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}

View File

@ -1,301 +1,425 @@
package controller
import (
"electricity_bill_calc/exceptions"
"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"
"net/http"
"strconv"
"time"
"electricity_bill_calc/types"
"electricity_bill_calc/vo"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
"github.com/samber/lo"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
func InitializeReportController(router *fiber.App) {
router.Get("/reports/with/drafts", security.EnterpriseAuthorize, fetchNewestReportOfParkWithDraft)
router.Post("/park/:pid/report", security.EnterpriseAuthorize, initializeNewReport)
router.Get("/report/:rid/step/state", security.EnterpriseAuthorize, fetchReportStepStates)
router.Get("/report/:rid/summary", security.EnterpriseAuthorize, fetchReportParkSummary)
router.Put("/report/:rid/summary", security.EnterpriseAuthorize, fillReportSummary)
router.Get("/report/:rid/summary/calculate", security.EnterpriseAuthorize, testCalculateReportSummary)
router.Post("/report/:rid/summary/calculate", security.EnterpriseAuthorize, progressReportSummary)
router.Put("/report/:rid/step/meter/register", security.EnterpriseAuthorize, progressEndUserRegister)
var reportLog = logger.Named("Handler", "Report")
func InitializeReportHandlers(router *fiber.App) {
router.Get("/reports", security.MustAuthenticated, reportComprehensiveSearch)
router.Post("/report", security.EnterpriseAuthorize, initNewReportCalculateTask)
router.Get("/report/draft", security.EnterpriseAuthorize, listDraftReportIndicies)
//TODO: 2023-07-20将calcualte错误请求改为正确的calculate请求
router.Post("/report/calculate", security.EnterpriseAuthorize, testCalculateReportSummary)
router.Get("/report/calculate/status", security.EnterpriseAuthorize, listCalculateTaskStatus)
router.Get("/report/:rid", security.EnterpriseAuthorize, getReportDetail)
router.Put("/report/:rid", security.EnterpriseAuthorize, updateReportCalculateTask)
router.Post("/report/:rid/publish", security.EnterpriseAuthorize, publishReport)
router.Get("/reports", security.MustAuthenticated, searchReports)
router.Get("/report/:rid", security.MustAuthenticated, fetchReportPublicity)
router.Post("/report/:rid/calculate", security.EnterpriseAuthorize, calculateReport)
router.Put("/report/:rid/calculate", security.EnterpriseAuthorize, initiateCalculateTask)
router.Get("/report/:rid/publics", security.MustAuthenticated, listPublicMetersInReport)
router.Get("/report/:rid/summary", security.MustAuthenticated, getReportSummary)
router.Get("/report/:rid/summary/filled", security.EnterpriseAuthorize, getParkFilledSummary)
router.Get("/report/:rid/pooled", security.MustAuthenticated, listPooledMetersInReport)
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 *fiber.Ctx, result *response.Result, requestReportId string) (bool, error) {
_, 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 {
return false, result.Unauthorized(err.Error())
log.Error("无法获取当前用户的会话信息", zap.Error(err))
return false, result.Unauthorized("无法获取当前用户的会话信息。")
}
requestReport, err := service.ReportService.RetreiveReportIndex(requestReportId)
ok, err := repository.ReportRepository.IsBelongsTo(reportId, session.Uid)
if err != nil {
return false, result.NotFound(err.Error())
log.Error("无法检查核算报表的所有权", zap.Error(err))
return false, result.Error(fiber.StatusInternalServerError, "无法检查核算报表的所有权。")
}
if requestReport == nil {
return false, result.NotFound("指定报表未能找到。")
if !ok {
log.Error("核算报表不属于当前用户")
return false, result.Forbidden("核算报表不属于当前用户。")
}
return ensureParkBelongs(c, result, requestReport.ParkId)
return true, nil
}
func fetchNewestReportOfParkWithDraft(c *fiber.Ctx) error {
// 获取当前登录用户下所有园区的尚未发布的核算报表索引
func listDraftReportIndicies(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
session, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return result.Unauthorized("无法获取当前用户的会话信息。")
}
parks, err := service.ReportService.FetchParksWithNewestReport(userSession.Uid)
reportLog.Info("检索指定用户下的未发布核算报表索引", zap.String("User", session.Uid))
indicies, err := service.ReportService.ListDraftReportIndicies(session.Uid)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
reportLog.Error("无法获取当前用户的核算报表索引", zap.Error(err))
return result.NotFound("当前用户下未找到核算报表索引。")
}
return result.Json(http.StatusOK, "已获取到指定用户下所有园区的最新报表记录。", fiber.Map{"parks": parks})
return result.Success(
"已经获取到指定用户的报表索引。",
fiber.Map{"reports": indicies},
)
}
func initializeNewReport(c *fiber.Ctx) error {
// 初始化一个新的核算任务
func initNewReportCalculateTask(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
userSession, err := _retreiveSession(c)
session, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return result.Unauthorized("无法获取当前用户的会话信息。")
}
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
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
}
requestPeriod := c.Query("period")
reportPeriod, err := time.Parse("2006-01", requestPeriod)
ok, err := repository.ReportRepository.CreateReport(&form)
if err != nil {
return result.NotAccept("提供的初始化期数格式不正确。")
reportLog.Error("无法创建核算报表", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法创建核算报表。")
}
valid, err := service.ReportService.IsNewPeriodValid(userSession.Uid, requestParkId, reportPeriod)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
if !ok {
reportLog.Error("未能完成核算报表的保存。")
return result.NotAccept("未能完成核算报表的保存。")
}
if !valid {
return result.NotAccept("只能初始化已发布报表下一个月份的新报表。")
}
newId, err := service.ReportService.InitializeNewReport(requestParkId, reportPeriod)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("新一期报表初始化成功。", fiber.Map{"reportId": newId})
return result.Success("已经成功创建核算报表。")
}
func fetchReportStepStates(c *fiber.Ctx) error {
// 更新指定的核算任务
func updateReportCalculateTask(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
reportId := c.Params("rid")
if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass {
return err
}
requestReport, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
return result.NotFound(err.Error())
var form vo.ReportModifyForm
if err := c.BodyParser(&form); err != nil {
reportLog.Error("无法解析更新核算报表的请求数据。", zap.Error(err))
return result.BadRequest("无法解析更新核算报表的请求数据。")
}
return result.Json(http.StatusOK, "已经获取到指定报表的填写状态。", fiber.Map{"steps": requestReport.StepState})
ok, err := repository.ReportRepository.UpdateReportSummary(reportId, &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 fetchReportParkSummary(c *fiber.Ctx) error {
// 启动指定的核算任务
func initiateCalculateTask(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
reportId := c.Params("rid")
if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass {
return err
}
summary, err := service.ReportService.RetreiveReportSummary(requestReportId)
err := service.ReportService.DispatchReportCalculate(reportId)
if err != nil {
return result.NotFound(err.Error())
reportLog.Error("无法启动核算报表计算任务", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法启动核算报表计算任务。")
}
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 {
return result.NotFound("指定报表未能找到。")
reportLog.Error("未找到核算报表的园区电量信息")
return result.NotFound("未找到核算报表的园区电量信息。")
}
return result.Json(http.StatusOK, "已经获取到指定报表中的园区概况。", fiber.Map{"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 *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
formData := new(ReportSummaryFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
originSummary, err := service.ReportService.RetreiveReportSummary(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
copier.Copy(originSummary, formData)
originSummary.ReportId = requestReportId
err = service.ReportService.UpdateReportSummary(originSummary)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定电费公示报表中的园区概况基本数据已经完成更新。")
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)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
reportLog.Info("试计算园区电量信息")
var form vo.TestCalculateForm
if err := c.BodyParser(&form); err != nil {
reportLog.Error("无法解析试计算核算报表的请求数据。", zap.Error(err))
return result.BadRequest("无法解析试计算核算报表的请求数据。")
}
summary, err := service.ReportService.RetreiveReportSummary(requestReportId)
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 {
return result.NotFound(err.Error())
reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return result.Unauthorized("无法获取当前用户的会话信息。")
}
summary.CalculatePrices()
calcResults := tools.ConvertStructToMap(summary)
return result.Json(
http.StatusOK,
"已完成园区概况的试计算。",
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{
"result": lo.PickByKeys(
calcResults,
[]string{"overallPrice", "criticalPrice", "peakPrice", "flat", "flatFee", "flatPrice", "valleyPrice", "consumptionFee"},
),
"detail": vo.NewReportDetailQueryResponse(user, park, report),
},
)
}
func progressReportSummary(c *fiber.Ctx) error {
// 获取指定核算报表的总览信息
func getReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
err := service.ReportService.CalculateSummaryAndFinishStep(requestReportId)
reportId := c.Params("rid")
report, err := repository.ReportRepository.RetrieveReportSummary(reportId)
if err != nil {
if nfErr, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound(nfErr.Error())
} else {
return result.Error(http.StatusInternalServerError, err.Error())
}
reportLog.Error("无法获取核算报表的总览信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表的总览信息。")
}
return result.Success("已经完成园区概况的计算,并可以进行到下一步骤。")
}
func progressEndUserRegister(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
report, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
err = service.ReportService.ProgressReportRegisterEndUser(*report)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("终端用户抄表编辑步骤已经完成。")
}
func publishReport(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
report, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
err = service.ReportService.PublishReport(*report)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("指定的公示报表已经发布。")
}
func searchReports(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
requestUser := lo.
If(session.Type == model.USER_TYPE_ENT, session.Uid).
Else(c.Query("user"))
requestPark := c.Query("park")
if len(requestPark) > 0 && session.Type == model.USER_TYPE_ENT {
if ensure, err := ensureParkBelongs(c, &result, requestPark); !ensure {
return err
}
}
requestPeriodString := c.Query("period")
var requestPeriod *time.Time = nil
if len(requestPeriodString) > 0 {
parsedPeriod, err := time.Parse("2006-01", requestPeriodString)
if err != nil {
return result.NotAccept("参数[period]的格式不正确。")
}
requestPeriod = lo.ToPtr(parsedPeriod)
}
requestKeyword := c.Query("keyword")
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return result.NotAccept("查询参数[page]格式不正确。")
}
requestAllReports, err := strconv.ParseBool(c.Query("all", "false"))
if err != nil {
return result.NotAccept("查询参数[all]格式不正确。")
}
records, totalItems, err := service.ReportService.SearchReport(requestUser, requestPark, requestKeyword, requestPeriod, requestPage, !requestAllReports)
if err != nil {
return result.NotFound(err.Error())
if report == nil {
reportLog.Error("未找到核算报表的总览信息")
return result.NotFound("未找到核算报表的总览信息。")
}
var summaryResponse vo.ParkSummaryResponse
copier.Copy(&summaryResponse, report)
return result.Success(
"已经取得符合条件的公示报表记录。",
response.NewPagedResponse(requestPage, totalItems).ToMap(),
fiber.Map{"reports": records},
"已经获取到核算报表的总览信息。",
fiber.Map{"summary": summaryResponse},
)
}
func fetchReportPublicity(c *fiber.Ctx) error {
// 获取指定报表中分页的公共表计的核算摘要信息
func listPublicMetersInReport(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
publicity, err := service.ReportService.AssembleReportPublicity(requestReportId)
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 {
if nfErr, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound(nfErr.Error())
} else {
return result.Error(http.StatusInternalServerError, err.Error())
}
reportLog.Error("无法获取核算报表中的公共表计信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的公共表计信息。")
}
return result.Success("已经取得指定公示报表的发布版本。", tools.ConvertStructToMap(publicity))
meterResponse := lo.Map(meters, func(meter *model.ReportDetailedPublicConsumption, _ int) *vo.ReportPublicQueryResponse {
m := &vo.ReportPublicQueryResponse{}
m.FromReportDetailPublicConsumption(meter)
return m
})
return result.Success(
"已经获取到指定核算报表中的分页公共表计的核算信息。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"public": meterResponse},
)
}
func calculateReport(c *fiber.Ctx) error {
// 获取指定报表中的分页的公摊表计的核算摘要信息
func listPooledMetersInReport(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
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
}
err := service.CalculateService.ComprehensivelyCalculateReport(requestReportId)
ok, err := repository.ReportRepository.PublishReport(reportId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
reportLog.Error("无法发布核算报表", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "发布核算报表出错。")
}
return result.Success("指定公示报表中的数据已经计算完毕。")
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_id"))
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,31 +1,36 @@
package controller
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"net/http"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"net/http"
)
var StatisticsWithdrawLog = logger.Named("Handler", "StatisticsWithdraw")
func InitializeStatisticsController(router *fiber.App) {
router.Get("/audits", security.OPSAuthorize, currentAuditAmount)
router.Get("/stat/reports", security.MustAuthenticated, statReports)
router.Get("/stat/reports", security.OPSAuthorize, statReports)
}
//获取当前系统中待审核的内容数量
func currentAuditAmount(c *fiber.Ctx) error {
StatisticsWithdrawLog.Info("开始获取当前系统中待审核的内容数量")
result := response.NewResult(c)
amount, err := service.WithdrawService.AuditWaits()
if err != nil {
StatisticsWithdrawLog.Error("获取当前系统中待审核的内容数量出错", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "已经获取到指定的统计信息。", fiber.Map{
"amounts": map[string]int64{
"withdraw": amount,
},
})
return result.Success("已经获取到指定的统计信息",
fiber.Map{"withdraw": amount})
}
func statReports(c *fiber.Ctx) error {
@ -42,28 +47,35 @@ func statReports(c *fiber.Ctx) error {
if session.Type != 0 {
enterprises, err = service.StatisticsService.EnabledEnterprises()
if err != nil {
StatisticsWithdrawLog.Error(err.Error())
return result.Error(http.StatusInternalServerError, err.Error())
}
parks, err = service.StatisticsService.EnabledParks()
if err != nil {
StatisticsWithdrawLog.Error(err.Error())
return result.Error(http.StatusInternalServerError, err.Error())
}
reports, err = service.StatisticsService.ParksNewestState()
//TODO: 2023.07.26 报表数据库结构改变,此处逻辑复杂放在最后处理
reports, err = service.StatisticsService.ParkNewestState()
if err != nil {
StatisticsWithdrawLog.Error(err.Error())
return result.Error(http.StatusInternalServerError, err.Error())
}
} else {
parks, err = service.StatisticsService.EnabledParks(session.Uid)
if err != nil {
StatisticsWithdrawLog.Error(err.Error())
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 {
StatisticsWithdrawLog.Error(err.Error())
return result.Error(http.StatusInternalServerError, err.Error())
}
}
return result.Json(http.StatusOK, "已经完成园区报告的统计。", fiber.Map{
return result.Success("已经完成园区报告的统计。", fiber.Map{
"statistics": fiber.Map{
"enterprises": enterprises,
"parks": parks,

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.Post("/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.Post("/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,
},
)
}

142
controller/top_up.go Normal file
View File

@ -0,0 +1,142 @@
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"
"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("商户充值记录删除不成功")
}
return result.Deleted(
"已经删除一条指定的商户充值记录",
)
}

View File

@ -3,131 +3,91 @@ package controller
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"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"
"fmt"
"electricity_bill_calc/vo"
"net/http"
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
func InitializeUserController(router *fiber.App) {
router.Delete("/password/:uid", security.OPSAuthorize, invalidUserPassword)
router.Delete("/login", security.MustAuthenticated, logout)
router.Put("/password", resetUserPassword)
router.Get("/accounts", security.ManagementAuthorize, listPagedUser)
router.Post("/login", login)
router.Put("/account/enabled/state", security.OPSAuthorize, switchUserEnabling)
router.Post("/account", security.OPSAuthorize, createOPSAndManagementAccount)
router.Get("/account/:uid", security.MustAuthenticated, getUserDetail)
var userLog = logger.Named("Handler", "User")
func InitializeUserHandlers(router *fiber.App) {
router.Delete("/login", security.MustAuthenticated, doLogout)
router.Post("/login", doLogin)
router.Get("/account", security.OPSAuthorize, searchUsers)
router.Post("/account", security.OPSAuthorize, createOPSAccount)
router.Get("/account/:uid", security.MustAuthenticated, fetchUserInformation)
router.Put("/account/:uid", security.OPSAuthorize, modifyUserInformation)
router.Put("/account/enabled/state", security.OPSAuthorize, changeUserState)
router.Get("/expiration", security.EnterpriseAuthorize, getAccountExpiration)
router.Post("/enterprise", security.OPSAuthorize, createEnterpriseAccount)
router.Put("/account/:uid", security.OPSAuthorize, modifyAccountDetail)
router.Get("/enterprise/quick/search", security.OPSAuthorize, quickSearchEnterprise)
router.Get("/expiration", security.EnterpriseAuthorize, fetchExpiration)
router.Put("/password", resetUserPassword)
router.Delete("/password/:uid", security.OPSAuthorize, invalidUserPassword)
}
type _LoginFormData struct {
type _LoginForm struct {
Username string `json:"uname"`
Password string `json:"upass"`
Type int8 `json:"type"`
Type int16 `json:"type"`
}
func login(c *fiber.Ctx) error {
result := response.NewResult(c)
loginData := new(_LoginFormData)
if err := c.BodyParser(loginData); err != nil {
return result.Error(http.StatusInternalServerError, "表单解析失败。")
func doLogin(c *fiber.Ctx) error {
result := response.NewResult(c) //创建一个相应结果对象
loginData := new(_LoginForm) //创建一个解析登录表单数据的实体
if err := c.BodyParser(loginData); err != nil { //解析请求体中的Json数据到loginData里如果解析出错就返回错误
userLog.Error("表单解析失败!", zap.Error(err))
return result.Error(http.StatusInternalServerError, "表单解析失败。") //返回一个内部服务器错误的相应结果
}
var (
session *model.Session
err error
)
if loginData.Type == model.USER_TYPE_ENT {
session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password)
userLog.Info("有用户请求登录。", zap.String("username", loginData.Username), zap.Int16("type", loginData.Type)) //记录日志相关信息
if loginData.Type == model.USER_TYPE_ENT { //根据登录类型选择不同的处理方法
session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password) //企业用户
} else {
session, err = service.UserService.ProcessManagementUserLogin(loginData.Username, loginData.Password)
userLog.Info("该用户是管理用户")
session, err = service.UserService.ProcessManagementUserLogin(loginData.Username, loginData.Password) //管理用户
}
if err != nil {
if authError, ok := err.(*exceptions.AuthenticationError); ok {
if authError.NeedReset {
if authError, ok := err.(*exceptions.AuthenticationError); ok { //检查错误是否为身份验证错误
if authError.NeedReset { //如果需要重置密码则返回对应结果
return result.LoginNeedReset()
}
return result.Error(int(authError.Code), authError.Message)
return result.Error(int(authError.Code), authError.Message) //返回身份验证错误相应
} else {
return result.Error(http.StatusInternalServerError, err.Error())
userLog.Error("用户登录请求处理失败!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error()) //返回内部服务器错误
}
}
return result.LoginSuccess(session)
return result.LoginSuccess(session) //返回登录成功相应结果,包含会话信息
}
func logout(c *fiber.Ctx) error {
func doLogout(c *fiber.Ctx) error {
result := response.NewResult(c)
session := c.Locals("session")
if session == nil {
session, err := _retreiveSession(c)
if err != nil {
return result.Success("用户会话已结束。")
}
_, err := cache.ClearSession(session.(*model.Session).Token)
_, 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 *fiber.Ctx) error {
result := response.NewResult(c)
targetUserId := c.Params("uid")
verifyCode, err := service.UserService.InvalidUserPassword(targetUserId)
if _, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound("未找到指定用户。")
}
if _, ok := err.(exceptions.UnsuccessfulOperationError); ok {
return result.NotAccept("未能成功更新用户的密码。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusAccepted, "用户密码已经失效", fiber.Map{"verify": verifyCode})
}
type _ResetPasswordFormData struct {
VerifyCode string `json:"verifyCode"`
Username string `json:"uname"`
NewPassword string `json:"newPass"`
}
func resetUserPassword(c *fiber.Ctx) error {
result := response.NewResult(c)
resetForm := new(_ResetPasswordFormData)
if err := c.BodyParser(resetForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
verified, err := service.UserService.VerifyUserPassword(resetForm.Username, resetForm.VerifyCode)
if _, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound("指定的用户不存在。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !verified {
return result.Error(http.StatusUnauthorized, "验证码不正确。")
}
completed, err := service.UserService.ResetUserPassword(resetForm.Username, resetForm.NewPassword)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if completed {
return result.Updated("用户凭据已更新。")
}
return result.NotAccept("用户凭据未能成功更新。")
}
func listPagedUser(c *fiber.Ctx) error {
func searchUsers(c *fiber.Ctx) error {
result := response.NewResult(c)
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
@ -145,213 +105,230 @@ func listPagedUser(c *fiber.Ctx) error {
} else {
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 {
return result.NotFound(err.Error())
}
return result.Json(
http.StatusOK,
return result.Success(
"已取得符合条件的用户集合。",
response.NewPagedResponse(requestPage, total).ToMap(),
fiber.Map{"accounts": users},
)
}
type _UserStateChangeFormData struct {
UserID string `json:"uid" form:"uid"`
Enabled bool `json:"enabled" form:"enabled"`
}
func switchUserEnabling(c *fiber.Ctx) error {
result := response.NewResult(c)
switchForm := new(_UserStateChangeFormData)
if err := c.BodyParser(switchForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
err := service.UserService.SwitchUserState(switchForm.UserID, switchForm.Enabled)
if err != nil {
if nfErr, ok := err.(*exceptions.NotFoundError); ok {
return result.NotFound(nfErr.Message)
} else {
return 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 *fiber.Ctx) error {
result := response.NewResult(c)
creationForm := new(_OPSAccountCreationFormData)
if err := c.BodyParser(creationForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
exists, err := service.UserService.IsUsernameExists(creationForm.Username)
if exists {
return result.Conflict("指定的用户名已经被使用了。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
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, _ = model.ParseDate("2099-12-31")
verifyCode, err := service.UserService.CreateUser(newUser, newUserDetail)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
cache.AbolishRelation("user")
return result.Json(http.StatusCreated, "用户已经成功创建。", fiber.Map{"verify": verifyCode})
}
func getUserDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
targetUserId := c.Params("uid")
exists, err := service.UserService.IsUserExists(targetUserId)
if !exists {
return result.NotFound("指定的用户不存在。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
userDetail, err := service.UserService.FetchUserDetail(targetUserId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "用户详细信息已获取到。", fiber.Map{"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 *fiber.Ctx) error {
result := response.NewResult(c)
creationForm := new(_EnterpriseCreationFormData)
if err := c.BodyParser(creationForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
exists, err := service.UserService.IsUsernameExists(creationForm.Username)
if exists {
return result.Conflict("指定的用户名已经被使用了。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
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 {
return result.BadRequest("用户月服务费无法解析。")
}
newUserDetail.ServiceExpiration = model.NewEmptyDate()
verifyCode, err := service.UserService.CreateUser(newUser, newUserDetail)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
cache.AbolishRelation("user")
return result.Json(http.StatusCreated, "用户已经成功创建。", fiber.Map{"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 *fiber.Ctx) error {
result := response.NewResult(c)
targetUserId := c.Params("uid")
modForm := new(_AccountModificationFormData)
if err := c.BodyParser(modForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
exists, err := service.UserService.IsUserExists(targetUserId)
if !exists {
return result.NotFound("指定的用户不存在。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
newUserInfo := new(model.UserDetail)
newUserInfo.Id = targetUserId
newUserInfo.Name = &modForm.Name
if len(modForm.Name) > 0 {
abbr := tools.PinyinAbbr(modForm.Name)
newUserInfo.Abbr = &abbr
}
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 {
return result.BadRequest("用户月服务费无法解析。")
}
_, err = global.DB.NewUpdate().Model(newUserInfo).
WherePK().
Column("name", "abbr", "region", "address", "contact", "phone", "unit_service_fee").
Exec(c.Context())
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
cache.AbolishRelation(fmt.Sprintf("user:%s", targetUserId))
return result.Updated("指定用户的信息已经更新。")
}
func quickSearchEnterprise(c *fiber.Ctx) error {
result := response.NewResult(c)
keyword := c.Query("keyword")
searchResult, err := service.UserService.SearchLimitUsers(keyword, 6)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "已查询到存在符合条件的企业", fiber.Map{"users": searchResult})
}
func fetchExpiration(c *fiber.Ctx) error {
func getAccountExpiration(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
userLog.Error("未找到有效的用户会话。", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
user, err := service.UserService.FetchUserDetail(session.Uid)
userDetail, err := repository.UserRepository.FindUserDetailById(session.Uid)
if err != nil {
return result.NotFound(err.Error())
return result.NotFound("未找到指定的用户档案")
}
return result.Json(
http.StatusOK,
return result.Success(
"已经取得用户的服务期限信息",
fiber.Map{"expiration": user.ServiceExpiration.Format("2006-01-02")},
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,81 +1,134 @@
package controller
import (
"electricity_bill_calc/exceptions"
"electricity_bill_calc/logger"
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"net/http"
"strconv"
"electricity_bill_calc/vo"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"net/http"
)
func InitializeWithdrawController(router *fiber.App) {
router.Delete("/publicity/:pid", security.EnterpriseAuthorize, applyReportWithdraw)
router.Get("/withdraws", security.OPSAuthorize, fetchWithdrawsWaitingAutdit)
router.Put("/withdraw/:rid", security.OPSAuthorize, auditWithdraw)
var withdrawLog = logger.Named("Handler", "Withdraw")
func InitializeWithdrawHandlers(router *fiber.App) {
router.Get("/withdraw", security.OPSAuthorize, withdraw)
router.Put("/withdraw/:rid", security.OPSAuthorize, reviewRequestWithdraw)
router.Delete("/withdraw/:rid", security.EnterpriseAuthorize, recallReport)
}
func applyReportWithdraw(c *fiber.Ctx) error {
//用于分页检索用户的核算报表
func withdraw(c *fiber.Ctx) error {
//记录日志
withdrawLog.Info("带分页的待审核的核算撤回申请列表")
//获取请求参数
result := response.NewResult(c)
requestReportId := c.Params("pid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
deleted, err := service.WithdrawService.ApplyWithdraw(requestReportId)
keyword := c.Query("keyword", "")
page := c.QueryInt("page", 1)
withdrawLog.Info("参数为: ", zap.String("keyword", keyword), zap.Int("page", page))
//中间数据库操作暂且省略。。。。
//首先进行核算报表的分页查询
withdraws, total, err := repository.WithdrawRepository.FindWithdraw(uint(page), &keyword)
if err != nil {
if nfErr, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound(nfErr.Error())
} else if ioErr, ok := err.(exceptions.ImproperOperateError); ok {
return result.NotAccept(ioErr.Error())
} else {
return result.Error(http.StatusInternalServerError, err.Error())
}
withdrawLog.Error("检索用户核算报表失败。", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if !deleted {
return result.Error(http.StatusInternalServerError, "未能完成公示报表的申请撤回操作。")
}
return result.Success("指定的公示报表已经申请撤回。")
}
func fetchWithdrawsWaitingAutdit(c *fiber.Ctx) error {
result := response.NewResult(c)
keyword := c.Query("keyword")
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return result.NotAccept("查询参数[page]格式不正确。")
}
reports, totalitems, err := service.WithdrawService.FetchPagedWithdrawApplies(requestPage, keyword)
if err != nil {
return result.NotFound(err.Error())
}
return result.Json(
http.StatusOK,
"已经取得符合条件的等待审核的撤回申请。",
response.NewPagedResponse(requestPage, totalitems).ToMap(),
fiber.Map{"records": reports},
//TODO: 2023-07-18 此处返回值是个示例,具体返回值需要查询数据库(完成)
return result.Success(
"withdraw请求成功",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"records": withdraws},
)
}
type WithdrawAuditFormData struct {
Audit bool `json:"audit" form:"audit"`
}
func auditWithdraw(c *fiber.Ctx) error {
//用于审核撤回报表
func reviewRequestWithdraw(c *fiber.Ctx) error {
Rid := c.Params("rid", "")
Data := new(vo.ReviewWithdraw)
result := response.NewResult(c)
requestReportId := c.Params("rid")
formData := new(WithdrawAuditFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
if err := c.BodyParser(&Data); err != nil {
withdrawLog.Error("无法解析审核指定报表的请求数据", zap.Error(err))
return result.BadRequest("无法解析审核指定报表的请求数据。")
}
err := service.WithdrawService.AuditWithdraw(requestReportId, formData.Audit)
if err != nil {
if nfErr, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound(nfErr.Error())
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.NotAccept(err.Error())
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("审核拒绝撤回报表成功!")
}
}
return result.Success("指定公示报表的撤回申请已经完成审核")
}
//用于撤回电费核算
func recallReport(c *fiber.Ctx) error {
// 获取用户会话信息和参数
rid := c.Params("rid", "")
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
withdrawLog.Error("无法获取当前用户的会话。")
return result.Unauthorized(err.Error())
}
// 检查指定报表的所属情况
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,8 +1,8 @@
package excel
import (
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"electricity_bill_calc/types"
"encoding/json"
"errors"
"fmt"
@ -19,11 +19,10 @@ import (
type ExcelTemplateGenerator interface {
Close()
WriteTo(w io.Writer) (int64, error)
WriteMeterData(meters []model.EndUserDetail) error
}
type ColumnRecognizer struct {
Pattern []string
Pattern [][]string
Tag string
MatchIndex int
MustFill bool
@ -45,7 +44,7 @@ type ExcelAnalysisError struct {
Err AnalysisError `json:"error"`
}
func NewColumnRecognizer(tag string, patterns ...string) ColumnRecognizer {
func NewColumnRecognizer(tag string, patterns ...[]string) ColumnRecognizer {
return ColumnRecognizer{
Pattern: patterns,
Tag: tag,
@ -67,9 +66,17 @@ func (e ExcelAnalysisError) Error() string {
func (r *ColumnRecognizer) Recognize(cellValue string) bool {
matches := make([]bool, 0)
for _, p := range r.Pattern {
matches = append(matches, strings.Contains(cellValue, p))
for _, pG := range r.Pattern {
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 acc && elem
}, true)
@ -189,6 +196,54 @@ func (a *ExcelAnalyzer[T]) Analysis(bean T) ([]T, []ExcelAnalysisError) {
} else {
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,35 +0,0 @@
package excel
import (
"electricity_bill_calc/model"
"io"
)
var (
endUserNonPVRecognizers = []*ColumnRecognizer{
{Pattern: []string{"电表编号"}, Tag: "meterId", MatchIndex: -1, MustFill: true},
{Pattern: []string{"上期", "(总)"}, Tag: "lastPeriodOverall", MatchIndex: -1, MustFill: true},
{Pattern: []string{"本期", "(总)"}, Tag: "currentPeriodOverall", MatchIndex: -1, MustFill: true},
{Pattern: []string{"退补", "(总)"}, Tag: "adjustOverall", MatchIndex: -1, MustFill: true},
}
endUserPVRecognizers = append(
endUserNonPVRecognizers,
&ColumnRecognizer{Pattern: []string{"上期", "(尖峰)"}, Tag: "lastPeriodCritical", MatchIndex: -1},
&ColumnRecognizer{Pattern: []string{"上期", "(峰)"}, Tag: "lastPeriodPeak", MatchIndex: -1},
&ColumnRecognizer{Pattern: []string{"上期", "(谷)"}, Tag: "lastPeriodValley", MatchIndex: -1},
&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,21 +1,139 @@
package excel
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"io"
"github.com/samber/lo"
"github.com/xuri/excelize/v2"
"go.uber.org/zap"
)
var meter04kVExcelRecognizers = []*ColumnRecognizer{
{Pattern: []string{"表号"}, Tag: "code", MatchIndex: -1, MustFill: true},
{Pattern: []string{"户名"}, Tag: "name", MatchIndex: -1},
{Pattern: []string{"户址"}, Tag: "address", MatchIndex: -1},
{Pattern: []string{"联系人"}, Tag: "contact", MatchIndex: -1},
{Pattern: []string{"电话"}, Tag: "phone", MatchIndex: -1},
{Pattern: []string{"倍率"}, Tag: "ratio", MatchIndex: -1, MustFill: true},
{Pattern: []string{"序号"}, Tag: "seq", MatchIndex: -1, MustFill: true},
{Pattern: []string{"公用设备"}, Tag: "public", MatchIndex: -1, MustFill: true},
var meterArchiveRecognizers = []*ColumnRecognizer{
{Pattern: [][]string{{"表号"}}, Tag: "code", MatchIndex: -1, MustFill: true},
{Pattern: [][]string{{"表址", "地址", "户址"}}, Tag: "address", MatchIndex: -1},
{Pattern: [][]string{{"类型"}}, Tag: "meterType", MatchIndex: -1, MustFill: true},
{Pattern: [][]string{{"建筑"}}, Tag: "building", MatchIndex: -1},
{Pattern: [][]string{{"楼层"}}, Tag: "onFloor", MatchIndex: -1},
{Pattern: [][]string{{"面积"}}, Tag: "area", MatchIndex: -1},
{Pattern: [][]string{{"倍率"}}, Tag: "ratio", MatchIndex: -1, MustFill: true},
{Pattern: [][]string{{"序号"}}, Tag: "seq", 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: "flat", MatchIndex: -1},
{Pattern: [][]string{{"有功", "表底", "底数"}, {"谷"}}, Tag: "valley", MatchIndex: -1},
}
func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.Meter04KV], error) {
return NewExcelAnalyzer[model.Meter04KV](file, meter04kVExcelRecognizers)
func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.MeterImportRow], error) {
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,
meter.CurrentPeriodOverall,
meter.AdjustOverall,
},
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,
meter.CurrentPeriodOverall,
meter.LastPeriodCritical,
meter.CurrentPeriodCritical,
meter.LastPeriodPeak,
meter.CurrentPeriodPeak,
meter.LastPeriodValley,
meter.CurrentPeriodValley,
meter.AdjustOverall,
meter.AdjustCritical,
meter.AdjustPeak,
meter.AdjustValley,
},
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
}

View File

@ -31,5 +31,5 @@ func NewUnauthorizedError(msg string) *UnauthorizedError {
}
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 {
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 {
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 {
return &NotFoundError{Message: fmt.Sprintf("%s%v", msg, err)}
return &NotFoundError{Message: fmt.Sprintf("所需数据未找到,%s%v", msg, err)}
}
func (e NotFoundError) Error() string {

View File

@ -1,11 +1,130 @@
package exceptions
type UnsuccessfulOperationError struct{}
import (
"strings"
)
func NewUnsuccessfulOperationError() *UnsuccessfulOperationError {
return &UnsuccessfulOperationError{}
type OperationType int16
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 {
return "Unsuccessful Operation"
func NewUnsuccessfulOperationError(oeprate OperationType, describe, message string) *UnsuccessfulOperationError {
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()
}

View File

@ -1,60 +1,91 @@
package global
import (
"database/sql"
"context"
"fmt"
"time"
"electricity_bill_calc/config"
"electricity_bill_calc/logger"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/driver/pgdriver"
"go.uber.org/zap/zapcore"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/samber/lo"
"go.uber.org/zap"
)
var (
DB *bun.DB
DB *pgxpool.Pool
)
func SetupDatabaseConnection() error {
// connStr := fmt.Sprintf(
// "host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai connect_timeout=0 tcp_user_timeout=180000",
// config.DatabaseSettings.Host,
// config.DatabaseSettings.User,
// config.DatabaseSettings.Pass,
// config.DatabaseSettings.DB,
// config.DatabaseSettings.Port,
// )
pgconn := pgdriver.NewConnector(
pgdriver.WithNetwork("tcp"),
pgdriver.WithAddr(fmt.Sprintf("%s:%d", config.DatabaseSettings.Host,
config.DatabaseSettings.Port)),
pgdriver.WithUser(config.DatabaseSettings.User),
pgdriver.WithInsecure(true),
pgdriver.WithPassword(config.DatabaseSettings.Pass),
pgdriver.WithDatabase(config.DatabaseSettings.DB),
pgdriver.WithDialTimeout(30*time.Second),
pgdriver.WithReadTimeout(3*time.Minute),
pgdriver.WithWriteTimeout(10*time.Minute),
connString := fmt.Sprintf(
"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",
config.DatabaseSettings.User,
config.DatabaseSettings.Pass,
config.DatabaseSettings.Host,
config.DatabaseSettings.Port,
config.DatabaseSettings.DB,
0,
"elec_service_go",
config.DatabaseSettings.MaxOpenConns,
config.DatabaseSettings.MaxIdleConns,
"60m",
"10m",
"10s",
)
sqldb := sql.OpenDB(pgconn)
DB = bun.NewDB(sqldb, pgdialect.New())
DB.AddQueryHook(logger.NewQueryHook(logger.QueryHookOptions{
LogSlow: 3 * time.Second,
Logger: logger.Named("PG"),
QueryLevel: zapcore.DebugLevel,
ErrorLevel: zapcore.ErrorLevel,
SlowLevel: zapcore.WarnLevel,
ErrorTemplate: "{{.Operation}}[{{.Duration}}]: {{.Query}}: {{.Error}}",
MessageTemplate: "{{.Operation}}[{{.Duration}}]: {{.Query}}",
}))
DB.SetMaxIdleConns(config.DatabaseSettings.MaxIdleConns)
DB.SetMaxOpenConns(config.DatabaseSettings.MaxOpenConns)
DB.SetConnMaxIdleTime(10 * time.Minute)
DB.SetConnMaxLifetime(60 * time.Minute)
DB.Ping()
poolConfig, err := pgxpool.ParseConfig(connString)
if err != nil {
logger.Named("DB INIT").Error("数据库连接初始化失败。", zap.Error(err))
return err
}
poolConfig.ConnConfig.Tracer = QueryLogger{logger: logger.Named("PG")}
DB, _ = pgxpool.NewWithConfig(context.Background(), poolConfig)
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)
})...)
// for index, arg := range data.Args {
// ql.logger.Info(fmt.Sprintf("[Arg %d]: %v", index, arg))
// }
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

@ -15,10 +15,13 @@ var (
func SetupRedisConnection() error {
var err error
a := fmt.Sprintf("%s:%d", config.RedisSettings.Host, config.RedisSettings.Port)
fmt.Println(a)
Rd, err = rueidis.NewClient(rueidis.ClientOption{
InitAddress: []string{fmt.Sprintf("%s:%d", config.RedisSettings.Host, config.RedisSettings.Port)},
Password: config.RedisSettings.Password,
InitAddress: []string{"127.0.0.1:6379"},
Password: "",
SelectDB: config.RedisSettings.DB,
DisableCache:true,
})
if err != nil {
return err

74
go.mod
View File

@ -4,67 +4,83 @@ go 1.19
require (
github.com/deckarep/golang-set/v2 v2.1.0
github.com/fufuok/utils v0.7.13
github.com/gofiber/fiber/v2 v2.38.1
github.com/fufuok/utils v0.10.2
github.com/georgysavva/scany/v2 v2.0.0
github.com/gofiber/fiber/v2 v2.46.0
github.com/google/uuid v1.3.0
github.com/jackc/pgx/v5 v5.3.1
github.com/jinzhu/copier v0.3.5
github.com/liamylian/jsontime/v2 v2.0.0
github.com/mozillazg/go-pinyin v0.19.0
github.com/rueian/rueidis v0.0.73
github.com/samber/lo v1.27.0
github.com/mozillazg/go-pinyin v0.20.0
github.com/rueian/rueidis v0.0.100
github.com/samber/lo v1.38.1
github.com/shopspring/decimal v1.3.1
github.com/spf13/viper v1.12.0
github.com/valyala/fasthttp v1.40.0
github.com/xuri/excelize/v2 v2.6.1
go.uber.org/zap v1.23.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
github.com/spf13/viper v1.16.0
github.com/valyala/fasthttp v1.47.0
github.com/xuri/excelize/v2 v2.7.1
go.uber.org/zap v1.24.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)
require (
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/klauspost/compress v1.15.0 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/jmoiron/sqlx v1.3.5 // 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/rogpeppe/go-internal v1.9.0 // 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/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/sync v0.2.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
mellium.im/sasl v0.3.0 // indirect
)
require (
github.com/fsnotify/fsnotify v1.5.4 // indirect
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/magiconair/properties v1.8.6 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.3 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // 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/uptrace/bun v1.1.8
github.com/uptrace/bun/dialect/pgdialect v1.1.8
github.com/uptrace/bun/driver/pgdriver v1.1.8
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9 // indirect
github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

164
go.sum
View File

@ -39,8 +39,11 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@ -55,6 +58,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY=
github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -62,15 +68,26 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fufuok/utils v0.7.13 h1:FGx8Mnfg0ZB8HdVz1X60JJ2kFu1rtcsFDYUxUTzNKkU=
github.com/fufuok/utils v0.7.13/go.mod h1:ztIaorPqZGdbvmW3YlwQp80K8rKJmEy6xa1KwpJSsmk=
github.com/fufuok/utils v0.10.2 h1:jXgE7yBSUW9z+sJs/VQq3o4MH+jN30PzIILVXFw73lE=
github.com/fufuok/utils v0.10.2/go.mod h1:87MJq0gAZDYBgUOpxSGoLkdv8VCuRNOL9vK02F7JC3s=
github.com/georgysavva/scany/v2 v2.0.0 h1:RGXqxDv4row7/FYoK8MRXAZXqoWF/NM+NP0q50k3DKU=
github.com/georgysavva/scany/v2 v2.0.0/go.mod h1:sigOdh+0qb/+aOs3TVhehVT10p8qJL7K/Zhyz8vWo38=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gofiber/fiber/v2 v2.38.1 h1:GEQ/Yt3Wsf2a30iTqtLXlBYJZso0JXPovt/tmj5H9jU=
github.com/gofiber/fiber/v2 v2.38.1/go.mod h1:t0NlbaXzuGH7I+7M4paE848fNWInZ7mfxI/Er1fTth8=
github.com/gofiber/fiber/v2 v2.46.0 h1:wkkWotblsGVlLjXj2dpgKQAYHtXumsK/HyFugQM68Ns=
github.com/gofiber/fiber/v2 v2.46.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -135,10 +152,20 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk=
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@ -147,17 +174,35 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/liamylian/jsontime/v2 v2.0.0 h1:3if2kDW/boymUdO+4Qj/m4uaXMBSF6np9KEgg90cwH0=
github.com/liamylian/jsontime/v2 v2.0.0/go.mod h1:UHp1oAPqCBfspokvGmaGe0IAl2IgOpgOgDaKPcvcGGY=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -171,10 +216,17 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/mozillazg/go-pinyin v0.19.0 h1:p+J8/kjJ558KPvVGYLvqBhxf8jbZA2exSLCs2uUVN8c=
github.com/mozillazg/go-pinyin v0.19.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
github.com/mozillazg/go-pinyin v0.20.0 h1:BtR3DsxpApHfKReaPO1fCqF4pThRwH9uwvXzm+GnMFQ=
github.com/mozillazg/go-pinyin v0.20.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -187,26 +239,48 @@ github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rueian/rueidis v0.0.73 h1:+r0Z6C6HMnkquPgY3zaHVpTqmCyJL56Z36GSlyBrufk=
github.com/rueian/rueidis v0.0.73/go.mod h1:FwnfDILF2GETrvXcYFlhIiru/7NmSIm1f+7C5kutO0I=
github.com/rueian/rueidis v0.0.100 h1:22yp/+8YHuWc/vcrp8bkjeE7baD3vygoh2gZ2+xu1KQ=
github.com/rueian/rueidis v0.0.100/go.mod h1:ivvsRYRtAUcf9OnheuKc5Gpa8IebrkLT1P45Lr2jlXE=
github.com/samber/lo v1.27.0 h1:GOyDWxsblvqYobqsmUuMddPa2/mMzkKyojlXol4+LaQ=
github.com/samber/lo v1.27.0/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4=
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -216,9 +290,18 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=
github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/uptrace/bun v1.1.8 h1:slxuaP4LYWFbPRUmTtQhfJN+6eX/6ar2HDKYTcI50SA=
@ -231,6 +314,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.40.0 h1:CRq/00MfruPGFLTQKY8b+8SfdK60TxNztjRMnH0t1Yc=
github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c=
github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
@ -239,14 +324,21 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9 h1:ge5g8vsTQclA5lXDi+PuiAFw5GMIlMHOB/5e1hsf96E=
github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.6.1 h1:ICBdtw803rmhLN3zfvyEGH3cwSmZv+kde7LhTDT659k=
github.com/xuri/excelize/v2 v2.6.1/go.mod h1:tL+0m6DNwSXj/sILHbQTYsLi9IF4TW59H2EF3Yrx1AU=
github.com/xuri/excelize/v2 v2.7.1 h1:gm8q0UCAyaTt3MEF5wWMjVdmthm2EHAWesGSKS9tdVI=
github.com/xuri/excelize/v2 v2.7.1/go.mod h1:qc0+2j4TvAUrBw36ATtcTeC1VCM0fFdAXZOmcF4nTpY=
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83 h1:xVwnvkzzi+OiwhIkWOXvh1skFI6bagk8OvGuazM80Rw=
github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -256,22 +348,41 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -284,10 +395,14 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -309,6 +424,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -342,8 +460,17 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -363,6 +490,11 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -401,11 +533,27 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U=
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -415,6 +563,14 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -459,12 +615,16 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -564,8 +724,12 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -1,163 +0,0 @@
package logger
import (
"bytes"
"context"
"database/sql"
"fmt"
"strings"
"text/template"
"time"
"github.com/uptrace/bun"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// QueryHookOptions logging options
type QueryHookOptions struct {
LogSlow time.Duration
Logger *zap.Logger
QueryLevel zapcore.Level
SlowLevel zapcore.Level
ErrorLevel zapcore.Level
MessageTemplate string
ErrorTemplate string
}
// QueryHook wraps query hook
type QueryHook struct {
opts QueryHookOptions
errorTemplate *template.Template
messageTemplate *template.Template
}
// LogEntryVars variables made available t otemplate
type LogEntryVars struct {
Timestamp time.Time
Query string
Operation string
Duration time.Duration
Error error
}
// NewQueryHook returns new instance
func NewQueryHook(opts QueryHookOptions) *QueryHook {
h := new(QueryHook)
if opts.ErrorTemplate == "" {
opts.ErrorTemplate = "{{.Operation}}[{{.Duration}}]: {{.Query}}: {{.Error}}"
}
if opts.MessageTemplate == "" {
opts.MessageTemplate = "{{.Operation}}[{{.Duration}}]: {{.Query}}"
}
h.opts = opts
errorTemplate, err := template.New("ErrorTemplate").Parse(h.opts.ErrorTemplate)
if err != nil {
panic(err)
}
messageTemplate, err := template.New("MessageTemplate").Parse(h.opts.MessageTemplate)
if err != nil {
panic(err)
}
h.errorTemplate = errorTemplate
h.messageTemplate = messageTemplate
return h
}
// BeforeQuery does nothing tbh
func (h *QueryHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context {
return ctx
}
// AfterQuery convert a bun QueryEvent into a logrus message
func (h *QueryHook) AfterQuery(ctx context.Context, event *bun.QueryEvent) {
var level zapcore.Level
var isError bool
var msg bytes.Buffer
now := time.Now()
dur := now.Sub(event.StartTime)
switch event.Err {
case nil, sql.ErrNoRows:
isError = false
if h.opts.LogSlow > 0 && dur >= h.opts.LogSlow {
level = h.opts.SlowLevel
} else {
level = h.opts.QueryLevel
}
default:
isError = true
level = h.opts.ErrorLevel
}
if level == 0 {
return
}
args := &LogEntryVars{
Timestamp: now,
Query: string(event.Query),
Operation: eventOperation(event),
Duration: dur,
Error: event.Err,
}
if isError {
if err := h.errorTemplate.Execute(&msg, args); err != nil {
panic(err)
}
} else {
if err := h.messageTemplate.Execute(&msg, args); err != nil {
panic(err)
}
}
switch level {
case zapcore.DebugLevel:
h.opts.Logger.Debug(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs))
case zapcore.InfoLevel:
h.opts.Logger.Info(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs))
case zapcore.WarnLevel:
h.opts.Logger.Warn(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs))
case zapcore.ErrorLevel:
h.opts.Logger.Error(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs), zap.Error(event.Err))
case zapcore.FatalLevel:
h.opts.Logger.Fatal(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs), zap.Error(event.Err))
case zapcore.PanicLevel:
h.opts.Logger.Panic(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs), zap.Error(event.Err))
default:
panic(fmt.Errorf("unsupported level: %v", level))
}
}
// taken from bun
func eventOperation(event *bun.QueryEvent) string {
switch event.QueryAppender.(type) {
case *bun.SelectQuery:
return "SELECT"
case *bun.InsertQuery:
return "INSERT"
case *bun.UpdateQuery:
return "UPDATE"
case *bun.DeleteQuery:
return "DELETE"
case *bun.CreateTableQuery:
return "CREATE TABLE"
case *bun.DropTableQuery:
return "DROP TABLE"
}
return queryOperation(event.Query)
}
// taken from bun
func queryOperation(name string) string {
if idx := strings.Index(name, " "); idx > 0 {
name = name[:idx]
}
if len(name) > 16 {
name = name[:16]
}
return string(name)
}

View File

@ -1,8 +1,10 @@
package logger
import (
"electricity_bill_calc/types"
"os"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
@ -40,7 +42,7 @@ func init() {
logger = zap.New(core).Named("App")
sugaredLogger = logger.Sugar()
logger.Info("Logger initialized.")
logger.Info("日志系统初始化完成。")
}
func GetLogger() *zap.Logger {
@ -130,3 +132,53 @@ func With(fields ...zap.Field) *zap.Logger {
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)
}

View File

@ -62,9 +62,13 @@ func NewLogMiddleware(config LogMiddlewareConfig) fiber.Handler {
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)),
// zap.Object("response", Resp(c.Response())),
// zap.Object("request", Req(c)),
}
if u := c.Locals("userId"); u != nil {

View File

@ -1,9 +1,12 @@
package logger
import (
"fmt"
"io"
"log"
"math"
"os"
"time"
"gopkg.in/natefinch/lumberjack.v2"
)
@ -14,10 +17,11 @@ func newRollingWriter() io.Writer {
return nil
}
now := time.Now()
return &lumberjack.Logger{
Filename: "log/service.log",
MaxBackups: 366 * 10, // files
MaxSize: 200, // megabytes
MaxAge: 366 * 10, // days
Filename: fmt.Sprintf("log/service_%s.log", now.Format("2006-01-02_15")),
MaxBackups: math.MaxInt, // files
MaxSize: 200, // megabytes
MaxAge: math.MaxInt, // days
}
}

152
main.go
View File

@ -1,25 +1,18 @@
package main
import (
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/migration"
"electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/router"
"electricity_bill_calc/service"
"encoding/csv"
"electricity_bill_calc/types"
"fmt"
"io"
"os"
"strconv"
"time"
"github.com/samber/lo"
"github.com/shopspring/decimal"
"github.com/uptrace/bun/migrate"
"go.uber.org/zap"
)
@ -27,145 +20,70 @@ func init() {
l := logger.Named("Init")
err := config.SetupSetting()
if err != nil {
l.Fatal("Configuration load failed.", zap.Error(err))
l.Fatal("服务配置文件加载失败!", zap.Error(err))
}
l.Info("Configuration loaded!")
l.Info("服务配置已经完成加载。")
err = global.SetupDatabaseConnection()
if err != nil {
l.Fatal("Main Database connect failed.", zap.Error(err))
}
l.Info("Main Database connected!")
migrator := migrate.NewMigrator(global.DB, migration.Migrations)
ctx, cancel := global.TimeoutContext(12)
defer cancel()
err = migrator.Init(ctx)
if err != nil {
l.Fatal("Database migration unable to initialized.", zap.Error(err))
}
group, err := migrator.Migrate(ctx)
if err != nil {
l.Fatal("Database migrate failed.", zap.Error(err))
}
if group.IsZero() {
l.Info("There are no new migrations to run (database is up to date)")
l.Fatal("主数据库连接失败!", zap.Error(err))
}
l.Info("主数据库已经连接。")
err = global.SetupRedisConnection()
if err != nil {
l.Fatal("Main Cache Database connect failed.", zap.Error(err))
l.Fatal("主缓存数据库连接失败!", zap.Error(err))
}
l.Info("Main Cache Database connected!")
err = initializeRegions()
if err != nil {
l.Fatal("Regions initialize failed.", zap.Error(err))
}
l.Info("Regions synchronized.")
l.Info("主缓存数据库已经连接。")
err = intializeSingularity()
if err != nil {
l.Fatal("Singularity account intialize failed.", zap.Error(err))
l.Fatal("奇点账号初始化失败。", zap.Error(err))
}
l.Info("Singularity account intialized.")
}
func initializeRegions() error {
ctx, cancel := global.TimeoutContext()
defer cancel()
logger.Info("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.DB.NewSelect().Model((*model.Region)(nil)).
Column("code").
Scan(ctx, &existRegions)
if err != nil {
return fmt.Errorf("unable to retreive regions from database: %w", err)
}
regionCsv := csv.NewReader(regionCsvFile)
transaction, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if 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.NewInsert().Model(&model.Region{
Code: record[0],
Name: record[1],
Level: level,
Parent: record[3],
}).Exec(ctx); 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
l.Info("奇点账号已经完成初始化。")
}
func intializeSingularity() error {
singularityExists, err := service.UserService.IsUserExists("000")
l := logger.Named("Init", "Singularity")
singularityExists, err := repository.UserRepository.IsUserExists("000")
if err != nil {
return fmt.Errorf("singularity detect failed: %w", err)
l.Error("检测奇点账号失败。", zap.Error(err))
return fmt.Errorf("检测奇点账号失败: %w", err)
}
if singularityExists {
l.Info("奇点账号已经存在,跳过剩余初始化步骤。")
return nil
}
singularity := &model.User{
Id: "000",
singularityId := "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",
Name: "Singularity",
Type: 2,
Enabled: true,
Expires: singularityExpires,
}
singularityName := "Singularity"
singularityExpires, err := model.ParseDate("2099-12-31")
verifyCode, err := service.UserService.CreateUserAccount(
singularity.IntoUser(),
singularity.IntoUserDetail())
if err != nil {
return fmt.Errorf("singularity expires time parse failed: %w", err)
}
singularityDetail := &model.UserDetail{
Name: &singularityName,
UnitServiceFee: decimal.Zero,
ServiceExpiration: singularityExpires,
}
verifyCode, err := service.UserService.CreateUser(singularity, singularityDetail)
if err != nil {
return fmt.Errorf("singularity account failed to create: %w", err)
l.Error("创建奇点账号失败。", zap.Error(err))
return fmt.Errorf("创建奇点账号失败: %w", err)
}
logger.Info(
fmt.Sprintf("Singularity account created, use %s as verify code to reset password.", verifyCode),
zap.String("account", "singularity"),
zap.String("verifyCode", verifyCode),
fmt.Sprintf("奇点账号已经完成创建, 首次登录需要使用验证码 [%s] 重置密码。", *verifyCode),
zap.String("账号名称", "singularity"),
zap.String("验证码", *verifyCode),
)
return nil
}
func DBConnectionKeepLive() {
for range time.Tick(30 * time.Second) {
err := global.DB.Ping()
if err != nil {
continue
}
}
}
// 清理Redis缓存中的孤儿键。
func RedisOrphanCleanup() {
cleanLogger := logger.Named("Cache").With(zap.String("function", "Cleanup"))
for range time.Tick(2 * time.Minute) {
@ -179,8 +97,6 @@ func RedisOrphanCleanup() {
}
func main() {
// 本次停用检测的原因是使用Ping来保持数据库链接看起来没有什么用处。
// go DBConnectionKeepLive()
go RedisOrphanCleanup()
app := router.App()
app.Listen(fmt.Sprintf(":%d", config.ServerSettings.HttpPort))

View File

@ -1,240 +0,0 @@
create table if not exists region (
code varchar(15) not null primary key,
name varchar(50) not null,
level smallint not null default 0,
parent varchar(15) not null default '0'
);
create table if not exists `user` (
created_at timestamptz not null,
id varchar(120) not null primary key,
username varchar(30) not null,
password varchar(256) not null,
reset_needed boolean not null default false,
type smallint not null,
enabled boolean not null default true
);
create table if not exists user_detail (
created_at timestamptz not null,
created_by varchar(100),
last_modified_at timestamptz,
last_modified_by varchar(100),
deleted_at timestamptz,
deleted_by varchar(100),
id varchar(120) not null primary key,
name varchar(100),
abbr varchar(50),
region varchar(10),
address varchar(120),
contact varchar(100),
phone varchar(50),
unit_service_fee numeric(8,2) not null default 0,
service_expiration date not null
);
create table if not exists user_charge (
created_at timestamptz not null,
seq bigserial not null primary key,
user_id varchar(120) not null,
fee numeric(12,2),
discount numeric(5,4),
amount numeric(12,2),
charge_to date not null,
settled boolean not null default false,
settled_at timestamptz,
cancelled boolean not null default false,
cancelled_at timestamptz,
refunded boolean not null default false,
refunded_at timestamptz
);
create table if not exists park (
created_at timestamptz not null,
created_by varchar(100),
last_modified_at timestamptz,
last_modified_by varchar(100),
deleted_at timestamptz,
deleted_by varchar(100),
id varchar(120) not null primary key,
user_id varchar(120) not null,
name varchar(70) not null,
abbr varchar(50),
area numeric(14,2),
tenement_quantity numeric(8,0),
capacity numeric(16,2),
category smallint not null default 0,
meter_04kv_type smallint not null default 0,
region varchar(10),
address varchar(120),
contact varchar(100),
phone varchar(50),
enabled boolean not null default true
);
create table if not exists meter_04kv (
created_at timestamptz not null,
created_by varchar(100),
last_modified_at timestamptz,
last_modified_by varchar(100),
code varchar(120) not null,
park_id varchar(120) not null,
address varchar(100),
customer_name varchar(100),
contact_name varchar(70),
contact_phone varchar(50),
ratio numeric(8,4) not null default 1,
seq bigint not null default 0,
public_meter boolean not null default false,
dilute boolean not null default false,
enabled boolean not null default true,
primary key (code, park_id)
);
create table if not exists maintenance_fee (
created_at timestamptz not null,
created_by varchar(100),
last_modified_at timestamptz,
last_modified_by varchar(100),
deleted_at timestamptz,
deleted_by varchar(100),
id varchar(120) not null primary key,
park_id varchar(120) not null,
name varchar(50) not null,
fee numeric(8,2) not null default 0,
memo text,
enabled boolean not null default true
);
create table if not exists report (
created_at timestamptz not null,
created_by varchar(100),
last_modified_at timestamptz,
last_modified_by varchar(100),
id varchar(120) not null primary key,
park_id varchar(120) not null,
period date not null,
category smallint not null default 0,
meter_04kv_type smallint not null default 0,
step_state jsonb not null,
published boolean not null default false,
published_at timestamptz,
withdraw smallint not null default 0,
last_withdraw_applied_at timestamptz,
last_withdraw_audit_at timestamptz
);
create table if not exists report_summary (
report_id varchar(120) not null primary key,
overall numeric(14,2) not null default 0,
overall_fee numeric(14,2) not null default 0,
consumption_fee numeric(14,2),
overall_price numeric(16,8),
critical numeric(14,2) not null default 0,
critical_fee numeric(14,2) not null default 0,
critical_price numeric(16,8),
peak numeric(14,2) not null default 0,
peak_fee numeric(14,2) not null default 0,
peak_price numeric(16,8),
flat numeric(14,2) not null default 0,
flat_fee numeric(14,2) not null default 0,
flat_price numeric(16,8),
valley numeric(14,2) not null default 0,
valley_fee numeric(14,2) not null default 0,
valley_price numeric(16,8),
loss numeric(14,2),
loss_fee numeric(16,2),
loss_proportion numeric(16,15),
customer_consumption numeric(16,2),
customer_consumption_fee numeric(14,2),
customer_consumption_critical numeric(16,2),
customer_consumption_critical_fee numeric(14,2),
customer_consumption_peak numeric(16,2),
customer_consumption_peak_fee numeric(14,2),
customer_consumption_flat numeric(16,2),
customer_consumption_flat_fee numeric(14,2),
customer_consumption_valley numeric(16,2),
customer_consumption_valley_fee numeric(14,2),
public_consumption numeric(16,2),
public_consumption_fee numeric(14,2),
public_consumption_proportion numeric(16,15),
public_consumption_critical numeric(16,2),
public_consumption_critical_fee numeric(14,2),
public_consumption_peak numeric(16,2),
public_consumption_peak_fee numeric(14,2),
public_consumption_flat numeric(16,2),
public_consumption_flat_fee numeric(14,2),
public_consumption_valley numeric(16,2),
public_consumption_valley_fee numeric(14,2),
basic_fee numeric(14,2) not null default 0,
basic_diluted_price numeric(18,8),
adjust_fee numeric(14,2) not null default 0,
adjust_diluted_price numeric(18,8),
maintenance_diluted_price numeric(16,8),
loss_diluted_price numeric(16,8),
public_consumption_diluted_price numeric(16,8),
maintenance_overall numeric(16,8),
final_diluted_overall numeric(14,2)
);
create table if not exists will_diluted_fee (
id varchar(120) not null primary key,
report_id varchar(120) not null,
source_id varchar(120),
name varchar(50) not null,
fee numeric(8,2) not null default 0,
memo text
);
create table if not exists end_user_detail (
created_at timestamptz not null,
created_by varchar(100),
last_modified_at timestamptz,
last_modified_by varchar(100),
report_id varchar(120) not null,
park_id varchar(120) not null,
meter_04kv_id varchar(120) not null,
seq bigint not null default 0,
ratio numeric(8,4) not null default 1,
address varchar(100),
customer_name varchar(100),
contact_name varchar(70),
contact_phone varcahar(50),
public_meter boolean not null default false,
dilute boolean not null default false,
last_period_overall numeric(14,2) not null default 0,
last_period_critical numeric(14,2) not null default 0,
last_period_peak numeric(14,2) not null default 0,
last_period_flat numeric(14,2) not null default 0,
last_period_valley numeric(14,2) not null default 0,
current_period_overall numeric(14,2) not null default 0,
current_period_critical numeric(14,2) not null default 0,
current_period_peak numeric(14,2) not null default 0,
current_period_flat numeric(14,2) not null default 0,
current_period_valley numeric(14,2) not null default 0,
adjust_overall numeric(14,2) not null default 0,
adjust_critical numeric(14,2) not null default 0,
adjust_peak numeric(14,2) not null default 0,
adjust_flat numeric(14,2) not null default 0,
adjust_valley numeric(14,2) not null default 0,
overall numeric(14,2),
overall_fee numeric(14,2),
overall_proportion numeric(16,15) not null default 0,
critical numeric(14,2),
critical_fee numeric(18,8),
peak numeric(14,2),
peak_fee numeric(18,8),
flat numeric(14,2),
flat_fee numeric(18,8),
valley numeric(14,2),
valley_fee numeric(18,8),
basic_fee_diluted numeric(18,8),
adjust_fee_diluted numeric(18,8),
loss_diluted numeric(18,8),
loss_fee_diluted numeric(18,8),
maintenance_fee_diluted numeric(18,8),
public_consumption_diluted numeric(18,8),
final_diluted numeric(14,2),
final_charge numeric(14,2),
primary key (report_id, park_id, meter_04kv_id)
);

View File

@ -1,21 +0,0 @@
drop table if exists region;
drop table if exists user;
drop table if exists user_detail;
drop table if exists user_charge;
drop table if exists park;
drop table if exists meter_04kv;
drop table if exists maintenance_fee;
drop table if exists report;
drop table if exists report_summary;
drop table if exists will_diluted_fee;
drop table if exists end_user_detail;

View File

@ -1,31 +0,0 @@
alter table if exists `user` add constraint user_type_check check (type in (0, 1, 2));
alter table if exists user_detail add constraint positive_service_fee check (unit_service_fee >= 0);
alter table if exists user_charge add constraint positive_fee check (fee >= 0);
alter table if exists user_charge add constraint positive_amount check (amount >= 0);
alter table if exists park add constraint positive_tenement check (tenement_quantity >= 0);
alter table if exists park add constraint positive_area check (area >= 0);
alter table if exists park add constraint positive_capacity check (capacity >= 0);
alter table if exists park add constraint category_check check (category in (0, 1, 2));
alter table if exists park add constraint meter_check check (meter_04kv_type in (0, 1));
alter table if exists meter_04kv add constraint positive_ratio check (ratio > 0);
alter table if exists maintenance_fee add constraint positive_fee check (fee >= 0);
alter table if exists report add constraint category_check check (category in (0, 1, 2));
alter table if exists report add constraint meter_check check (meter_04kv_type in (0, 1));
alter table if exists report add constraint withdraw_action_check check (withdraw in (0, 1, 2, 3));
alter table if exists will_diluted_fee add constraint positive_fee check (fee >= 0);
alter table if exists end_user_detail add constraint positive_ratio check (ratio > 0);

View File

@ -1,31 +0,0 @@
alter table if exists user drop constraint user_type_check;
alter table if exists user_detail drop constraint positive_service_fee;
alter table if exists user_charge drop constraint positive_fee;
alter table if exists user_charge drop constraint positive_amount;
alter table if exists park drop constraint positive_tenement;
alter table if exists park drop constraint positive_area;
alter table if exists park drop constraint positive_capacity;
alter table if exists park drop constraint category_check;
alter table if exists park drop constraint meter_check;
alter table if exists meter_04kv drop constraint positive_ratio;
alter table if exists maintenance_fee drop constraint positive_fee;
alter table if exists report drop constraint category_check;
alter table if exists report drop constraint meter_check;
alter table if exists report drop constraint withdraw_action_check;
alter table if exists will_diluted_fee drop constraint positive_fee;
alter table if exists end_user_detail drop constraint positive_ratio;

View File

@ -1,23 +0,0 @@
update report_summary
set
customer_consumption = nullif(trim(customers->>'consumption'), '')::numeric(16,2),
customer_consumption_fee = nullif(trim(customers->>'consumptionFee'), '')::numeric(14,2),
customer_consumption_critical = nullif(trim(customers->>'critical'), '')::numeric(16,2),
customer_consumption_critical_fee = nullif(trim(customers->>'criticalFee'), '')::numeric(14,2),
customer_consumption_peak = nullif(trim(customers->>'peak'), '')::numeric(16,2),
customer_consumption_peak_fee = nullif(trim(customers->>'peakFee'), '')::numeric(14,2),
customer_consumption_flat = nullif(trim(customers->>'flat'), '')::numeric(16,2),
customer_consumption_flat_fee = nullif(trim(customers->>'flatFee'), '')::numeric(14,2),
customer_consumption_valley = nullif(trim(customers->>'valley'), '')::numeric(16,2),
customer_consumption_valley_fee = nullif(trim(customers->>'valleyFee'), '')::numeric(14,2),
public_consumption = nullif(trim(publics->>'consumption'), '')::numeric(16,2),
public_consumption_fee = nullif(trim(publics->>'consumptionFee'), '')::numeric(14,2),
public_consumption_critical = nullif(trim(publics->>'critical'), '')::numeric(16,2),
public_consumption_critical_fee = nullif(trim(publics->>'criticalFee'), '')::numeric(14,2),
public_consumption_peak = nullif(trim(publics->>'peak'), '')::numeric(16,2),
public_consumption_peak_fee = nullif(trim(publics->>'peakFee'), '')::numeric(14,2),
public_consumption_flat = nullif(trim(publics->>'flat'), '')::numeric(16,2),
public_consumption_flat_fee = nullif(trim(publics->>'flatFee'), '')::numeric(14,2),
public_consumption_valley = nullif(trim(publics->>'valley'), '')::numeric(16,2),
public_consumption_valley_fee = nullif(trim(publics->>'valleyFee'), '')::numeric(14,2),
public_consumption_proportion = nullif(trim(publics->>'proportion'), '')::numeric(16,15);

View File

@ -1,46 +0,0 @@
alter table report_summary
add column if not exists customers jsonb,
add column if not exists publics jsonb,
add column if not exists diluteds jsonb;
update report_summary
set
customers = jsonb_build_object(
'consumption', customer_consumption,
'fee', customer_consumption_fee,
'critical', customer_consumption_critical,
'criticalFee', customer_consumption_critical_fee,
'peak', customer_consumption_peak,
'peakFee', customer_consumption_peak_fee,
'flat', customer_consumption_flat,
'flatFee', customer_consumption_flat_fee,
'valley', customer_consumption_valley,
'valleyFee', customer_consumption_valley_fee,
'proportion', null
),
diluteds = jsonb_build_object(
'consumption', public_consumption,
'fee', public_consumption_fee,
'critical', public_consumption_critical,
'criticalFee', public_consumption_critical_fee,
'peak', public_consumption_peak,
'peakFee', public_consumption_peak_fee,
'flat', public_consumption_flat,
'flatFee', public_consumption_flat_fee,
'valley', public_consumption_valley,
'valleyFee', public_consumption_valley_fee,
'proportion', public_consumption_proportion
),
publics = jsonb_build_object(
'consumption', null,
'fee', null,
'critical', null,
'criticalFee', null,
'peak', null,
'peakFee', null,
'flat', null,
'flatFee', null,
'valley', null,
'valleyFee', null,
'proportion', null
);

View File

@ -1,15 +0,0 @@
alter table meter_04kv
add column dilute boolean not null default false;
alter table end_user_detail
add column dilute boolean not null default false,
add column maintenance_fee_diluted numeric(18,8),
add column public_consumption_diluted numeric(18,8);
alter table maintenance_fee
drop column period;
alter table report_summary
drop column authorize_loss,
drop column authorize_loss_fee,
drop column authorize_loss_proportion;

View File

@ -1,37 +0,0 @@
alter table meter_04kv
drop column dilute;
alter table end_user_detail
drop column dilute,
drop column maintenance_fee_diluted,
drop column public_consumption_diluted;
alter table maintenance_fee
add column period varchar(10);
alter table report_summary
drop column customer_consumption,
drop column customer_consumption_fee,
drop column customer_consumption_critical,
drop column customer_consumption_critical_fee,
drop column customer_consumption_peak,
drop column customer_consumption_peak_fee,
drop column customer_consumption_flat,
drop column customer_consumption_flat_fee,
drop column customer_consumption_valley,
drop column customer_consumption_valley_fee,
drop column public_consumption,
drop column public_consumption_fee,
drop column public_consumption_critical,
drop column public_consumption_critical_fee,
drop column public_consumption_peak,
drop column public_consumption_peak_fee,
drop column public_consumption_flat,
drop column public_consumption_flat_fee,
drop column public_consumption_valley,
drop column public_consumption_valley_fee,
drop column public_consumption_proportion,
drop column diluteds,
add column authorize_loss numeric(14,2),
add column authorize_loss_fee numeric(16,2),
add column authorize_loss_proportion numeric(16,15);

View File

@ -1,21 +0,0 @@
package migration
import (
"electricity_bill_calc/logger"
"embed"
"github.com/uptrace/bun/migrate"
"go.uber.org/zap"
)
var (
//go:embed *.sql
sqlMigrations embed.FS
Migrations = migrate.NewMigrations()
)
func init() {
if err := Migrations.Discover(sqlMigrations); err != nil {
logger.Named("Migrations").Fatal("Unable to load migrations.", zap.Error(err))
}
}

View File

@ -0,0 +1,213 @@
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
Detail model.MeterDetail
CoveredArea decimal.Decimal
LastTermReading *Reading
CurrentTermReading *Reading
Overall model.ConsumptionUnit
Critical model.ConsumptionUnit
Peak model.ConsumptionUnit
Flat model.ConsumptionUnit
Valley model.ConsumptionUnit
AdjustLoss model.ConsumptionUnit
PooledBasic model.ConsumptionUnit
PooledAdjust model.ConsumptionUnit
PooledLoss model.ConsumptionUnit
PooledPublic model.ConsumptionUnit
SharedPoolingProportion decimal.Decimal
Poolings []*Pooling
}
type PrimaryTenementStatistics struct {
Tenement model.Tenement
Meters []Meter
}
type TenementCharge struct {
Tenement string
Overall model.ConsumptionUnit
Critical model.ConsumptionUnit
Peak model.ConsumptionUnit
Flat model.ConsumptionUnit
Valley model.ConsumptionUnit
BasicFee decimal.Decimal
AdjustFee decimal.Decimal
LossPooled decimal.Decimal
PublicPooled decimal.Decimal
FinalCharges decimal.Decimal
Submeters []*Meter
Poolings []*Meter
}
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,133 +0,0 @@
package model
import (
"context"
"errors"
"time"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
type EndUserDetail struct {
bun.BaseModel `bun:"table:end_user_detail,alias:eud"`
CreatedAndModified `bun:"extend"`
ReportId string `bun:",pk,notnull" json:"reportId"`
ParkId string `bun:",pk,notnull" json:"parkId"`
MeterId string `bun:"meter_04kv_id,pk,notnull" json:"meterId"`
Seq int64 `bun:"type:bigint,notnull" json:"seq"`
Ratio decimal.Decimal `bun:"type:numeric,notnull" json:"ratio"`
Address *string `json:"address"`
CustomerName *string `json:"customerName"`
ContactName *string `json:"contactName"`
ContactPhone *string `json:"contactPhone"`
IsPublicMeter bool `bun:"public_meter,notnull" json:"isPublicMeter"`
LastPeriodOverall decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodOverall"`
LastPeriodCritical decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodCritical"`
LastPeriodPeak decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodPeak"`
LastPeriodFlat decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodFlat"`
LastPeriodValley decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodValley"`
CurrentPeriodOverall decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodOverall"`
CurrentPeriodCritical decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodCritical"`
CurrentPeriodPeak decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodPeak"`
CurrentPeriodFlat decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodFlat"`
CurrentPeriodValley decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodValley"`
AdjustOverall decimal.Decimal `bun:"type:numeric,notnull" json:"adjustOverall"`
AdjustCritical decimal.Decimal `bun:"type:numeric,notnull" json:"adjustCritical"`
AdjustPeak decimal.Decimal `bun:"type:numeric,notnull" json:"adjustPeak"`
AdjustFlat decimal.Decimal `bun:"type:numeric,notnull" json:"adjustFlat"`
AdjustValley decimal.Decimal `bun:"type:numeric,notnull" json:"adjustValley"`
Overall decimal.NullDecimal `bun:"type:numeric" json:"overall"`
OverallFee decimal.NullDecimal `bun:"type:numeric" json:"overallFee"`
OverallProportion decimal.Decimal `bun:"type:numeric,notnull" json:"-"`
Critical decimal.NullDecimal `bun:"type:numeric" json:"critical"`
CriticalFee decimal.NullDecimal `bun:"type:numeric" json:"criticalFee"`
Peak decimal.NullDecimal `bun:"type:numeric" json:"peak"`
PeakFee decimal.NullDecimal `bun:"type:numeric" json:"peakFee"`
Flat decimal.NullDecimal `bun:"type:numeric" json:"flat"`
FlatFee decimal.NullDecimal `bun:"type:numeric" json:"flatFee"`
Valley decimal.NullDecimal `bun:"type:numeric" json:"valley"`
ValleyFee decimal.NullDecimal `bun:"type:numeric" json:"valleyFee"`
BasicFeeDiluted decimal.NullDecimal `bun:"type:numeric" json:"basicFeeDiluted"`
AdjustFeeDiluted decimal.NullDecimal `bun:"type:numeric" json:"adjustFeeDiluted"`
LossDiluted decimal.NullDecimal `bun:"type:numeric" json:"lossDiluted"`
LossFeeDiluted decimal.NullDecimal `bun:"type:numeric" json:"lossFeeDiluted"`
FinalDiluted decimal.NullDecimal `bun:"type:numeric" json:"finalDiluted"`
FinalCharge decimal.NullDecimal `bun:"type:numeric" json:"finalCharge"`
Initialize bool `bun:"-" json:"-"`
Origin *Meter04KV `bun:"rel:belongs-to,join:park_id=park_id,join:meter_04kv_id=code" json:"-"`
Report *Report `bun:"rel:belongs-to,join:report_id=id" json:"-"`
Park *Park `bun:"rel:belongs-to,join:park_id=id" json:"-"`
}
var _ bun.BeforeAppendModelHook = (*EndUserDetail)(nil)
func (d *EndUserDetail) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
d.CreatedAt = oprTime
d.LastModifiedAt = &oprTime
case *bun.UpdateQuery:
d.LastModifiedAt = &oprTime
}
return nil
}
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.LastPeriodFlat = d.LastPeriodOverall.Sub(d.LastPeriodCritical).Sub(d.LastPeriodPeak).Sub(d.LastPeriodValley)
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"`
LastPeriodOverall decimal.Decimal `excel:"lastPeriodOverall"`
CurrentPeriodOverall decimal.Decimal `excel:"currentPeriodOverall"`
LastPeriodCritical decimal.NullDecimal `excel:"lastPeriodCritical"`
LastPeriodPeak decimal.NullDecimal `excel:"lastPeriodPeak"`
LastPeriodValley decimal.NullDecimal `excel:"lastPeriodValley"`
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"`
}
type EndUserPeriodStat struct {
CustomerName string `json:"customerName"`
Address string `json:"address"`
ParkId string `json:"parkId"`
MeterId string `bun:"meter_04kv_id" json:"meterId"`
IsPublicMeter bool `bun:"public_meter" json:"isPublicMeter"`
Kind int8 `bun:"-" json:"pvKind"`
Overall decimal.NullDecimal `json:"overall"`
Critical decimal.NullDecimal `json:"critical"`
Peak decimal.NullDecimal `json:"peak"`
Valley decimal.NullDecimal `json:"valley"`
OverallFee decimal.NullDecimal `json:"overallFee"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
PeakFee decimal.NullDecimal `json:"peakFee"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
AdjustFee decimal.NullDecimal `bun:"final_diluted" json:"adjustFee"`
AdjustProportion decimal.NullDecimal `bun:"-" json:"adjustProportion"`
}

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,48 +0,0 @@
package model
import (
"context"
"time"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
type MaintenanceFee struct {
bun.BaseModel `bun:"table:maintenance_fee,alias:m"`
CreatedAndModified `bun:"extend"`
Deleted `bun:"extend"`
Id string `bun:",pk,notnull" json:"id"`
ParkId string `bun:",notnull" json:"parkId"`
Name string `bun:",notnull" json:"name"`
Period string `bun:",notnull" json:"period"`
Fee decimal.Decimal `bun:"type:numeric,notnull" json:"fee"`
Memo *string `bun:"type:text" json:"memo"`
Enabled bool `bun:",notnull" json:"enabled"`
Park Park `bun:"rel:belongs-to,join:park_id=id"`
}
var _ bun.BeforeAppendModelHook = (*MaintenanceFee)(nil)
func (f *MaintenanceFee) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
f.CreatedAt = oprTime
f.LastModifiedAt = &oprTime
case *bun.UpdateQuery:
f.LastModifiedAt = &oprTime
}
return nil
}
type AdditionalCharge struct {
ParkId string `json:"parkId"`
Period string `json:"period"`
Fee decimal.Decimal `json:"fee"`
Price decimal.Decimal `json:"price"`
QuarterPrice decimal.Decimal `json:"quarterPrice"`
SemiAnnualPrice decimal.Decimal `json:"semiAnnualPrice"`
Enterprise UserDetailSimplified `json:"user"`
Park Park `json:"park"`
}

106
model/meter.go Normal file
View File

@ -0,0 +1,106 @@
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"`
}
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"`
}

View File

@ -1,39 +0,0 @@
package model
import (
"context"
"time"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
type Meter04KV struct {
bun.BaseModel `bun:"table:meter_04kv,alias:mt"`
CreatedAndModified `bun:"extend"`
Code string `bun:",pk,notnull" json:"code" excel:"code"`
ParkId string `bun:",pk,notnull" json:"parkId"`
Address *string `json:"address" excel:"address"`
CustomerName *string `json:"customerName" excel:"name"`
ContactName *string `json:"contactName" excel:"contact"`
ContactPhone *string `json:"contactPhone" excel:"phone"`
Ratio decimal.Decimal `bun:"type:numeric,notnull" json:"ratio" excel:"ratio"`
Seq int64 `bun:"type:bigint,notnull" json:"seq" excel:"seq"`
IsPublicMeter bool `bun:"public_meter,notnull" json:"isPublicMeter" excel:"public"`
Enabled bool `bun:",notnull" json:"enabled"`
ParkDetail *Park `bun:"rel:belongs-to,join:park_id=id" json:"-"`
}
var _ bun.BeforeAppendModelHook = (*Meter04KV)(nil)
func (m *Meter04KV) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
m.CreatedAt = oprTime
m.LastModifiedAt = &oprTime
case *bun.UpdateQuery:
m.LastModifiedAt = &oprTime
}
return nil
}

View File

@ -1,89 +1,45 @@
package model
import (
"context"
"electricity_bill_calc/types"
"time"
"github.com/jinzhu/copier"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
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 {
bun.BaseModel `bun:"table:park,alias:p"`
CreatedAndModified `bun:"extend"`
Deleted `bun:"extend"`
Id string `bun:",pk,notnull" json:"id"`
UserId string `bun:",notnull" json:"userId"`
Name string `bun:",notnull" json:"name"`
Abbr *string `json:"abbr"`
Area decimal.NullDecimal `bun:"type:numeric" json:"area"`
TenementQuantity decimal.NullDecimal `bun:"type:numeric" json:"tenement"`
Capacity decimal.NullDecimal `bun:"type:numeric" json:"capacity"`
Category int8 `bun:"type:smallint,notnull" json:"category"`
SubmeterType int8 `bun:"meter_04kv_type,type:smallint,notnull" json:"meter04kvType"`
Region *string `json:"region"`
Address *string `json:"address"`
Contact *string `json:"contact"`
Phone *string `json:"phone"`
Enabled bool `bun:",notnull" json:"enabled"`
EnterpriseIndex *User `bun:"rel:belongs-to,join:user_id=id" json:"-"`
Enterprise *UserDetail `bun:"rel:belongs-to,join:user_id=id" json:"-"`
MaintenanceFees []*MaintenanceFee `bun:"rel:has-many,join:id=park_id" json:"-"`
Meters []*Meter04KV `bun:"rel:has-many,join:id=park_id" json:"-"`
Reports []*Report `bun:"rel:has-many,join:id=park_id" json:"-"`
}
type ParkSimplified struct {
bun.BaseModel `bun:"table:park,alias:p"`
Id string `bun:",pk,notnull" json:"id"`
UserId string `bun:",notnull" json:"userId"`
Name string `bun:",notnull" json:"name"`
Abbr *string `json:"abbr"`
Id string `json:"id"`
UserId string `json:"userId"`
Name string `json:"name"`
Abbr string `json:"-"`
Area decimal.NullDecimal `json:"area"`
TenementQuantity decimal.NullDecimal `json:"tenement"`
Capacity decimal.NullDecimal `json:"capacity"`
Category int8 `bun:"type:smallint,notnull" json:"category"`
SubmeterType int8 `bun:"meter_04kv_type,type:smallint,notnull" json:"meter04kvType"`
Category int16 `json:"category"`
MeterType int16 `json:"meter04kvType" db:"meter_04kv_type"`
PricePolicy int16 `json:"pricePolicy"`
BasicPooled int16 `json:"basicDiluted"`
AdjustPooled int16 `json:"adjustDiluted"`
LossPooled int16 `json:"lossDiluted"`
PublicPooled int16 `json:"publicDiluted"`
TaxRate decimal.NullDecimal `json:"taxRate"`
Region *string `json:"region"`
Address *string `json:"address"`
Contact *string `json:"contact"`
Phone *string `json:"phone"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"createdAt"`
LastModifiedAt time.Time `json:"lastModifiedAt"`
DeletedAt *time.Time `json:"deletedAt"`
}
type Parks struct {
Park
NormAuthorizedLossRate float64 `json:"norm_authorized_loss_rate"`
}
type ParkPeriodStatistics struct {
Id string `bun:"park__id,notnull" json:"id"`
Name string `bun:"park__name,notnull" json:"name"`
Period *Date `bun:"type:date" json:"period"`
}
func FromPark(park Park) ParkSimplified {
dest := ParkSimplified{}
copier.Copy(&dest, park)
return dest
}
var _ bun.BeforeAppendModelHook = (*Park)(nil)
func (p *Park) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
p.CreatedAt = oprTime
p.LastModifiedAt = &oprTime
case *bun.UpdateQuery:
p.LastModifiedAt = &oprTime
}
return nil
Id string `json:"id"`
Name string `json:"name"`
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,116 +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 ConsumptionOverallPart 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"`
AuthorizeQuantity decimal.Decimal `json:"authorizeQuantity"`
AuthorizeConsumptionFee decimal.Decimal `json:"authorizeConsumptionFee"`
}
type OtherShouldCollectionPart struct {
LossFee decimal.NullDecimal `json:"lossFee"`
BasicFees decimal.Decimal `json:"basicFees"`
}
type MaintenancePart struct {
BasicFees decimal.Decimal `json:"basicFees"`
LossFee decimal.Decimal `json:"lossFee"`
AdjustFee decimal.Decimal `json:"adjustFee"`
LossProportion decimal.Decimal `json:"lossProportion"`
AdjustProportion decimal.Decimal `json:"adjustProportion"`
AdjustPrice decimal.Decimal `json:"adjustPrice"`
}
type EndUserSummary struct {
CustomerName *string `json:"customerName"`
Address *string `json:"address"`
MeterId string `json:"meterId"`
IsPublicMeter bool `json:"isPublicMeter"`
Overall decimal.Decimal `json:"overall"`
OverallPrice decimal.Decimal `json:"overallPrice"`
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"`
Loss decimal.Decimal `json:"loss"`
LossFee decimal.Decimal `json:"lossFee"`
}
type Publicity struct {
Report Report `json:"index"`
User UserDetail `json:"enterprise"`
Park Park `json:"park"`
Paid PaidPart `json:"paid"`
EndUser ConsumptionOverallPart `json:"endUserSum"`
Loss LossPart `json:"loss"`
PublicConsumptionOverall ConsumptionOverallPart `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,11 +1,8 @@
package model
import "github.com/uptrace/bun"
type Region struct {
bun.BaseModel `bun:"table:region,alias:r"`
Code string `bun:",pk,notnull" json:"code"`
Name string `bun:",notnull" json:"name"`
Level int `bun:",notnull" json:"level"`
Parent string `bun:",notnull" json:"parent"`
Code string `json:"code"`
Name string `json:"name"`
Level int32 `json:"level"`
Parent string `json:"parent"`
}

View File

@ -1,99 +1,139 @@
package model
import (
"context"
"time"
"electricity_bill_calc/types"
"github.com/uptrace/bun"
"github.com/shopspring/decimal"
)
const (
REPORT_NOT_WITHDRAW int8 = iota
REPORT_WITHDRAW_APPLIED
REPORT_WITHDRAW_DENIED
REPORT_WITHDRAW_GRANTED
)
type Report struct {
bun.BaseModel `bun:"table:report,alias:r"`
CreatedAndModified `bun:"extend"`
Id string `bun:",pk,notnull" json:"id"`
ParkId string `bun:",notnull" json:"parkId"`
Period time.Time `bun:"type:date,notnull" json:"period" time_format:"simple_date" time_location:"shanghai"`
Category int8 `bun:"type:smallint,notnull" json:"category"`
SubmeterType int8 `bun:"meter_04kv_type,type:smallint,notnull" json:"meter04kvType"`
StepState Steps `bun:"type:jsonb,notnull" json:"stepState"`
Published bool `bun:",notnull" json:"published"`
PublishedAt *time.Time `bun:"type:timestamptz,nullzero" json:"publishedAt" time_format:"simple_datetime" time_location:"shanghai"`
Withdraw int8 `bun:"type:smallint,notnull" json:"withdraw"`
LastWithdrawAppliedAt *time.Time `bun:"type:timestamptz,nullzero" json:"lastWithdrawAppliedAt" time_format:"simple_datetime" time_location:"shanghai"`
LastWithdrawAuditAt *time.Time `bun:"type:timestamptz,nullzero" json:"lastWithdrawAuditAt" time_format:"simple_datetime" time_location:"shanghai"`
Park *Park `bun:"rel:belongs-to,join:park_id=id" json:"-"`
Summary *ReportSummary `bun:"rel:has-one,join:id=report_id" json:"-"`
WillDilutedFees []*WillDilutedFee `bun:"rel:has-many,join:id=report_id" json:"-"`
EndUsers []*EndUserDetail `bun:"rel:has-many,join:id=report_id,join:park_id=park_id" json:"-"`
type ReportIndex struct {
Id string `json:"id"`
Park string `json:"parkId" db:"park_id"`
Period types.DateRange `json:"period"`
Category int16 `json:"category"`
MeterType int16 `json:"meter04kvType" db:"meter_04kv_type"`
PricePolicy int16 `json:"pricePolicy"`
BasisPooled int16 `json:"basisPooled"`
AdjustPooled int16 `json:"adjustPooled"`
LossPooled int16 `json:"lossPooled"`
PublicPooled int16 `json:"publicPooled"`
Published bool `json:"published"`
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 {
Summary bool `json:"summary"`
WillDiluted bool `json:"willDiluted"`
Submeter bool `json:"submeter"`
Calculate bool `json:"calculate"`
Preview bool `json:"preview"`
Publish bool `json:"publish"`
type ReportSummary struct {
ReportId string `json:"reportId" db:"report_id"`
OverallArea decimal.Decimal `json:"overallArea" db:"overall_area"`
Overall ConsumptionUnit `json:"overall"`
ConsumptionFee decimal.NullDecimal `json:"consumptionFee" db:"consumption_fee"`
Critical ConsumptionUnit `json:"critical"`
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 NewSteps() Steps {
return Steps{
Summary: false,
WillDiluted: false,
Submeter: false,
Calculate: false,
Preview: false,
Publish: false,
func (rs ReportSummary) GetConsumptionFee() decimal.Decimal {
if !rs.ConsumptionFee.Valid {
return rs.Overall.Fee.Sub(rs.BasicFee).Sub(rs.AdjustFee)
}
return rs.ConsumptionFee.Decimal
}
type ParkNewestReport struct {
Park Park `bun:"extends" json:"park"`
Report *Report `bun:"extends" json:"report"`
type ReportPublicConsumption struct {
ReportId string `json:"reportId" db:"report_id"`
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 (p *ParkNewestReport) AfterLoad() {
if p.Report != nil && len(p.Report.Id) == 0 {
p.Report = nil
}
type ReportDetailedPublicConsumption struct {
MeterDetail
ReportPublicConsumption
}
type ReportIndexSimplified struct {
bun.BaseModel `bun:"table:report,alias:r"`
Id string `bun:",pk,notnull" json:"id"`
ParkId string `bun:",notnull" json:"parkId"`
Period Date `bun:"type:date,notnull" json:"period"`
StepState Steps `bun:"type:jsonb,notnull" json:"stepState"`
Published bool `bun:",notnull" json:"published"`
PublishedAt *time.Time `bun:"type:timestampz" json:"publishedAt" time_format:"simple_datetime" time_location:"shanghai"`
Withdraw int8 `bun:"type:smallint,notnull" json:"withdraw"`
LastWithdrawAppliedAt *time.Time `bun:"type:timestamptz" json:"lastWithdrawAppliedAt" time_format:"simple_datetime" time_location:"shanghai"`
LastWithdrawAuditAt *time.Time `bun:"type:timestamptz" json:"lastWithdrawAuditAt" time_format:"simple_datetime" time_location:"shanghai"`
type ReportPooledConsumption struct {
ReportId string `json:"reportId" db:"report_id"`
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 JoinedReportForWithdraw struct {
Report Report `bun:"extends" json:"report"`
Park ParkSimplified `bun:"extends" json:"park"`
User UserDetailSimplified `bun:"extends" json:"user"`
type ReportDetailedPooledConsumption struct {
MeterDetail
ReportPooledConsumption
PublicPooled int16 `json:"publicPooled"`
}
var _ bun.BeforeAppendModelHook = (*Report)(nil)
func (p *Report) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
p.CreatedAt = oprTime
p.LastModifiedAt = &oprTime
case *bun.UpdateQuery:
p.LastModifiedAt = &oprTime
}
return nil
type ReportDetailNestedMeterConsumption struct {
Meter MeterDetail `json:"meter"`
Consumption NestedMeter `json:"consumption"`
}
type ReportTenement struct {
ReportId string `json:"reportId" db:"report_id"`
Tenement string `json:"tenementId" db:"tenement_id"`
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"`
}
type ReportTask struct {
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,119 +0,0 @@
package model
import (
"errors"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
type ReportSummary struct {
bun.BaseModel `bun:"table:report_summary,alias:rs"`
ReportId string `bun:",pk,notnull" json:"-"`
Overall decimal.Decimal `bun:"type:numeric,notnull" json:"overall"`
OverallFee decimal.Decimal `bun:"type:numeric,notnull" json:"overallFee"`
ConsumptionFee decimal.NullDecimal `bun:"type:numeric" json:"consumptionFee"`
OverallPrice decimal.NullDecimal `bun:"type:numeric" json:"overallPrice"`
Critical decimal.Decimal `bun:"type:numeric,notnull" json:"critical"`
CriticalFee decimal.Decimal `bun:"type:numeric,notnull" json:"criticalFee"`
CriticalPrice decimal.NullDecimal `bun:"type:numeric" json:"criticalPrice"`
Peak decimal.Decimal `bun:"type:numeric,notnull" json:"peak"`
PeakFee decimal.Decimal `bun:"type:numeric,notnull" json:"peakFee"`
PeakPrice decimal.NullDecimal `bun:"type:numeric" json:"peakPrice"`
Flat decimal.Decimal `bun:"type:numeric,notnull" json:"flat"`
FlatFee decimal.Decimal `bun:"type:numeric,notnull" json:"flatFee"`
FlatPrice decimal.NullDecimal `bun:"type:numeric" json:"flatPrice"`
Valley decimal.Decimal `bun:"type:numeric,notnull" json:"valley"`
ValleyFee decimal.Decimal `bun:"type:numeric,notnull" json:"valleyFee"`
ValleyPrice decimal.NullDecimal `bun:"type:numeric" json:"valleyPrice"`
Loss decimal.NullDecimal `bun:"type:numeric" json:"loss"`
LossFee decimal.NullDecimal `bun:"type:numeric" json:"lossFee"`
LossProportion decimal.NullDecimal `bun:"type:numeric" json:"lossProportion"`
AuthorizeLoss decimal.NullDecimal `bun:"type:numeric" json:"authorizeLoss"`
AuthorizeLossFee decimal.NullDecimal `bun:"type:numeric" json:"authorizeLossFee"`
AuthorizeLossProportion decimal.NullDecimal `bun:"type:numeric" json:"authorizeLossProportion"`
BasicFee decimal.Decimal `bun:"type:numeric,notnull" json:"basicFee"`
BasicDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"basicDilutedPrice"`
AdjustFee decimal.Decimal `bun:"type:numeric,notnull" json:"adjustFee"`
AdjustDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"adjustDilutedPrice"`
MaintenanceDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"maintencanceDilutedPrice"`
LossDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"lossDilutedPrice"`
PublicConsumptionDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"publicConsumptionDilutedPrice"`
MaintenanceOverall decimal.NullDecimal `bun:"type:numeric" json:"maintenanceOverall"`
FinalDilutedOverall decimal.NullDecimal `bun:"type:numeric" json:"finalDilutedOverall"`
Customers Consumptions `bun:"type:jsonb" json:"customers"`
Publics Consumptions `bun:"type:jsonb" json:"publics"`
}
type Consumptions struct {
Consumption decimal.NullDecimal `json:"consumption"`
ConsumptionFee decimal.NullDecimal `json:"fee"`
Critical decimal.NullDecimal `json:"critical"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
Peak decimal.NullDecimal `json:"peak"`
PeakFee decimal.NullDecimal `json:"peakFee"`
Flat decimal.NullDecimal `json:"flat"`
FlatFee decimal.NullDecimal `json:"flatFee"`
Valley decimal.NullDecimal `json:"valley"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
Proportion decimal.NullDecimal `json:"proportion"`
}
func NewConsumptions() Consumptions {
return Consumptions{
Consumption: decimal.NewNullDecimal(decimal.Zero),
ConsumptionFee: decimal.NewNullDecimal(decimal.Zero),
Critical: decimal.NewNullDecimal(decimal.Zero),
Peak: decimal.NewNullDecimal(decimal.Zero),
PeakFee: decimal.NewNullDecimal(decimal.Zero),
Flat: decimal.NewNullDecimal(decimal.Zero),
CriticalFee: decimal.NewNullDecimal(decimal.Zero),
FlatFee: decimal.NewNullDecimal(decimal.Zero),
Valley: decimal.NewNullDecimal(decimal.Zero),
ValleyFee: decimal.NewNullDecimal(decimal.Zero),
Proportion: decimal.NewNullDecimal(decimal.Zero),
}
}
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.ConsumptionFee.Decimal.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 {
Uid string `json:"uid"`
Name string `json:"name"`
Type int8 `json:"type"`
Type int16 `json:"type"`
Token string `json:"token"`
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 `bun:"type:timestamptz,notnull" json:"createdAt" time_format:"simple_datetime" time_location:"shanghai"`
}
type CreatedWithUser struct {
Created `bun:"extend"`
CreatedBy *string `json:"createdBy"`
}
type Deleted struct {
DeletedAt *time.Time `bun:"type:timestamptz,soft_delete,nullzero" json:"deletedAt" time_format:"simple_datetime" time_location:"shanghai"`
}
type DeletedWithUser struct {
Deleted `bun:"extend"`
DeletedBy *string `json:"deletedBy"`
}
type CreatedAndModified struct {
Created `bun:"extend"`
LastModifiedAt *time.Time `bun:"type:timestamptz,nullzero" json:"lastModifiedAt" time_format:"simple_datetime" time_location:"shanghai"`
}
type CreatedAndModifiedWithUser struct {
CreatedAndModified `bun:"extend"`
CreatedBy *string `json:"createdBy"`
LastModifiedBy *string `json:"lastModifiedBy"`
}

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:"-"`
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,118 +0,0 @@
package model
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"time"
)
type Date struct {
time.Time
}
func NewDate(t time.Time) Date {
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
panic(err)
}
t = t.In(loc)
return Date{
Time: time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc),
}
}
func NewEmptyDate() Date {
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
panic(err)
}
return Date{
Time: time.Time{}.In(loc),
}
}
func ParseDate(t string) (Date, error) {
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
return NewEmptyDate(), fmt.Errorf("unable to load time zone, %w", err)
}
d, err := time.ParseInLocation("2006-01-02", t, loc)
if err != nil {
return NewEmptyDate(), fmt.Errorf("unable to parse given time, %w", err)
}
return Date{
Time: d,
}, nil
}
func (d Date) IsEmpty() bool {
return d.Time.IsZero()
}
func (d Date) Format(fmt string) string {
return d.Time.Format(fmt)
}
func (d Date) ToString() string {
return d.Time.Format("2006-01-02")
}
var _ driver.Valuer = (*Date)(nil)
func (d Date) Value() (driver.Value, error) {
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
panic(err)
}
return d.In(loc).Format("2006-01-02"), nil
}
var _ sql.Scanner = (*Date)(nil)
// Scan scans the time parsing it if necessary using timeFormat.
func (d *Date) Scan(src interface{}) (err error) {
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
panic(err)
}
switch src := src.(type) {
case time.Time:
*d = NewDate(src)
return nil
case string:
d.Time, err = time.ParseInLocation("2006-01-02", src, loc)
return err
case []byte:
d.Time, err = time.ParseInLocation("2006-01-02", string(src), loc)
return err
case nil:
d.Time = time.Time{}
return nil
default:
return fmt.Errorf("unsupported data type: %T", src)
}
}
var _ json.Marshaler = (*Date)(nil)
func (d Date) MarshalJSON() ([]byte, error) {
return json.Marshal(d.Time.Format("2006-01-02"))
}
var _ json.Unmarshaler = (*Date)(nil)
func (d *Date) UnmarshalJSON(raw []byte) error {
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
return fmt.Errorf("unable to load time zone, %w", err)
}
var s string
err = json.Unmarshal(raw, &s)
if err != nil {
return fmt.Errorf("unable to unmarshal value, %w", err)
}
d.Time, err = time.ParseInLocation("2006-01-02", s, loc)
return err
}

View File

@ -1,48 +1,114 @@
package model
import (
"context"
"electricity_bill_calc/types"
"time"
"github.com/uptrace/bun"
"github.com/shopspring/decimal"
)
const (
USER_TYPE_ENT int8 = iota
USER_TYPE_ENT int16 = iota
USER_TYPE_SUP
USER_TYPE_OPS
)
type User struct {
bun.BaseModel `bun:"table:user,alias:u"`
Created `bun:"extend"`
Id string `bun:",pk,notnull" json:"id"`
Username string `bun:",notnull" json:"username"`
Password string `bun:",notnull" json:"-"`
ResetNeeded bool `bun:",notnull" json:"resetNeeded"`
Type int8 `bun:"type:smallint,notnull" json:"type"`
Enabled bool `bun:",notnull" json:"enabled"`
Detail *UserDetail `bun:"rel:has-one,join:id=id" json:"-"`
Charges []*UserCharge `bun:"rel:has-many,join:id=user_id" json:"-"`
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"`
}
type UserWithCredentials struct {
bun.BaseModel `bun:"table:user,alias:u"`
Created `bun:"extend"`
Id string `bun:",pk,notnull" json:"id"`
Username string `bun:",notnull" json:"username"`
Password string `bun:",notnull" json:"credential"`
ResetNeeded bool `bun:",notnull" json:"resetNeeded"`
Type int8 `bun:"type:smallint,notnull" json:"type"`
Enabled bool `bun:",notnull" json:"enabled"`
}
var _ bun.BeforeAppendModelHook = (*User)(nil)
func (u *User) BeforeAppendModel(ctx context.Context, query bun.Query) error {
switch query.(type) {
case *bun.InsertQuery:
u.CreatedAt = time.Now()
func (m ManagementAccountCreationForm) IntoUser() *User {
return &User{
Id: *m.Id,
Username: m.Username,
Password: "",
ResetNeeded: false,
UserType: m.Type,
Enabled: m.Enabled,
CreatedAt: nil,
}
return 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 {
Id string `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
ResetNeeded bool `json:"resetNeeded"`
UserType int16 `db:"type"`
Enabled bool `json:"enabled"`
CreatedAt *time.Time `json:"createdAt"`
}
type UserDetail struct {
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 UserWithDetail struct {
Id string `json:"id"`
Username string `json:"username"`
ResetNeeded bool `json:"resetNeeded"`
UserType int16 `db:"type" json:"type"`
Enabled bool `json:"enabled"`
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"`
}

View File

@ -1,43 +0,0 @@
package model
import (
"context"
"time"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
type UserCharge struct {
bun.BaseModel `bun:"table:user_charge,alias:c"`
Created `bun:"extend"`
Seq int64 `bun:"type:bigint,pk,notnull,autoincrement" json:"seq"`
UserId string `bun:",notnull" json:"userId"`
Fee decimal.NullDecimal `bun:"type:numeric" json:"fee"`
Discount decimal.NullDecimal `bun:"type:numeric" json:"discount"`
Amount decimal.NullDecimal `bun:"type:numeric" json:"amount"`
ChargeTo Date `bun:"type:date,notnull" json:"chargeTo"`
Settled bool `bun:",notnull" json:"settled"`
SettledAt *time.Time `bun:"type:timestamptz" json:"settledAt" time_format:"simple_datetime" time_location:"shanghai"`
Cancelled bool `bun:",notnull" json:"cancelled"`
CancelledAt *time.Time `bun:"type:timestamptz" json:"cancelledAt" time_format:"simple_datetime" time_location:"shanghai"`
Refunded bool `bun:",notnull" json:"refunded"`
RefundedAt *time.Time `bun:"type:timestamptz" json:"refundedAt" time_format:"simple_datetime" time_location:"shanghai"`
Detail *UserDetail `bun:"rel:belongs-to,join:user_id=id" json:"-"`
}
type ChargeWithName struct {
UserDetail `bun:"extend"`
UserCharge `bun:"extend"`
}
var _ bun.BeforeAppendModelHook = (*UserCharge)(nil)
func (uc *UserCharge) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
uc.CreatedAt = oprTime
}
return nil
}

View File

@ -1,69 +0,0 @@
package model
import (
"context"
"time"
"github.com/jinzhu/copier"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
type UserDetail struct {
bun.BaseModel `bun:"table:user_detail,alias:d"`
CreatedAndModifiedWithUser `bun:"extend"`
DeletedWithUser `bun:"extend"`
Id string `bun:",pk,notnull" json:"-"`
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 `bun:"type:numeric,notnull" json:"unitServiceFee"`
ServiceExpiration Date `bun:"type:date,notnull" json:"serviceExpiration"`
}
type JoinedUserDetail struct {
UserDetail `bun:"extend"`
Id string `json:"id"`
Username string `json:"username"`
Type int8 `json:"type"`
Enabled bool `json:"enabled"`
}
type FullJoinedUserDetail struct {
UserDetail `bun:"extend"`
User `bun:"extend"`
}
type UserDetailSimplified struct {
bun.BaseModel `bun:"table:user_detail,alias:d"`
Id string `bun:",pk,notnull" 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"`
}
func FromUserDetail(user UserDetail) UserDetailSimplified {
dest := UserDetailSimplified{}
copier.Copy(&dest, user)
return dest
}
var _ bun.BeforeAppendModelHook = (*UserDetail)(nil)
func (d *UserDetail) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
d.CreatedAt = oprTime
d.LastModifiedAt = &oprTime
case *bun.UpdateQuery:
d.LastModifiedAt = &oprTime
}
return nil
}

View File

@ -1,35 +0,0 @@
package model
import (
"context"
"time"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
type WillDilutedFee struct {
bun.BaseModel `bun:"table:will_diluted_fee,alias:w"`
CreatedAndModified `bun:"extend"`
Id string `bun:",pk,notnull" json:"diluteId"`
ReportId string `bun:",notnull" json:"reportId"`
SourceId *string `json:"sourceId"`
Name string `bun:",notnull" json:"name"`
Fee decimal.Decimal `bun:"type:numeric,notnull" json:"fee"`
Memo *string `bun:"type:text" json:"memo"`
Origin *MaintenanceFee `bun:"rel:belongs-to,join:source_id=id"`
}
var _ bun.BeforeAppendModelHook = (*WillDilutedFee)(nil)
func (d *WillDilutedFee) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
d.CreatedAt = oprTime
d.LastModifiedAt = &oprTime
case *bun.UpdateQuery:
d.LastModifiedAt = &oprTime
}
return nil
}

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"`
}

241
repository/calculate.go Normal file
View File

@ -0,0 +1,241 @@
package repository
import (
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/types"
"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"),
}
// 获取当前正在等待计算的核算任务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").IsNull(),
goqu.I("associated_at").Lte(associatedBefore),
)).
Where(goqu.And(
goqu.I("associated_at").IsNull(),
goqu.I("associated_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
}
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.I("mr.read_at::date <@ 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.I(" mr.read_at::date <= 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.I("t.moved_in_at <= upper(r.period)"),
).ToSQL()
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
}

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
}

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
}

991
repository/meter.go Normal file
View File

@ -0,0 +1,991 @@
package repository
import (
"context"
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"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"
"electricity_bill_calc/vo"
"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 _MeterRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var MeterRepository = _MeterRepository{
log: logger.Named("Repository", "Meter"),
ds: goqu.Dialect("postgres"),
}
// 获取指定园区中所有的表计信息
func (mr _MeterRepository) AllMeters(pid string) ([]*model.MeterDetail, error) {
mr.log.Info("列出指定园区中的所有表计", zap.String("park id", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var meters []*model.MeterDetail
metersSql, metersArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.detachedAt").IsNull(),
).
Order(goqu.I("m.seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &meters, metersSql, metersArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 列出指定园区下的所有表计信息,包含已经拆除的表计
func (mr _MeterRepository) AllUsedMeters(pid string) ([]*model.MeterDetail, error) {
mr.log.Info("列出指定园区中的所有使用过的表计", zap.String("park id", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var meters []*model.MeterDetail
metersSql, metersArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
).
Order(goqu.I("m.seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &meters, metersSql, metersArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 列出指定核算报表中所使用的所有表计,包含已经拆除的表计
func (mr _MeterRepository) AllUsedMetersInReport(rid string) ([]*model.MeterDetail, error) {
mr.log.Info("列出指定核算报表中所使用的所有表计", zap.String("report id", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var meters []*model.MeterDetail
metersSql, metersArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Join(goqu.T("report").As("r"), goqu.On(goqu.I("m.park_id").Eq(goqu.I("r.park_id")))).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("m.enabled").Eq(true),
goqu.L("m.attached_at::date < upper(r.period)"),
goqu.Or(
goqu.I("m.detached_at").IsNull(),
goqu.L("m.detached_at::date >= lower(r.period)"),
),
).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Order(goqu.I("m.seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &meters, metersSql, metersArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 分页列出指定园区下的表计信息
func (mr _MeterRepository) MetersIn(pid string, page uint, keyword *string) ([]*model.MeterDetail, int64, error) {
mr.log.Info("分页列出指定园区下的表计信息", zap.String("park id", pid), zap.Uint("page", page), zap.String("keyword", tools.DefaultTo(keyword, "")))
ctx, cancel := global.TimeoutContext()
defer cancel()
meterQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.detached_at").IsNull(),
)
countQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
Select(goqu.COUNT("*")).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.detached_at").IsNull(),
)
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
meterQuery = meterQuery.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
meterQuery = meterQuery.Order(goqu.I("m.seq").Asc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
var (
meters []*model.MeterDetail
total int64
)
if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), 0, err
}
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
mr.log.Error("查询表计数量失败", zap.Error(err))
return make([]*model.MeterDetail, 0), 0, err
}
return meters, total, nil
}
// 列出指定园区中指定列表中所有表计的详细信息,将忽略所有表计的当前状态
func (mr _MeterRepository) ListMetersByIDs(pid string, ids []string) ([]*model.MeterDetail, error) {
mr.log.Info("列出指定园区中指定列表中所有表计的详细信息", zap.String("park id", pid), zap.Strings("meter ids", ids))
if len(ids) == 0 {
return make([]*model.MeterDetail, 0), nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var meters []*model.MeterDetail
metersSql, metersArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.code").In(ids),
).
Order(goqu.I("m.seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &meters, metersSql, metersArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 获取指定表计的详细信息
func (mr _MeterRepository) FetchMeterDetail(pid, code string) (*model.MeterDetail, error) {
mr.log.Info("获取指定表计的详细信息", zap.String("park id", pid), zap.String("meter code", code))
ctx, cancel := global.TimeoutContext()
defer cancel()
var meter model.MeterDetail
meterSql, meterArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.code").Eq(code),
).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &meter, meterSql, meterArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return nil, err
}
return &meter, nil
}
// 创建一条新的表计信息
func (mr _MeterRepository) CreateMeter(tx pgx.Tx, ctx context.Context, pid string, meter vo.MeterCreationForm) (bool, error) {
mr.log.Info("创建一条新的表计信息", zap.String("park id", pid), zap.String("meter code", meter.Code))
timeNow := types.Now()
meterSql, meterArgs, _ := mr.ds.
Insert(goqu.T("meter_04kv")).
Cols(
"park_id", "code", "address", "ratio", "seq", "meter_type", "building", "on_floor", "area", "enabled",
"attached_at", "created_at", "last_modified_at",
).
Vals(
goqu.Vals{pid, meter.Code, meter.Address, meter.Ratio, meter.Seq, meter.MeterType, meter.Building, meter.OnFloor, meter.Area, meter.Enabled,
timeNow, timeNow, timeNow,
},
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, meterSql, meterArgs...)
if err != nil {
mr.log.Error("创建表计信息失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 创建或者更新一条表计的信息
func (mr _MeterRepository) CreateOrUpdateMeter(tx pgx.Tx, ctx context.Context, pid string, meter vo.MeterCreationForm) (bool, error) {
mr.log.Info("创建或者更新一条表计的信息", zap.String("park id", pid), zap.String("meter code", meter.Code))
timeNow := types.Now()
meterSql, meterArgs, _ := mr.ds.
Insert(goqu.T("meter_04kv")).
Cols(
"park_id", "code", "address", "ratio", "seq", "meter_type", "building", "on_floor", "area", "enabled",
"attached_at", "created_at", "last_modified_at",
).
Vals(
goqu.Vals{pid, meter.Code, meter.Address, meter.Ratio, meter.Seq, meter.MeterType, meter.Building, meter.OnFloor, meter.Area, meter.Enabled,
timeNow, timeNow, timeNow,
},
).
OnConflict(
goqu.DoUpdate("code, park_id",
goqu.Record{
"address": goqu.I("excluded.address"),
"seq": goqu.I("excluded.seq"),
"ratio": goqu.I("excluded.ratio"),
"meter_type": goqu.I("excluded.meter_type"),
"building": goqu.I("excluded.building"),
"on_floor": goqu.I("excluded.on_floor"),
"area": goqu.I("excluded.area"),
"last_modified_at": goqu.I("excluded.last_modified_at"),
}),
).
Prepared(true).ToSQL()
res, err := tx.Exec(ctx, meterSql, meterArgs...)
if err != nil {
mr.log.Error("创建或者更新表计信息失败", zap.Error(err))
return false, err
}
return res.RowsAffected() > 0, nil
}
// 记录一条表计的抄表信息
func (mr _MeterRepository) RecordReading(tx pgx.Tx, ctx context.Context, pid, code string, meterType int16, ratio decimal.Decimal, reading *vo.MeterReadingForm) (bool, error) {
mr.log.Info("记录一条表计的抄表信息", zap.String("park id", pid), zap.String("meter code", code))
readAt := tools.DefaultTo(reading.ReadAt, types.Now())
readingSql, readingArgs, _ := mr.ds.
Insert(goqu.T("meter_reading")).
Cols(
"park_id", "meter_id", "read_at", "meter_type", "ratio", "overall", "critical", "peak", "flat", "valley",
).
Vals(
goqu.Vals{pid, code, readAt, meterType, ratio, reading.Overall, reading.Critical, reading.Peak, reading.Flat, reading.Valley},
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, readingSql, readingArgs...)
if err != nil {
mr.log.Error("记录表计抄表信息失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 更新一条表计的详细信息
func (mr _MeterRepository) UpdateMeter(tx pgx.Tx, ctx context.Context, pid, code string, detail *vo.MeterModificationForm) (bool, error) {
mr.log.Info("更新一条表计的详细信息", zap.String("park id", pid), zap.String("meter code", code))
timeNow := types.Now()
meterSql, meterArgs, _ := mr.ds.
Update(goqu.T("meter_04kv")).
Set(
goqu.Record{
"address": detail.Address,
"seq": detail.Seq,
"ratio": detail.Ratio,
"enabled": detail.Enabled,
"meter_type": detail.MeterType,
"building": detail.Building,
"on_floor": detail.OnFloor,
"area": detail.Area,
"last_modified_at": timeNow,
},
).
Where(
goqu.I("park_id").Eq(pid),
goqu.I("code").Eq(code),
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, meterSql, meterArgs...)
if err != nil {
mr.log.Error("更新表计信息失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 列出指定园区中已经存在的表计编号,无论该表计是否已经不再使用。
func (mr _MeterRepository) ListMeterCodes(pid string) ([]string, error) {
mr.log.Info("列出指定园区中已经存在的表计编号", zap.String("park id", pid))
cacheConditions := []string{pid}
if codes, err := cache.RetrieveSearch[[]string]("meter_codes", cacheConditions...); err == nil {
mr.log.Info("从缓存中获取到了指定园区中的表计编号", zap.Int("count", len(*codes)))
return *codes, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var codes []string
codesSql, codesArgs, _ := mr.ds.
From(goqu.T("meter_04kv")).
Select("code").
Where(
goqu.I("park_id").Eq(pid),
).
Order(goqu.I("seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &codes, codesSql, codesArgs...); err != nil {
mr.log.Error("查询表计编号失败", zap.Error(err))
return make([]string, 0), err
}
return codes, nil
}
// 解除指定园区中指定表计的使用
func (mr _MeterRepository) DetachMeter(tx pgx.Tx, ctx context.Context, pid, code string) (bool, error) {
mr.log.Info("解除指定园区中指定表计的使用", zap.String("park id", pid), zap.String("meter code", code))
timeNow := types.Now()
meterSql, meterArgs, _ := mr.ds.
Update(goqu.T("meter_04kv")).
Set(
goqu.Record{
"detached_at": timeNow,
"last_modified_at": timeNow,
},
).
Where(
goqu.I("park_id").Eq(pid),
goqu.I("code").Eq(code),
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, meterSql, meterArgs...)
if err != nil {
mr.log.Error("解除表计使用失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 将商户表计绑定到公摊表计上
func (mr _MeterRepository) BindMeter(tx pgx.Tx, ctx context.Context, pid, masterMeter, slaveMeter string) (bool, error) {
mr.log.Info("将商户表计绑定到公摊表计上", zap.String("master meter code", masterMeter), zap.String("slave meter code", slaveMeter))
masterDetail, err := mr.FetchMeterDetail(pid, masterMeter)
if err != nil {
mr.log.Error("查询公摊表计信息失败", zap.Error(err))
return false, err
}
if masterDetail.MeterType != model.METER_INSTALLATION_POOLING {
mr.log.Error("给定的公摊表计不是公摊表计", zap.Error(err))
return false, fmt.Errorf("给定的公摊表计不是公摊表计")
}
slaveDetail, err := mr.FetchMeterDetail(pid, slaveMeter)
if err != nil {
mr.log.Error("查询商户表计信息失败", zap.Error(err))
return false, err
}
if slaveDetail.MeterType != model.METER_INSTALLATION_TENEMENT {
mr.log.Error("给定的商户表计不是商户表计", zap.Error(err))
return false, fmt.Errorf("给定的商户表计不是商户表计")
}
timeNow := types.Now()
serial.StringSerialRequestChan <- 1
code := serial.Prefix("PB", <-serial.StringSerialResponseChan)
relationSql, relationArgs, _ := mr.ds.
Insert(goqu.T("meter_relations")).
Cols(
"id", "park_id", "master_meter_id", "slave_meter_id", "established_at",
).
Vals(
goqu.Vals{
code,
pid,
masterMeter,
slaveMeter,
timeNow,
},
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, relationSql, relationArgs...)
if err != nil {
mr.log.Error("绑定表计关系失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 解除两个表计之间的关联
func (mr _MeterRepository) UnbindMeter(tx pgx.Tx, ctx context.Context, pid, masterMeter, slaveMeter string) (bool, error) {
mr.log.Info("解除两个表计之间的关联", zap.String("master meter code", masterMeter), zap.String("slave meter code", slaveMeter))
relationSql, relationArgs, _ := mr.ds.
Update(goqu.T("meter_relations")).
Set(
goqu.Record{
"revoked_at": types.Now(),
},
).
Where(
goqu.I("park_id").Eq(pid),
goqu.I("master_meter_id").Eq(masterMeter),
goqu.I("slave_meter_id").Eq(slaveMeter),
goqu.I("revoked_at").IsNull(),
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, relationSql, relationArgs...)
if err != nil {
mr.log.Error("解除表计关系失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 列出指定公摊表计的所有关联表计关系
func (mr _MeterRepository) ListPooledMeterRelations(pid, code string) ([]*model.MeterRelation, error) {
mr.log.Info("列出指定公摊表计的所有关联表计关系", zap.String("park id", pid), zap.String("meter code", code))
ctx, cancel := global.TimeoutContext()
defer cancel()
var relations []*model.MeterRelation
relationsSql, relationsArgs, _ := mr.ds.
From(goqu.T("meter_relations").As("r")).
Select("r.*").
Where(
goqu.I("r.park_id").Eq(pid),
goqu.I("r.master_meter_id").Eq(code),
goqu.I("r.revoked_at").IsNull(),
).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &relations, relationsSql, relationsArgs...); err != nil {
mr.log.Error("查询表计关系失败", zap.Error(err))
return make([]*model.MeterRelation, 0), err
}
return relations, nil
}
// 列出指定公摊表计列表所包含的全部关联表计关系
func (mr _MeterRepository) ListPooledMeterRelationsByCodes(pid string, codes []string) ([]*model.MeterRelation, error) {
mr.log.Info("列出指定公摊表计列表所包含的全部关联表计关系", zap.String("park id", pid), zap.Strings("meter codes", codes))
ctx, cancel := global.TimeoutContext()
defer cancel()
var relations []*model.MeterRelation
relationsSql, relationsArgs, _ := mr.ds.
From(goqu.T("meter_relations").As("r")).
Select("r.*").
Where(
goqu.I("r.park_id").Eq(pid),
goqu.I("r.master_meter_id").In(codes),
goqu.I("r.revoked_at").IsNull(),
).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &relations, relationsSql, relationsArgs...); err != nil {
mr.log.Error("查询表计关系失败", zap.Error(err))
return make([]*model.MeterRelation, 0), err
}
return relations, nil
}
// 列出指定商户表计、园区表计与公摊表计之间的关联关系
func (mr _MeterRepository) ListMeterRelations(pid, code string) ([]*model.MeterRelation, error) {
mr.log.Info("列出指定商户表计、园区表计与公摊表计之间的关联关系", zap.String("park id", pid), zap.String("meter code", code))
ctx, cancel := global.TimeoutContext()
defer cancel()
var relations []*model.MeterRelation
relationsSql, relationsArgs, _ := mr.ds.
From(goqu.T("meter_relations")).
Select("*").
Where(
goqu.I("r.park_id").Eq(pid),
goqu.I("r.slave_meter_id").Eq(code),
goqu.I("r.revoked_at").IsNull(),
).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &relations, relationsSql, relationsArgs...); err != nil {
mr.log.Error("查询表计关系失败", zap.Error(err))
return make([]*model.MeterRelation, 0), err
}
return relations, nil
}
// 列出指定园区中的所有公摊表计
func (mr _MeterRepository) ListPoolingMeters(pid string, page uint, keyword *string) ([]*model.MeterDetail, int64, error) {
mr.log.Info("列出指定园区中的所有公摊表计", zap.String("park id", pid), zap.Uint("page", page), zap.String("keyword", tools.DefaultTo(keyword, "")))
ctx, cancel := global.TimeoutContext()
defer cancel()
meterQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.enabled").IsTrue(),
goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_POOLING),
)
countQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
Select(goqu.COUNT("*")).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.enabled").IsTrue(),
goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_POOLING),
)
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
meterQuery = meterQuery.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
meterQuery = meterQuery.Order(goqu.I("m.code").Asc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
var (
meters []*model.MeterDetail
total int64
)
if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil {
mr.log.Error("查询公摊表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), 0, err
}
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
mr.log.Error("查询公摊表计数量失败", zap.Error(err))
return make([]*model.MeterDetail, 0), 0, err
}
return meters, total, nil
}
// 列出目前尚未绑定到公摊表计的商户表计
func (mr _MeterRepository) ListUnboundMeters(uid string, pid *string, keyword *string, limit *uint) ([]*model.MeterDetail, error) {
mr.log.Info("列出目前尚未绑定到公摊表计的商户表计", zap.Stringp("park id", pid), zap.String("user id", uid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("limit", tools.DefaultTo(limit, 0)))
ctx, cancel := global.TimeoutContext()
defer cancel()
meterQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_TENEMENT),
goqu.I("m.enabled").IsTrue(),
)
if pid != nil && len(*pid) > 0 {
meterQuery = meterQuery.Where(
goqu.I("m.park_id").Eq(*pid),
)
}
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
meterQuery = meterQuery.Where(
goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
),
)
}
slaveMeterQuery := mr.ds.
From("meter_relations").
Select("id")
if pid != nil && len(*pid) > 0 {
slaveMeterQuery = slaveMeterQuery.Where(
goqu.I("park_id").Eq(*pid),
)
} else {
slaveMeterQuery = slaveMeterQuery.Where(
goqu.I("park_id").In(
mr.ds.
From("park").
Select("id").
Where(goqu.I("user_id").Eq(uid)),
))
}
slaveMeterQuery = slaveMeterQuery.Where(
goqu.I("revoked_at").IsNull(),
)
meterQuery = meterQuery.Where(
goqu.I("m.code").NotIn(slaveMeterQuery),
).
Order(goqu.I("m.attached_at").Asc())
if limit != nil && *limit > 0 {
meterQuery = meterQuery.Limit(*limit)
}
meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL()
var meters []*model.MeterDetail
if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil {
mr.log.Error("查询商户表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 列出目前未绑定到商户的商户表计
func (mr _MeterRepository) ListUnboundTenementMeters(uid string, pid *string, keyword *string, limit *uint) ([]*model.MeterDetail, error) {
mr.log.Info("列出目前未绑定到商户的商户表计", zap.Stringp("park id", pid), zap.String("user id", uid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("limit", tools.DefaultTo(limit, 0)))
ctx, cancel := global.TimeoutContext()
defer cancel()
meterQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_TENEMENT),
goqu.I("m.enabled").IsTrue(),
)
if pid != nil && len(*pid) > 0 {
meterQuery = meterQuery.Where(
goqu.I("m.park_id").Eq(*pid),
)
}
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
meterQuery = meterQuery.Where(
goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
),
)
}
subMeterQuery := mr.ds.
From("tenement_meter").
Select("meter_id")
if pid != nil && len(*pid) > 0 {
subMeterQuery = subMeterQuery.Where(
goqu.I("park_id").Eq(*pid),
)
} else {
subMeterQuery = subMeterQuery.Where(
goqu.I("park_id").In(
mr.ds.
From("park").
Select("id").
Where(goqu.I("user_id").Eq(uid)),
))
}
subMeterQuery = subMeterQuery.Where(
goqu.I("disassociated_at").IsNull(),
)
meterQuery = meterQuery.Where(
goqu.I("m.code").NotIn(subMeterQuery),
).
Order(goqu.I("m.attached_at").Asc())
if limit != nil && *limit > 0 {
meterQuery = meterQuery.Limit(*limit)
}
meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL()
var meters []*model.MeterDetail
if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil {
mr.log.Error("查询商户表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 查询指定园区中的符合条件的抄表记录
func (mr _MeterRepository) ListMeterReadings(pid string, keyword *string, page uint, start, end *types.Date, buidling *string) ([]*model.MeterReading, int64, error) {
mr.log.Info("查询指定园区中的符合条件的抄表记录", zap.String("park id", pid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("page", page), logger.DateFieldp("start", start), logger.DateFieldp("end", end), zap.String("building", tools.DefaultTo(buidling, "")))
ctx, cancel := global.TimeoutContext()
defer cancel()
readingQuery := mr.ds.
From(goqu.T("meter_reading").As("r")).
LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("r.meter_id").Eq(goqu.I("m.code")))).
Select("r.*").
Where(
goqu.I("r.park_id").Eq(pid),
)
countQuery := mr.ds.
From(goqu.T("meter_reading").As("r")).
LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("r.meter_id").Eq(goqu.I("m.code")))).
Select(goqu.COUNT("*")).
Where(
goqu.I("r.park_id").Eq(pid),
)
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
readingQuery = readingQuery.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),
),
)
}
if start != nil {
readingQuery = readingQuery.Where(
goqu.I("r.read_at").Gte(start.ToBeginningOfDate()),
)
countQuery = countQuery.Where(
goqu.I("r.read_at").Gte(start.ToBeginningOfDate()),
)
}
if end != nil {
readingQuery = readingQuery.Where(
goqu.I("r.read_at").Lte(end.ToEndingOfDate()),
)
countQuery = countQuery.Where(
goqu.I("r.read_at").Lte(end.ToEndingOfDate()),
)
}
if buidling != nil && len(*buidling) > 0 {
readingQuery = readingQuery.Where(
goqu.I("m.building").Eq(*buidling),
)
countQuery = countQuery.Where(
goqu.I("m.building").Eq(*buidling),
)
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
readingQuery = readingQuery.Order(goqu.I("r.read_at").Desc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
readingSql, readingArgs, _ := readingQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
var (
readings []*model.MeterReading
total int64
)
if err := pgxscan.Select(ctx, global.DB, &readings, readingSql, readingArgs...); err != nil {
mr.log.Error("查询抄表记录失败", zap.Error(err))
return make([]*model.MeterReading, 0), 0, err
}
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
mr.log.Error("查询抄表记录数量失败", zap.Error(err))
return make([]*model.MeterReading, 0), 0, err
}
return readings, total, nil
}
// 修改指定表计的指定抄表记录
func (mr _MeterRepository) UpdateMeterReading(pid, mid string, readAt types.DateTime, reading *vo.MeterReadingForm) (bool, error) {
mr.log.Info("修改指定表计的指定抄表记录", zap.String("park id", pid), zap.String("meter id", mid), logger.DateTimeField("read at", readAt), zap.Any("reading", reading))
ctx, cancel := global.TimeoutContext()
defer cancel()
updateSql, updateArgs, _ := mr.ds.
Update(goqu.T("meter_reading")).
Set(
goqu.Record{
"overall": reading.Overall,
"critical": reading.Critical,
"peak": reading.Peak,
"flat": reading.Flat,
"valley": reading.Valley,
},
).
Where(
goqu.I("park_id").Eq(pid),
goqu.I("meter_id").Eq(mid),
goqu.I("read_at").Eq(readAt),
).
Prepared(true).ToSQL()
ok, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
mr.log.Error("更新抄表记录失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 列出指定园区中指定时间区域内的所有表计抄表记录
func (mr _MeterRepository) ListMeterReadingsByTimeRange(pid string, start, end types.Date) ([]*model.MeterReading, error) {
mr.log.Info("列出指定园区中指定时间区域内的所有表计抄表记录", zap.String("park id", pid), zap.Time("start", start.Time), zap.Time("end", end.Time))
ctx, cancel := global.TimeoutContext()
defer cancel()
var readings []*model.MeterReading
readingSql, readingArgs, _ := mr.ds.
From(goqu.T("meter_reading").As("r")).
Select("*").
Where(
goqu.I("r.park_id").Eq(pid),
goqu.I("r.read_at").Gte(start.ToBeginningOfDate()),
goqu.I("r.read_at").Lte(end.ToEndingOfDate()),
).
Order(goqu.I("r.read_at").Desc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &readings, readingSql, readingArgs...); err != nil {
mr.log.Error("查询抄表记录失败", zap.Error(err))
return make([]*model.MeterReading, 0), err
}
return readings, nil
}
// 列出指定园区中在指定日期之前的最后一次抄表记录
func (mr _MeterRepository) ListLastMeterReading(pid string, date types.Date) ([]*model.MeterReading, error) {
mr.log.Info("列出指定园区中在指定日期之前的最后一次抄表记录", zap.String("park id", pid), zap.Time("date", date.Time))
ctx, cancel := global.TimeoutContext()
defer cancel()
var readings []*model.MeterReading
readingSql, readingArgs, _ := mr.ds.
From(goqu.T("meter_reading")).
Select(
goqu.MAX("read_at").As("read_at"),
"park_id", "meter_id", "overall", "critical", "peak", "flat", "valley",
).
Where(
goqu.I("park_id").Eq(pid),
goqu.I("read_at").Lt(date.ToEndingOfDate()),
).
GroupBy("park_id", "meter_id", "overall", "critical", "peak", "flat", "valley").
Order(goqu.I("read_at").Desc()).
Limit(1).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &readings, readingSql, readingArgs...); err != nil {
mr.log.Error("查询抄表记录失败", zap.Error(err))
return make([]*model.MeterReading, 0), err
}
return readings, nil
}
// 列出指定园区中的表计与商户的关联详细记录用于写入Excel模板文件
func (mr _MeterRepository) ListMeterDocForTemplate(pid string) ([]*model.SimpleMeterDocument, error) {
mr.log.Info("列出指定园区中的表计与商户的关联详细记录", zap.String("park id", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var docs []*model.SimpleMeterDocument
docSql, docArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(
goqu.T("tenement_meter").As("tm"),
goqu.On(
goqu.I("m.code").Eq(goqu.I("tm.meter_id")),
goqu.I("m.park_id").Eq(goqu.I("tm.park_id")),
),
).
LeftJoin(
goqu.T("tenement").As("t"),
goqu.On(
goqu.I("tm.tenement_id").Eq(goqu.I("t.id")),
goqu.I("tm.park_id").Eq(goqu.I("t.park_id")),
),
).
Select(
"m.code", "m.address", "m.ratio", "m.seq", goqu.I("t.full_name").As("tenement_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.enabled").IsTrue(),
goqu.I("tm.disassociated_at").IsNull(),
).
Order(goqu.I("m.seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &docs, docSql, docArgs...); err != nil {
mr.log.Error("查询表计与商户关联信息失败", zap.Error(err))
return make([]*model.SimpleMeterDocument, 0), err
}
return docs, nil
}

419
repository/park.go Normal file
View File

@ -0,0 +1,419 @@
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",
).
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
}

846
repository/report.go Normal file
View File

@ -0,0 +1,846 @@
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"
"fmt"
"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, 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_o4kv_type", "price_policy",
"basic_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()
summarySql, summaryArgs, _ := rr.ds.
Insert(goqu.T("report_summary")).
Cols(
"report_id", "overall", "critical", "peak", "flat", "valley", "basic_fee",
"adjust_fee",
).
Vals(goqu.Vals{
reportId,
model.ConsumptionUnit{
Amount: form.Overall,
Fee: form.OverallFee,
},
model.ConsumptionUnit{
Amount: form.Critical,
Fee: form.CriticalFee,
},
model.ConsumptionUnit{
Amount: form.Peak,
Fee: form.PeakFee,
},
model.ConsumptionUnit{
Amount: form.Flat,
Fee: form.FlatFee,
},
model.ConsumptionUnit{
Amount: form.Valley,
Fee: form.ValleyFee,
},
form.BasicFee,
form.AdjustFee,
}).
Prepared(true).ToSQL()
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
}
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, 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) ([]*model.ReportDetailedPublicConsumption, 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("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.park_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.I("r.*"), goqu.I("b.name").As("building_name"), goqu.I("p.public_pooled"),
).
Where(goqu.I("r.report_id").Eq(rid))
countQuery := rr.ds.
From(goqu.T("report_public_consumption").As("r")).
Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.park_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.ReportDetailedPublicConsumption = make([]*model.ReportDetailedPublicConsumption, 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("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.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))
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
}
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 []*model.ReportTenement = make([]*model.ReportTenement, 0)
count int64
)
querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
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 && len(*pid) > 0 {
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))
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
}

458
repository/tenement.go Normal file
View File

@ -0,0 +1,458 @@
package repository
import (
"context"
"electricity_bill_calc/config"
"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"
"electricity_bill_calc/vo"
"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"
"go.uber.org/zap"
)
type _TenementRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var TenementRepository = _TenementRepository{
log: logger.Named("Repository", "Tenement"),
ds: goqu.Dialect("postgres"),
}
// 判断指定商户是否属于指定用户的管辖
func (tr _TenementRepository) IsTenementBelongs(tid, uid string) (bool, error) {
tr.log.Info("检查指定商户是否属于指定企业管辖", zap.String("Tenement", tid), zap.String("Enterprise", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
countSql, countArgs, _ := tr.ds.
From(goqu.T("tenement").As("t")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("t.park_id").Eq(goqu.I("p.id")))).
Select(goqu.COUNT("t.*")).
Where(
goqu.I("t.id").Eq(tid),
goqu.I("p.user_id").Eq(uid),
).
Prepared(true).ToSQL()
var count int
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
tr.log.Error("检查指定商户是否属于指定企业管辖失败", zap.Error(err))
return false, err
}
return count > 0, nil
}
// 列出指定园区中的所有商户
func (tr _TenementRepository) ListTenements(pid string, page uint, keyword, building *string, startDate, endDate *types.Date, state int) ([]*model.Tenement, int64, error) {
tr.log.Info(
"检索查询指定园区中符合条件的商户",
zap.String("Park", pid),
zap.Uint("Page", page),
zap.Stringp("Keyword", keyword),
zap.Stringp("Building", building),
logger.DateFieldp("StartDate", startDate),
logger.DateFieldp("EndDate", endDate),
zap.Int("State", state),
)
ctx, cancel := global.TimeoutContext()
defer cancel()
tenementQuery := tr.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")))).
Select("t.*", goqu.I("b.name").As("building_name")).
Where(goqu.I("t.park_id").Eq(pid))
countQuery := tr.ds.
From(goqu.T("tenement").As("t")).
Select(goqu.COUNT("t.*")).
Where(goqu.I("t.park_id").Eq(pid))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
tenementQuery = tenementQuery.Where(
goqu.Or(
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.I("t.address").ILike(pattern),
),
)
countQuery = countQuery.Where(
goqu.Or(
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.I("t.address").ILike(pattern),
),
)
}
if building != nil && len(*building) > 0 {
tenementQuery = tenementQuery.Where(goqu.I("t.building").Eq(*building))
countQuery = countQuery.Where(goqu.I("t.building").Eq(*building))
}
if startDate != nil {
tenementQuery = tenementQuery.Where(
goqu.Or(
goqu.I("t.moved_in_at").Gte(startDate.ToBeginningOfDate()),
goqu.I("t.moved_out_at").Gte(startDate.ToBeginningOfDate()),
),
)
countQuery = countQuery.Where(
goqu.Or(
goqu.I("t.moved_in_at").Gte(startDate.ToBeginningOfDate()),
goqu.I("t.moved_out_at").Gte(startDate.ToBeginningOfDate()),
),
)
}
if endDate != nil {
tenementQuery = tenementQuery.Where(
goqu.Or(
goqu.I("t.moved_in_at").Lte(endDate.ToEndingOfDate()),
goqu.I("t.moved_out_at").Lte(endDate.ToEndingOfDate()),
),
)
countQuery = countQuery.Where(
goqu.Or(
goqu.I("t.moved_in_at").Lte(endDate.ToEndingOfDate()),
goqu.I("t.moved_out_at").Lte(endDate.ToEndingOfDate()),
),
)
}
if state == 0 {
tenementQuery = tenementQuery.Where(
goqu.I("t.moved_out_at").IsNull(),
)
countQuery = countQuery.Where(
goqu.I("t.moved_out_at").IsNull(),
)
} else {
tenementQuery = tenementQuery.Where(
goqu.I("t.moved_out_at").IsNotNull(),
)
countQuery = countQuery.Where(
goqu.I("t.moved_out_at").IsNotNull(),
)
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
tenementQuery = tenementQuery.Order(goqu.I("t.created_at").Desc()).Limit(config.ServiceSettings.ItemsPageSize).Offset(startRow)
tenementSql, tenementArgs, _ := tenementQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
var (
tenements []*model.Tenement = make([]*model.Tenement, 0)
total int64
)
if err := pgxscan.Select(ctx, global.DB, &tenements, tenementSql, tenementArgs...); err != nil {
tr.log.Error("检索查询指定园区中符合条件的商户失败", zap.Error(err))
return tenements, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
tr.log.Error("检索查询指定园区中符合条件的商户总数量失败", zap.Error(err))
return tenements, 0, err
}
return tenements, total, nil
}
// 查询指定园区中某一商户下的所有表计编号,不包含公摊表计
func (tr _TenementRepository) ListMeterCodesBelongsTo(pid, tid string) ([]string, error) {
tr.log.Info("查询指定商户下所有的表计编号", zap.String("Park", pid), zap.String("Tenement", tid))
ctx, cancel := global.TimeoutContext()
defer cancel()
sql, args, _ := tr.ds.
From("tenement_meter").
Select("meter_id").
Where(
goqu.I("park_id").Eq(pid),
goqu.I("tenement_id").Eq(tid),
goqu.I("disassociated_at").IsNull(),
).
Prepared(true).ToSQL()
var meterCodes []string = make([]string, 0)
if err := pgxscan.Select(ctx, global.DB, &meterCodes, sql, args...); err != nil {
tr.log.Error("查询指定商户下所有的表计编号失败", zap.Error(err))
return meterCodes, err
}
return meterCodes, nil
}
// 在指定园区中创建一个新的商户
func (tr _TenementRepository) AddTenement(tx pgx.Tx, ctx context.Context, pid string, tenement *vo.TenementCreationForm) error {
tr.log.Info("在指定园区中创建一个新的商户", zap.String("Park", pid))
serial.StringSerialRequestChan <- 1
tenementId := serial.Prefix("T", <-serial.StringSerialResponseChan)
currentTime := types.Now()
if _, err := tx.Exec(
ctx,
"INSERT INTO tenement (id, park_id, full_name, short_name, abbr, address, contact_name, contact_phone, building, on_floor, invoice_info, moved_in_at, created_at, last_modified_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)",
[]interface{}{
tenementId,
pid,
tenement.Name,
tenement.ShortName,
tools.PinyinAbbr(tenement.Name),
tenement.Address,
tenement.Contact,
tenement.Phone,
tenement.Building,
tenement.OnFloor,
&model.InvoiceTitle{
Name: tenement.Name,
USCI: tenement.USCI,
Address: tools.DefaultOrEmptyStr(tenement.InvoiceAddress, ""),
Phone: tools.DefaultOrEmptyStr(tenement.InvoicePhone, ""),
Bank: tools.DefaultOrEmptyStr(tenement.Bank, ""),
Account: tools.DefaultOrEmptyStr(tenement.Account, ""),
},
currentTime,
currentTime,
currentTime,
}...,
); err != nil {
tr.log.Error("在指定园区中创建一个新的商户失败", zap.Error(err))
return err
}
return nil
}
// 向园区中指定商户下绑定一个新的表计
func (tr _TenementRepository) BindMeter(tx pgx.Tx, ctx context.Context, pid, tid, meter string) error {
tr.log.Info("向园区中指定商户下绑定一个新的表计", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Meter", meter))
createSql, createArgs, _ := tr.ds.
Insert("tenement_meter").
Cols(
"park_id", "tenement_id", "meter_id", "associated_at",
).
Vals(
goqu.Vals{
pid,
tid,
meter,
types.Now(),
},
).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, createSql, createArgs...); err != nil {
tr.log.Error("向园区中指定商户下绑定一个新的表计失败", zap.Error(err))
return err
}
return nil
}
// 将指定商户与指定表计解绑
func (tr _TenementRepository) UnbindMeter(tx pgx.Tx, ctx context.Context, pid, tid, meter string) error {
tr.log.Info("将指定商户与指定表计解绑", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Meter", meter))
updateSql, updateArgs, _ := tr.ds.
Update("tenement_meter").
Set(
goqu.Record{
"disassociated_at": types.Now(),
},
).
Where(
goqu.I("park_id").Eq(pid),
goqu.I("tenement_id").Eq(tid),
goqu.I("meter_id").Eq(meter),
).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil {
tr.log.Error("将指定商户与指定表计解绑失败", zap.Error(err))
return err
}
return nil
}
// 修改指定商户的信息
func (tr _TenementRepository) UpdateTenement(pid, tid string, tenement *vo.TenementCreationForm) error {
tr.log.Info("修改指定商户的信息", zap.String("Park", pid), zap.String("Tenement", tid))
ctx, cancel := global.TimeoutContext()
defer cancel()
updateSql, updateArgs, _ := tr.ds.
Update("tenement").
Set(
goqu.Record{
"full_name": tenement.Name,
"short_name": tenement.ShortName,
"abbr": tools.PinyinAbbr(tenement.Name),
"address": tenement.Address,
"contact_name": tenement.Contact,
"contact_phone": tenement.Phone,
"building": tenement.Building,
"on_floor": tenement.OnFloor,
"invoice_info": &model.InvoiceTitle{
Name: tenement.Name,
USCI: tenement.USCI,
Address: tools.DefaultOrEmptyStr(tenement.InvoiceAddress, ""),
Phone: tools.DefaultOrEmptyStr(tenement.InvoicePhone, ""),
Bank: tools.DefaultOrEmptyStr(tenement.Bank, ""),
Account: tools.DefaultOrEmptyStr(tenement.Account, ""),
},
"last_modified_at": types.Now(),
},
).
Where(
goqu.I("id").Eq(tid),
goqu.I("park_id").Eq(pid),
).
Prepared(true).ToSQL()
if _, err := global.DB.Exec(ctx, updateSql, updateArgs...); err != nil {
tr.log.Error("修改指定商户的信息失败", zap.Error(err))
return err
}
return nil
}
// 迁出指定商户
func (tr _TenementRepository) MoveOut(tx pgx.Tx, ctx context.Context, pid, tid string) error {
tr.log.Info("迁出指定商户", zap.String("Park", pid), zap.String("Tenement", tid))
updateSql, updateArgs, _ := tr.ds.
Update("tenement").
Set(
goqu.Record{
"moved_out_at": types.Now(),
},
).
Where(
goqu.I("id").Eq(tid),
goqu.I("park_id").Eq(pid),
).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil {
tr.log.Error("迁出指定商户失败", zap.Error(err))
return err
}
return nil
}
// 列出用于下拉列表的符合指定条件的商户信息
func (tr _TenementRepository) ListForSelect(uid string, pid, keyword *string, limit *uint) ([]*model.Tenement, error) {
tr.log.Info("列出用于下拉列表的符合指定条件的商户信息", zap.String("Ent", uid), zap.String("Park", tools.DefaultOrEmptyStr(pid, "All")), zap.Stringp("Keyword", keyword), zap.Uintp("Limit", limit))
ctx, cancel := global.TimeoutContext()
defer cancel()
tenementQuery := tr.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("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("t.park_id")))).
Select(
"t.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("p.user_id").Eq(uid),
goqu.I("t.moved_out_at").IsNull(),
)
if pid != nil && len(*pid) > 0 {
tenementQuery = tenementQuery.Where(goqu.I("p.id").Eq(*pid))
}
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
tenementQuery = tenementQuery.Where(
goqu.Or(
goqu.I("t.full_name").ILike(pattern),
goqu.I("t.short_name").ILike(pattern),
goqu.I("t.abbr").ILike(pattern),
),
)
}
tenementQuery = tenementQuery.Order(goqu.I("t.created_at").Desc())
if limit != nil && *limit > 0 {
tenementQuery = tenementQuery.Limit(*limit)
}
tenementSql, tenementArgs, _ := tenementQuery.Prepared(true).ToSQL()
var tenements = make([]*model.Tenement, 0)
if err := pgxscan.Select(ctx, global.DB, &tenements, tenementSql, tenementArgs...); err != nil {
tr.log.Error("列出用于下拉列表的符合指定条件的商户信息失败", zap.Error(err))
return tenements, err
}
return tenements, nil
}
// 列出指定园区中在指定时间区间内存在过入住的商户
func (tr _TenementRepository) ListTenementsInTimeRange(pid string, start, end types.Date) ([]*model.Tenement, error) {
tr.log.Info("列出指定园区中在指定时间区间内存在过入住的商户", zap.String("Park", pid), logger.DateField("Start", start), logger.DateField("End", end))
ctx, cancel := global.TimeoutContext()
defer cancel()
tenementQuery := tr.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")))).
Select(
"t.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("t.park_id").Eq(pid),
goqu.I("t.moved_in_at").Lte(end.ToEndingOfDate()),
goqu.Or(
goqu.I("t.moved_out_at").IsNull(),
goqu.I("t.moved_out_at").Gte(start.ToBeginningOfDate()),
),
).
Order(goqu.I("t.created_at").Desc())
tenementSql, tenementArgs, _ := tenementQuery.Prepared(true).ToSQL()
var tenements = make([]*model.Tenement, 0)
if err := pgxscan.Select(ctx, global.DB, &tenements, tenementSql, tenementArgs...); err != nil {
tr.log.Error("列出指定园区中在指定时间区间内存在过入住的商户失败", zap.Error(err))
return tenements, err
}
return tenements, nil
}
// 获取指定园区中指定商户的详细信息
func (tr _TenementRepository) RetrieveTenementDetail(pid, tid string) (*model.Tenement, error) {
tr.log.Info("获取指定园区中指定商户的详细信息", zap.String("Park", pid), zap.String("Tenement", tid))
ctx, cancel := global.TimeoutContext()
defer cancel()
tenementSql, tenementArgs, _ := tr.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")))).
Select(
"t.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("t.id").Eq(tid),
goqu.I("t.park_id").Eq(pid),
).
Prepared(true).ToSQL()
var tenement model.Tenement
if err := pgxscan.Get(ctx, global.DB, &tenement, tenementSql, tenementArgs...); err != nil {
tr.log.Error("获取指定园区中指定商户的详细信息失败", zap.Error(err))
return nil, err
}
return &tenement, nil
}

166
repository/top_up.go Normal file
View File

@ -0,0 +1,166 @@
package repository
import (
"electricity_bill_calc/config"
"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"
"fmt"
"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 _TopUpRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var TopUpRepository = _TopUpRepository{
log: logger.Named("Repository", "TopUp"),
ds: goqu.Dialect("postgres"),
}
// 检索符合条件的商户充值记录
func (tur _TopUpRepository) ListTopUps(pid string, startDate, endDate *types.Date, keyword *string, page uint) ([]*model.TopUp, int64, error) {
tur.log.Info("查询符合条件的商户充值记录", zap.String("Park", pid), logger.DateFieldp("StartDate", startDate), logger.DateFieldp("EndDate", endDate), zap.Stringp("keyword", keyword), zap.Uint("page", page))
ctx, cancel := global.TimeoutContext()
defer cancel()
topUpQuery := tur.ds.
From(goqu.T("tenement_top_ups").As("t")).
LeftJoin(goqu.T("tenement").As("te"), goqu.On(goqu.I("te.id").Eq(goqu.I("t.tenement_id")), goqu.I("te.park_id").Eq(goqu.I("t.park_id")))).
LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))).
Select("t.*", goqu.I("te.full_name").As("tenement_name"), goqu.I("m.address").As("meter_address")).
Where(goqu.I("t.park_id").Eq(pid))
countQuery := tur.ds.
From(goqu.T("tenement_top_ups").As("t")).
LeftJoin(goqu.T("tenement").As("te"), goqu.On(goqu.I("te.id").Eq(goqu.I("t.tenement_id")), goqu.I("te.park_id").Eq(goqu.I("t.park_id")))).
LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))).
Select(goqu.COUNT("t.*")).
Where(goqu.I("t.park_id").Eq(pid))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
topUpQuery = topUpQuery.Where(goqu.Or(
goqu.I("te.full_name").ILike(pattern),
goqu.I("te.short_name").ILike(pattern),
goqu.I("te.abbr").ILike(pattern),
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("te.full_name").ILike(pattern),
goqu.I("te.short_name").ILike(pattern),
goqu.I("te.abbr").ILike(pattern),
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
}
if startDate != nil {
topUpQuery = topUpQuery.Where(goqu.I("t.topped_up_at").Gte(startDate.ToBeginningOfDate()))
countQuery = countQuery.Where(goqu.I("t.topped_up_at").Gte(startDate.ToBeginningOfDate()))
}
if endDate != nil {
topUpQuery = topUpQuery.Where(goqu.I("t.topped_up_at").Lte(endDate.ToEndingOfDate()))
countQuery = countQuery.Where(goqu.I("t.topped_up_at").Lte(endDate.ToEndingOfDate()))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
topUpQuery = topUpQuery.Order(goqu.I("t.topped_up_at").Desc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
topUpSql, topUpArgs, _ := topUpQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
var (
topUps []*model.TopUp = make([]*model.TopUp, 0)
total int64 = 0
)
if err := pgxscan.Select(ctx, global.DB, &topUps, topUpSql, topUpArgs...); err != nil {
tur.log.Error("查询商户充值记录失败", zap.Error(err))
return topUps, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
tur.log.Error("查询商户充值记录总数失败", zap.Error(err))
return topUps, 0, err
}
return topUps, total, nil
}
// 取得一个充值记录的详细信息
func (tur _TopUpRepository) GetTopUp(pid, topUpCode string) (*model.TopUp, error) {
tur.log.Info("查询充值记录", zap.String("Park", pid), zap.String("TopUpCode", topUpCode))
ctx, cancel := global.TimeoutContext()
defer cancel()
topUpSql, topUpArgs, _ := tur.ds.
From(goqu.T("tenement_top_ups").As("t")).
LeftJoin(goqu.T("tenement").As("te"), goqu.On(goqu.I("te.id").Eq(goqu.I("t.tenement_id")), goqu.I("te.park_id").Eq(goqu.I("t.park_id")))).
LeftJoin(goqu.T("meter").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))).
Select("t.*", goqu.I("te.full_name").As("tenement_name"), goqu.I("m.address").As("meter_address")).
Where(goqu.I("t.park_id").Eq(pid), goqu.I("t.top_up_code").Eq(topUpCode)).
Prepared(true).ToSQL()
var topUp model.TopUp
if err := pgxscan.Get(ctx, global.DB, &topUp, topUpSql, topUpArgs...); err != nil {
tur.log.Error("查询充值记录失败", zap.Error(err))
return nil, err
}
return &topUp, nil
}
// 创建一条新的充值记录
func (tur _TopUpRepository) CreateTopUp(pid string, form *vo.TopUpCreationForm) error {
tur.log.Info("创建一条新的充值记录", zap.String("Park", pid), zap.String("Tenement", form.Tenement), zap.String("Meter", form.Meter))
ctx, cancel := global.TimeoutContext()
defer cancel()
serial.StringSerialRequestChan <- 1
topUpCode := serial.Prefix("U", <-serial.StringSerialResponseChan)
topUpTime := types.Now()
topUpSql, topUpArgs, _ := tur.ds.
Insert("tenement_top_ups").
Cols("top_up_code", "park_id", "tenement_id", "meter_id", "topped_up_at", "amount", "payment_type").
Vals(goqu.Vals{
topUpCode,
pid,
form.Tenement,
form.Meter,
topUpTime,
form.Amount,
model.PAYMENT_CASH,
}).
Prepared(true).ToSQL()
if _, err := global.DB.Exec(ctx, topUpSql, topUpArgs...); err != nil {
tur.log.Error("创建充值记录失败", zap.Error(err))
return err
}
return nil
}
// 删除一条充值记录
func (tur _TopUpRepository) DeleteTopUp(pid, topUpCode string) error {
tur.log.Info("删除一条充值记录", zap.String("Park", pid), zap.String("TopUpCode", topUpCode))
ctx, cancel := global.TimeoutContext()
defer cancel()
topUpSql, topUpArgs, _ := tur.ds.
Update("tenement_top_ups").
Set(goqu.Record{"cancelled_at": types.Now()}).
Where(goqu.I("park_id").Eq(pid), goqu.I("top_up_code").Eq(topUpCode)).
Prepared(true).ToSQL()
if _, err := global.DB.Exec(ctx, topUpSql, topUpArgs...); err != nil {
tur.log.Error("删除充值记录失败", zap.Error(err))
return err
}
return nil
}

419
repository/user.go Normal file
View File

@ -0,0 +1,419 @@
package repository
import (
"context"
"electricity_bill_calc/config"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"electricity_bill_calc/types"
"fmt"
"time"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/fufuok/utils/xhash"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5"
"github.com/samber/lo"
"go.uber.org/zap"
)
type _UserRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var UserRepository = _UserRepository{
log: logger.Named("Repository", "User"),
ds: goqu.Dialect("postgres"),
}
// 使用用户名查询指定用户的基本信息
func (ur _UserRepository) FindUserByUsername(username string) (*model.User, error) {
ur.log.Info("根据用户名查询指定用户的基本信息。", zap.String("username", username))
ctx, cancel := global.TimeoutContext()
defer cancel()
var user model.User
sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"username": username}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err))
return nil, err
}
return &user, nil
}
// 使用用户唯一编号查询指定用户的基本信息
func (ur _UserRepository) FindUserById(uid string) (*model.User, error) {
ur.log.Info("根据用户唯一编号查询指定用户的基本信息。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var user model.User
sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err))
return nil, err
}
return &user, nil
}
// 使用用户的唯一编号获取用户的详细信息
func (ur _UserRepository) FindUserDetailById(uid string) (*model.UserDetail, error) {
ur.log.Info("根据用户唯一编号查询指定用户的详细信息。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var user model.UserDetail
sql, params, _ := ur.ds.From("user_detail").Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err))
return nil, err
}
return &user, nil
}
// 使用用户唯一编号获取用户的综合详细信息
func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail, error) {
ur.log.Info("根据用户唯一编号查询用户的综合详细信息", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var user model.UserWithDetail
sql, params, _ := ur.ds.
From("user").As("u").
Join(
goqu.T("user_detail").As("ud"),
goqu.On(goqu.Ex{
"ud.id": goqu.I("u.id"),
})).
Select(
"u.id", "u.username", "u.reset_needed", "u.type", "u.enabled",
"ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone",
"ud.unit_service_fee", "ud.service_expiration",
"ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by").
Where(goqu.Ex{"u.id": uid}).
Prepared(true).ToSQL()
if err := pgxscan.Get(
ctx, global.DB, &user, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err))
return nil, err
}
return &user, nil
}
// 检查指定用户唯一编号是否存在对应的用户
func (ur _UserRepository) IsUserExists(uid string) (bool, error) {
ur.log.Info("检查指定用户唯一编号是否存在对应的用户。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var userCount int
sql, params, _ := ur.ds.From("user").Select(goqu.COUNT("*")).Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &userCount, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err))
return false, err
}
return userCount > 0, nil
}
// 检查指定用户名在数据库中是否已经存在
func (ur _UserRepository) IsUsernameExists(username string) (bool, error) {
ur.log.Info("检查指定用户名在数据库中是否已经存在。", zap.String("username", username))
ctx, cancel := global.TimeoutContext()
defer cancel()
var userCount int
sql, params, _ := ur.ds.From("user").Select(goqu.COUNT("*")).Where(goqu.Ex{"username": username}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &userCount, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err))
return false, err
}
return userCount > 0, nil
}
// 创建一个新用户
func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, operator *string) (bool, error) {
ur.log.Info("创建一个新用户。", zap.String("username", user.Username))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
ur.log.Error("启动数据库事务失败。", zap.Error(err))
return false, err
}
createdTime := types.Now()
userSql, userParams, _ := ur.ds.
Insert("user").
Rows(
goqu.Record{
"id": user.Id, "username": user.Username, "password": user.Password,
"reset_needed": user.ResetNeeded, "type": user.UserType, "enabled": user.Enabled,
"created_at": createdTime,
},
).
Prepared(true).ToSQL()
userResult, err := tx.Exec(ctx, userSql, userParams...)
if err != nil {
ur.log.Error("向数据库插入新用户基本信息失败。", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
userDetailSql, userDetailParams, _ := ur.ds.
Insert("user_detail").
Rows(
goqu.Record{
"id": user.Id, "name": detail.Name, "abbr": tools.PinyinAbbr(*detail.Name), "region": detail.Region,
"address": detail.Address, "contact": detail.Contact, "phone": detail.Phone,
"unit_service_fee": detail.UnitServiceFee, "service_expiration": detail.ServiceExpiration,
"created_at": createdTime, "created_by": operator,
"last_modified_at": createdTime, "last_modified_by": operator,
},
).
Prepared(true).ToSQL()
detailResult, err := tx.Exec(ctx, userDetailSql, userDetailParams...)
if err != nil {
ur.log.Error("向数据库插入新用户详细信息失败。", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
err = tx.Commit(ctx)
if err != nil {
ur.log.Error("提交数据库事务失败。", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
return userResult.RowsAffected() > 0 && detailResult.RowsAffected() > 0, nil
}
// 根据给定的条件检索用户
func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, page uint) ([]*model.UserWithDetail, int64, error) {
ur.log.Info("根据给定的条件检索用户。", zap.Uint("page", page), zap.Stringp("keyword", keyword), zap.Int16("user type", userType), zap.Boolp("state", state))
ctx, cancel := global.TimeoutContext()
defer cancel()
var (
userWithDetails []*model.UserWithDetail
userCount int64
)
userQuery := ur.ds.
From(goqu.T("user").As("u")).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.Ex{"ud.id": goqu.I("u.id")})).
Select(
"u.id", "u.username", "u.reset_needed", "u.type", "u.enabled",
"ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone",
"ud.unit_service_fee", "ud.service_expiration",
"ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by",
)
countQuery := ur.ds.
From(goqu.T("user").As("u")).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.Ex{"ud.id": goqu.I("u.id")})).
Select(goqu.COUNT("*"))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
userQuery = userQuery.Where(
goqu.Or(
goqu.I("u.username").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
),
)
countQuery = countQuery.Where(
goqu.Or(
goqu.I("u.username").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
),
)
}
if userType != -1 {
userQuery = userQuery.Where(goqu.Ex{"u.type": userType})
countQuery = countQuery.Where(goqu.Ex{"u.type": userType})
}
if state != nil {
userQuery = userQuery.Where(goqu.Ex{"u.enabled": state})
countQuery = countQuery.Where(goqu.Ex{"u.enabled": state})
}
userQuery.Order(goqu.I("u.created_at").Desc())
currentPosition := (page - 1) * config.ServiceSettings.ItemsPageSize
userQuery = userQuery.Offset(currentPosition).Limit(config.ServiceSettings.ItemsPageSize)
userSql, userParams, _ := userQuery.Prepared(true).ToSQL()
countSql, countParams, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &userWithDetails, userSql, userParams...); err != nil {
ur.log.Error("从数据库查询用户列表失败。", zap.Error(err))
return make([]*model.UserWithDetail, 0), 0, err
}
if err := pgxscan.Get(ctx, global.DB, &userCount, countSql, countParams...); err != nil {
ur.log.Error("从数据库查询用户列表总数失败。", zap.Error(err))
return make([]*model.UserWithDetail, 0), 0, err
}
return userWithDetails, userCount, nil
}
// 更新指定用户的详细信息
func (ur _UserRepository) UpdateDetail(uid string, userDetail model.UserModificationForm, operator *string) (bool, error) {
ur.log.Info("更新指定用户的详细信息。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
updates := goqu.Record{
"name": userDetail.Name, "abbr": tools.PinyinAbbr(userDetail.Name), "region": userDetail.Region,
"address": userDetail.Address, "contact": userDetail.Contact, "phone": userDetail.Phone,
"last_modified_at": types.Now(), "last_modified_by": operator,
}
if userDetail.UnitServiceFee != nil {
updates = lo.Assign(updates, goqu.Record{"unit_service_fee": userDetail.UnitServiceFee})
}
userDetailUpdateQuery := ur.ds.
Update("user_detail").
Set(updates).
Where(goqu.Ex{"id": uid})
userDetailSql, userDetailParams, _ := userDetailUpdateQuery.
Prepared(true).ToSQL()
if res, err := global.DB.Exec(ctx, userDetailSql, userDetailParams...); err != nil {
ur.log.Error("向数据库更新指定用户的详细信息失败。", zap.String("user id", uid), zap.Error(err))
return false, err
} else {
return res.RowsAffected() > 0, nil
}
}
// 更新指定用户的登录凭据
func (ur _UserRepository) UpdatePassword(uid, newCredential string, needReset bool) (bool, error) {
ur.log.Info("更新指定用户的登录凭据。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
userUpdateQuery := ur.ds.
Update("user").
Set(goqu.Record{"password": xhash.Sha512Hex(newCredential), "reset_needed": needReset}).
Where(goqu.Ex{"id": uid})
userSql, userParams, _ := userUpdateQuery.
Prepared(true).ToSQL()
if res, err := global.DB.Exec(ctx, userSql, userParams...); err != nil {
ur.log.Error("向数据库更新指定用户的登录凭据失败。", zap.String("user id", uid), zap.Error(err))
return false, err
} else {
return res.RowsAffected() > 0, nil
}
}
// 更新指定用户的可用性状态
func (ur _UserRepository) ChangeState(uid string, state bool) (bool, error) {
ur.log.Info("更新指定用户的可用性状态。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
userUpdateQuery := ur.ds.
Update("user").
Set(goqu.Record{"enabled": state}).
Where(goqu.Ex{"id": uid})
userSql, userParams, _ := userUpdateQuery.
Prepared(true).ToSQL()
if res, err := global.DB.Exec(ctx, userSql, userParams...); err != nil {
ur.log.Error("向数据库更新指定用户的可用性状态失败。", zap.String("user id", uid), zap.Error(err))
return false, err
} else {
return res.RowsAffected() > 0, nil
}
}
// 检索条目数量有限的用户详细信息
func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, limit uint) ([]*model.UserWithDetail, error) {
ur.log.Info("检索条目数量有限的用户详细信息。", zap.Int16p("user type", userType), zap.Uint("limit", limit), zap.Stringp("keyword", keyword))
actualUserType := tools.DefaultTo(userType, model.USER_TYPE_ENT)
ctx, cancel := global.TimeoutContext()
defer cancel()
var users = make([]*model.UserWithDetail, 0)
userQuery := ur.ds.
From(goqu.T("user").As("u")).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.Ex{"ud.id": goqu.I("u.id")})).
Select(
"u.id", "u.username", "u.reset_needed", "u.type", "u.enabled",
"ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone",
"ud.unit_service_fee", "ud.service_expiration",
"ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by",
)
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
userQuery = userQuery.Where(
goqu.Or(
goqu.I("u.username").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
),
)
}
userQuery = userQuery.Where(goqu.Ex{"u.type": actualUserType})
userQuery.Order(goqu.I("u.created_at").Desc()).Limit(limit)
userSql, userParams, _ := userQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil {
ur.log.Error("从数据库查询用户列表失败。", zap.Error(err))
return nil, err
}
return users, nil
}
// 更新指定用户的服务有效期限
func (ur _UserRepository) UpdateServiceExpiration(tx pgx.Tx, ctx context.Context, uid string, expiration time.Time) (bool, error) {
ur.log.Info("更新指定用户的服务有效期限。", zap.String("user id", uid))
userDetailUpdateQuery := ur.ds.
Update("user_detail").
Set(goqu.Record{"service_expiration": expiration}).
Where(goqu.Ex{"id": uid})
userDetailSql, userDetailParams, _ := userDetailUpdateQuery.
Prepared(true).ToSQL()
if res, err := tx.Exec(ctx, userDetailSql, userDetailParams...); err != nil {
ur.log.Error("向数据库更新指定用户的服务有效期限失败。", zap.String("user id", uid), zap.Error(err))
return false, err
} else {
return res.RowsAffected() > 0, nil
}
}
// 检索指定用户列表的详细信息
func (ur _UserRepository) RetrieveUsersDetail(uids []string) ([]*model.UserDetail, error) {
ur.log.Info("检索指定用户列表的详细信息。", zap.Strings("user ids", uids))
if len(uids) == 0 {
return make([]*model.UserDetail, 0), nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var users []*model.UserDetail
userQuery := ur.ds.
From("user_detail").
Where(goqu.Ex{"id": uids})
userSql, userParams, _ := userQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil {
ur.log.Error("从数据库查询用户列表失败。", zap.Error(err))
return make([]*model.UserDetail, 0), err
}
return users, nil
}

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