forked from free-lancers/electricity_bill_calc_service
		
	Compare commits
	
		
			141 Commits
		
	
	
		
			fiber
			...
			ZiHangQinB
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| af359f4429 | |||
| ce4c483bcb | |||
| c916301f6b | |||
| 5710a640e8 | |||
| 6b3d3dd93c | |||
| 8fc463bd9d | |||
| f688f50ecb | |||
| f254ec1f3a | |||
| 9b899be33d | |||
| c36bfff05a | |||
| b84c51b18e | |||
| 1dd5f1049d | |||
| 18d48c7fea | |||
| 8ab89bca34 | |||
| b64929c10a | |||
| 1099a7c335 | |||
| 5866882c2d | |||
| 9ad3415cdb | |||
| 39e404451e | |||
| 251c44049a | |||
| b3032638fc | |||
| d8a29e7d17 | |||
| 6fece99e00 | |||
| ab44ff5cc4 | |||
| 61edef5c92 | |||
| 648fc0f370 | |||
|  | 7f2ec68197 | ||
|  | 9dc846c044 | ||
|  | b7eaaffc3a | ||
|  | 1db60a0e4f | ||
|  | 06f86e3cd4 | ||
|  | ccc6cac4df | ||
|  | 3ab505e446 | ||
|  | 26a951f970 | ||
|  | 0a9d5cd121 | ||
|  | 4df3efacd8 | ||
|  | 0c389e440a | ||
|  | a1a72b7204 | ||
|  | a626869f14 | ||
|  | b4ce754c0d | ||
|  | 7806f07766 | ||
|  | 877b8304c3 | ||
|  | b224f51613 | ||
|  | ac36c158c0 | ||
|  | 037e6258d1 | ||
|  | aec1655f1c | ||
|  | 0246eaba27 | ||
|  | e6d9435c14 | ||
|  | 062cc6a9ea | ||
|  | 2792959d1e | ||
|  | fa03bf5dbd | ||
|  | fbe4036389 | ||
|  | b2e4fb809f | ||
|  | 0d73665313 | ||
|  | 316553d81a | ||
|  | 2b5272dad9 | ||
|  | 234e811324 | ||
|  | 541932d62e | ||
|  | e88477a710 | ||
|  | 11bd661e79 | ||
|  | 986562c2c2 | ||
|  | e5dadf06be | ||
|  | db52a140b1 | ||
|  | ce2bb160e1 | ||
|  | 24f2c26d86 | ||
|  | fb388c53c7 | ||
|  | b244fd5823 | ||
|  | 2c303cfba7 | ||
|  | 2d6bff5828 | ||
|  | 577ac9d1dc | ||
|  | 7531a6a5a1 | ||
|  | be270597f5 | ||
|  | 77587b8157 | ||
|  | d97db0cf50 | ||
|  | 2558a83024 | ||
|  | 7da5bd1112 | ||
|  | d4fbf86800 | ||
|  | c9b7dc3aec | ||
|  | 424ba2e839 | ||
|  | 0cffc1b6d1 | ||
|  | ef325aede5 | ||
|  | e2a61d58ac | ||
|  | 542efdac86 | ||
|  | 7c6f211931 | ||
|  | 23e9e2ec4d | ||
|  | 6f0edc54bf | ||
|  | 7bac667c2a | ||
|  | 46ae943653 | ||
|  | 2339e4c725 | ||
|  | e366888608 | ||
|  | cadb2db8c7 | ||
|  | 7476278c52 | ||
|  | bfa9da4a03 | ||
|  | c2a56c8253 | ||
|  | 5d938da53e | ||
|  | 686890b5d8 | ||
|  | 50418f2e30 | ||
|  | 0020776218 | ||
|  | 48753eb3f0 | ||
|  | ab2bcb3cf6 | ||
|  | 005f9020a1 | ||
|  | 2d17bd5f0d | ||
|  | 2f17853dc0 | ||
|  | ea1c1d7829 | ||
|  | c302b32367 | ||
|  | 7f43965f1c | ||
|  | 0c725e99a4 | ||
|  | 33ccd7e0b6 | ||
|  | 40abbcfb8c | ||
|  | 62560e4eeb | ||
|  | 28b1478e9a | ||
|  | 6bf4009338 | ||
|  | bfb59a3626 | ||
|  | 8aa3a054b0 | ||
|  | 1fd5e7b9aa | ||
|  | 85f4d04a7f | ||
|  | c22e7e7dc0 | ||
|  | 98f3bdec0a | ||
|  | f8ef6aba98 | ||
|  | 097e25f070 | ||
|  | cd723e98e3 | ||
|  | 919883f521 | ||
|  | 04dc9c51aa | ||
|  | ee55507e05 | ||
|  | e5b5322e0d | ||
|  | bca0fd777d | ||
|  | 6efe16e2fe | ||
|  | afc0114181 | ||
|  | 9aa32d99b9 | ||
|  | 71f39c8c2f | ||
|  | 73737c3753 | ||
|  | 61fef8d0fa | ||
|  | 523e6215f4 | ||
|  | 47cd27c968 | ||
|  | 0d5457fca3 | ||
|  | 28609df9ec | ||
|  | 31ec847ab2 | ||
|  | 83b0cd89f4 | ||
|  | 1ccd19d78b | ||
|  | 2b30481419 | ||
|  | ac94c578d6 | 
							
								
								
									
										28
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal 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
									
								
							
							
						
						
									
										11
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
										Normal 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
									
								
							
							
						
						
									
										8
									
								
								.idea/electricity_bill_calc_service.iml
									
									
									
										generated
									
									
									
										Normal 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
									
								
							
							
						
						
									
										6
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal 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
									
								
							
							
						
						
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal 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
									
								
							
							
						
						
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal 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> | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| FROM golang:1.19-alpine AS builder | FROM dockerproxy.com/library/golang:1.20-alpine AS builder | ||||||
|  |  | ||||||
| ENV GO111MODULE=on | ENV GO111MODULE=on | ||||||
| ENV GOPROXY="https://goproxy.io" | ENV GOPROXY="https://goproxy.io" | ||||||
| @@ -9,7 +9,7 @@ RUN go mod download && go mod verify | |||||||
| ADD . /app | ADD . /app | ||||||
| RUN CGO_ENABLED=0 GOOS=linux go build -tags=jsoniter -v -o server . | RUN CGO_ENABLED=0 GOOS=linux go build -tags=jsoniter -v -o server . | ||||||
|  |  | ||||||
| FROM alpine:latest AS production | FROM dockerproxy.com/library/alpine:latest AS production | ||||||
| RUN echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.4/main/" > /etc/apk/repositories | RUN echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.4/main/" > /etc/apk/repositories | ||||||
| RUN apk add --no-cache tzdata | RUN apk add --no-cache tzdata | ||||||
| RUN apk update \ | RUN apk update \ | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										40
									
								
								cache/abstract.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								cache/abstract.go
									
									
									
									
										vendored
									
									
								
							| @@ -40,7 +40,7 @@ func Cache[T interface{}](key string, value *T, expires time.Duration) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| // 从Redis缓存中获取一个数据 | // 从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() | 	getCmd := global.Rd.B().Get().Key(key).Build() | ||||||
| 	result := global.Rd.Do(global.Ctx, getCmd) | 	result := global.Rd.Do(global.Ctx, getCmd) | ||||||
| 	if result.Error() != nil { | 	if result.Error() != nil { | ||||||
| @@ -81,23 +81,23 @@ func Delete(key string) (bool, error) { | |||||||
| 	return count > 0, err | 	return count > 0, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func dissembleScan(result rueidis.RedisResult) (int64, []string, error) { | func dissembleScan(result rueidis.RedisResult) (uint64, []string, error) { | ||||||
| 	var ( | 	var ( | ||||||
| 		err    error | 		err    error | ||||||
| 		cursor int64 | 		cursor uint64 | ||||||
| 		keys   = make([]string, 0) | 		keys   = make([]string, 0) | ||||||
| 	) | 	) | ||||||
| 	results, err := result.ToArray() | 	results, err := result.ToArray() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return -1, keys, err | 		return 0, keys, err | ||||||
| 	} | 	} | ||||||
| 	cursor, err = results[0].AsInt64() | 	cursor, err = results[0].AsUint64() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return -1, keys, err | 		return 0, keys, err | ||||||
| 	} | 	} | ||||||
| 	keys, err = results[1].AsStrSlice() | 	keys, err = results[1].AsStrSlice() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return -1, keys, err | 		return 0, keys, err | ||||||
| 	} | 	} | ||||||
| 	return cursor, keys, err | 	return cursor, keys, err | ||||||
| } | } | ||||||
| @@ -106,7 +106,7 @@ func dissembleScan(result rueidis.RedisResult) (int64, []string, error) { | |||||||
| func DeleteAll(pattern string) error { | func DeleteAll(pattern string) error { | ||||||
| 	var ( | 	var ( | ||||||
| 		err    error | 		err    error | ||||||
| 		cursor int64 | 		cursor uint64 | ||||||
| 		keys   = make([]string, 0) | 		keys   = make([]string, 0) | ||||||
| 		sKeys  []string | 		sKeys  []string | ||||||
| 	) | 	) | ||||||
| @@ -137,3 +137,27 @@ func CacheKey(category string, ids ...string) string { | |||||||
| 	} | 	} | ||||||
| 	return b.String() | 	return b.String() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type ToString[T any] interface { | ||||||
|  | 	ToString() string | ||||||
|  | 	*T | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 用于生成一个内容可以为空的Redis缓存键,这个键的类型必须实现了`ToString`接口。 | ||||||
|  | func NullableConditionKey[P any, T ToString[P]](value T, defaultStr ...string) string { | ||||||
|  | 	defaultStr = append(defaultStr, "UNDEF") | ||||||
|  | 	if value == nil { | ||||||
|  | 		return defaultStr[0] | ||||||
|  | 	} else { | ||||||
|  | 		return value.ToString() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 用于生成一个内容可以为空的字符串指针类型的Redis缓存键。 | ||||||
|  | func NullableStringKey(value *string, defaultStr ...string) string { | ||||||
|  | 	defaultStr = append(defaultStr, "UNDEF") | ||||||
|  | 	if value == nil { | ||||||
|  | 		return defaultStr[0] | ||||||
|  | 	} | ||||||
|  | 	return *value | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								cache/count.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								cache/count.go
									
									
									
									
										vendored
									
									
								
							| @@ -10,7 +10,7 @@ type _CountRecord struct { | |||||||
| 	Count int64 | 	Count int64 | ||||||
| } | } | ||||||
|  |  | ||||||
| func assembleCountKey(entityName string, additional ...string) string { | func AssembleCountKey(entityName string, additional ...string) string { | ||||||
| 	var keys = make([]string, 0) | 	var keys = make([]string, 0) | ||||||
| 	keys = append(keys, strings.ToUpper(entityName)) | 	keys = append(keys, strings.ToUpper(entityName)) | ||||||
| 	keys = append(keys, additional...) | 	keys = append(keys, additional...) | ||||||
| @@ -24,7 +24,7 @@ func assembleCountKey(entityName string, additional ...string) string { | |||||||
|  |  | ||||||
| // 向缓存中缓存模型名称明确的包含指定条件的实体记录数量 | // 向缓存中缓存模型名称明确的包含指定条件的实体记录数量 | ||||||
| func CacheCount(relationNames []string, entityName string, count int64, conditions ...string) error { | func CacheCount(relationNames []string, entityName string, count int64, conditions ...string) error { | ||||||
| 	countKey := assembleCountKey(entityName, conditions...) | 	countKey := AssembleCountKey(entityName, conditions...) | ||||||
| 	cacheInstance := &_CountRecord{Count: count} | 	cacheInstance := &_CountRecord{Count: count} | ||||||
| 	err := Cache(countKey, cacheInstance, 5*time.Minute) | 	err := Cache(countKey, cacheInstance, 5*time.Minute) | ||||||
| 	for _, relationName := range relationNames { | 	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) { | func RetrieveCount(entityName string, condtions ...string) (int64, error) { | ||||||
| 	countKey := assembleCountKey(entityName, condtions...) | 	countKey := AssembleCountKey(entityName, condtions...) | ||||||
| 	exist, err := Exists(countKey) | 	exist, err := Exists(countKey) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return -1, err | 		return -1, err | ||||||
| @@ -43,7 +43,7 @@ func RetreiveCount(entityName string, condtions ...string) (int64, error) { | |||||||
| 	if !exist { | 	if !exist { | ||||||
| 		return -1, nil | 		return -1, nil | ||||||
| 	} | 	} | ||||||
| 	instance, err := Retreive[_CountRecord](countKey) | 	instance, err := Retrieve[_CountRecord](countKey) | ||||||
| 	if instance != nil && err == nil { | 	if instance != nil && err == nil { | ||||||
| 		return instance.Count, nil | 		return instance.Count, nil | ||||||
| 	} else { | 	} else { | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								cache/entity.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								cache/entity.go
									
									
									
									
										vendored
									
									
								
							| @@ -6,7 +6,7 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func assembleEntityKey(entityName, id string) string { | func AssembleEntityKey(entityName, id string) string { | ||||||
| 	var keys = make([]string, 0) | 	var keys = make([]string, 0) | ||||||
| 	keys = append(keys, strings.ToUpper(entityName), id) | 	keys = append(keys, strings.ToUpper(entityName), id) | ||||||
| 	var b strings.Builder | 	var b strings.Builder | ||||||
| @@ -19,7 +19,7 @@ func assembleEntityKey(entityName, id string) string { | |||||||
|  |  | ||||||
| // 缓存模型名称明确的,使用ID进行检索的实体内容。 | // 缓存模型名称明确的,使用ID进行检索的实体内容。 | ||||||
| func CacheEntity[T any](instance T, relationNames []string, entityName, id string) error { | func CacheEntity[T any](instance T, relationNames []string, entityName, id string) error { | ||||||
| 	entityKey := assembleEntityKey(entityName, id) | 	entityKey := AssembleEntityKey(entityName, id) | ||||||
| 	err := Cache(entityKey, &instance, 5*time.Minute) | 	err := Cache(entityKey, &instance, 5*time.Minute) | ||||||
| 	for _, relationName := range relationNames { | 	for _, relationName := range relationNames { | ||||||
| 		CacheRelation(relationName, STORE_TYPE_KEY, entityKey) | 		CacheRelation(relationName, STORE_TYPE_KEY, entityKey) | ||||||
| @@ -28,15 +28,15 @@ func CacheEntity[T any](instance T, relationNames []string, entityName, id strin | |||||||
| } | } | ||||||
|  |  | ||||||
| // 从缓存中取出模型名称明确的,使用ID进行检索的实体内容。 | // 从缓存中取出模型名称明确的,使用ID进行检索的实体内容。 | ||||||
| func RetreiveEntity[T any](entityName, id string) (*T, error) { | func RetrieveEntity[T any](entityName, id string) (*T, error) { | ||||||
| 	entityKey := assembleEntityKey(entityName, id) | 	entityKey := AssembleEntityKey(entityName, id) | ||||||
| 	instance, err := Retreive[T](entityKey) | 	instance, err := Retrieve[T](entityKey) | ||||||
| 	return instance, err | 	return instance, err | ||||||
| } | } | ||||||
|  |  | ||||||
| // 精确的从缓存中删除指定模型名称、指定ID的实体内容。 | // 精确的从缓存中删除指定模型名称、指定ID的实体内容。 | ||||||
| func AbolishSpecificEntity(entityName, id string) (bool, error) { | func AbolishSpecificEntity(entityName, id string) (bool, error) { | ||||||
| 	entityKey := assembleEntityKey(entityName, id) | 	entityKey := AssembleEntityKey(entityName, id) | ||||||
| 	return Delete(entityKey) | 	return Delete(entityKey) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								cache/exists.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								cache/exists.go
									
									
									
									
										vendored
									
									
								
							| @@ -8,7 +8,7 @@ import ( | |||||||
| 	"github.com/samber/lo" | 	"github.com/samber/lo" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func assembleExistsKey(entityName string, additional ...string) string { | func AssembleExistsKey(entityName string, additional ...string) string { | ||||||
| 	var keys = make([]string, 0) | 	var keys = make([]string, 0) | ||||||
| 	keys = append(keys, strings.ToUpper(entityName)) | 	keys = append(keys, strings.ToUpper(entityName)) | ||||||
| 	keys = append(keys, additional...) | 	keys = append(keys, additional...) | ||||||
| @@ -22,7 +22,7 @@ func assembleExistsKey(entityName string, additional ...string) string { | |||||||
|  |  | ||||||
| // 缓存模型名称明确的、包含指定ID以及一些附加条件的记录 | // 缓存模型名称明确的、包含指定ID以及一些附加条件的记录 | ||||||
| func CacheExists(relationNames []string, entityName string, conditions ...string) error { | 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) | 	err := Cache(existskey, lo.ToPtr(true), 5*time.Minute) | ||||||
| 	for _, relationName := range relationNames { | 	for _, relationName := range relationNames { | ||||||
| 		CacheRelation(relationName, STORE_TYPE_KEY, existskey) | 		CacheRelation(relationName, STORE_TYPE_KEY, existskey) | ||||||
| @@ -32,7 +32,7 @@ func CacheExists(relationNames []string, entityName string, conditions ...string | |||||||
|  |  | ||||||
| // 从缓存中获取模型名称明确、包含指定ID以及一些附加条件的实体是否存在的标记,函数在返回false时不保证数据库中相关记录也不存在 | // 从缓存中获取模型名称明确、包含指定ID以及一些附加条件的实体是否存在的标记,函数在返回false时不保证数据库中相关记录也不存在 | ||||||
| func CheckExists(entityName string, condtions ...string) (bool, error) { | func CheckExists(entityName string, condtions ...string) (bool, error) { | ||||||
| 	existsKey := assembleExistsKey(entityName, condtions...) | 	existsKey := AssembleExistsKey(entityName, condtions...) | ||||||
| 	return Exists(existsKey) | 	return Exists(existsKey) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								cache/relation.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								cache/relation.go
									
									
									
									
										vendored
									
									
								
							| @@ -15,13 +15,13 @@ const ( | |||||||
| 	STORE_TYPE_HASH = "HASH" | 	STORE_TYPE_HASH = "HASH" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func assembleRelationKey(relationName string) string { | func AssembleRelationKey(relationName string) string { | ||||||
| 	var keys = make([]string, 0) | 	var keys = make([]string, 0) | ||||||
| 	keys = append(keys, strings.ToUpper(relationName)) | 	keys = append(keys, strings.ToUpper(relationName)) | ||||||
| 	return CacheKey(TAG_RELATION, keys...) | 	return CacheKey(TAG_RELATION, keys...) | ||||||
| } | } | ||||||
|  |  | ||||||
| func assembleRelationIdentity(storeType, key string, field ...string) string { | func AssembleRelationIdentity(storeType, key string, field ...string) string { | ||||||
| 	var identity = make([]string, 0) | 	var identity = make([]string, 0) | ||||||
| 	identity = append(identity, storeType, key) | 	identity = append(identity, storeType, key) | ||||||
| 	identity = append(identity, field...) | 	identity = append(identity, field...) | ||||||
| @@ -30,8 +30,8 @@ func assembleRelationIdentity(storeType, key string, field ...string) string { | |||||||
|  |  | ||||||
| // 向缓存中保存与指定关联名称相关联的键的名称以及键的类型和子字段的组成。 | // 向缓存中保存与指定关联名称相关联的键的名称以及键的类型和子字段的组成。 | ||||||
| func CacheRelation(relationName, storeType, key string, field ...string) error { | func CacheRelation(relationName, storeType, key string, field ...string) error { | ||||||
| 	relationKey := assembleRelationKey(relationName) | 	relationKey := AssembleRelationKey(relationName) | ||||||
| 	relationIdentity := assembleRelationIdentity(storeType, key, field...) | 	relationIdentity := AssembleRelationIdentity(storeType, key, field...) | ||||||
| 	cmd := global.Rd.B().Sadd().Key(relationKey).Member(relationIdentity).Build() | 	cmd := global.Rd.B().Sadd().Key(relationKey).Member(relationIdentity).Build() | ||||||
| 	result := global.Rd.Do(global.Ctx, cmd) | 	result := global.Rd.Do(global.Ctx, cmd) | ||||||
| 	return result.Error() | 	return result.Error() | ||||||
| @@ -39,7 +39,7 @@ func CacheRelation(relationName, storeType, key string, field ...string) error { | |||||||
|  |  | ||||||
| // 从缓存中清理指定的关联键 | // 从缓存中清理指定的关联键 | ||||||
| func AbolishRelation(relationName string) error { | func AbolishRelation(relationName string) error { | ||||||
| 	relationKey := assembleRelationKey(relationName) | 	relationKey := AssembleRelationKey(relationName) | ||||||
| 	cmd := global.Rd.B().Smembers().Key(relationKey).Build() | 	cmd := global.Rd.B().Smembers().Key(relationKey).Build() | ||||||
| 	relationItems, err := global.Rd.Do(global.Ctx, cmd).AsStrSlice() | 	relationItems, err := global.Rd.Do(global.Ctx, cmd).AsStrSlice() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -74,7 +74,7 @@ func AbolishRelation(relationName string) error { | |||||||
| func ClearOrphanRelationItems() error { | func ClearOrphanRelationItems() error { | ||||||
| 	var ( | 	var ( | ||||||
| 		err    error | 		err    error | ||||||
| 		cursor int64 | 		cursor uint64 | ||||||
| 		keys   = make([]string, 0) | 		keys   = make([]string, 0) | ||||||
| 		sKeys  []string | 		sKeys  []string | ||||||
| 	) | 	) | ||||||
|   | |||||||
							
								
								
									
										62
									
								
								cache/search.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										62
									
								
								cache/search.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,12 +1,17 @@ | |||||||
| package cache | package cache | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"electricity_bill_calc/logger" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"go.uber.org/zap" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func assembleSearchKey(entityName string, additional ...string) string { | var log = logger.Named("Cache") | ||||||
|  |  | ||||||
|  | func AssembleSearchKey(entityName string, additional ...string) string { | ||||||
| 	var keys = make([]string, 0) | 	var keys = make([]string, 0) | ||||||
| 	keys = append(keys, strings.ToUpper(entityName)) | 	keys = append(keys, strings.ToUpper(entityName)) | ||||||
| 	keys = append(keys, additional...) | 	keys = append(keys, additional...) | ||||||
| @@ -20,7 +25,7 @@ func assembleSearchKey(entityName string, additional ...string) string { | |||||||
|  |  | ||||||
| // 缓存模型名称明确的,使用或者包含非ID检索条件的实体内容。 | // 缓存模型名称明确的,使用或者包含非ID检索条件的实体内容。 | ||||||
| func CacheSearch[T any](instance T, relationNames []string, entityName string, conditions ...string) error { | func CacheSearch[T any](instance T, relationNames []string, entityName string, conditions ...string) error { | ||||||
| 	searchKey := assembleSearchKey(entityName, conditions...) | 	searchKey := AssembleSearchKey(entityName, conditions...) | ||||||
| 	err := Cache(searchKey, &instance, 5*time.Minute) | 	err := Cache(searchKey, &instance, 5*time.Minute) | ||||||
| 	for _, relationName := range relationNames { | 	for _, relationName := range relationNames { | ||||||
| 		CacheRelation(relationName, STORE_TYPE_KEY, searchKey) | 		CacheRelation(relationName, STORE_TYPE_KEY, searchKey) | ||||||
| @@ -29,9 +34,9 @@ func CacheSearch[T any](instance T, relationNames []string, entityName string, c | |||||||
| } | } | ||||||
|  |  | ||||||
| // 从缓存中取得模型名称明确的,使用或者包含非ID检索条件的实体内容。 | // 从缓存中取得模型名称明确的,使用或者包含非ID检索条件的实体内容。 | ||||||
| func RetreiveSearch[T any](entityName string, conditions ...string) (*T, error) { | func RetrieveSearch[T any](entityName string, conditions ...string) (*T, error) { | ||||||
| 	searchKey := assembleSearchKey(entityName, conditions...) | 	searchKey := AssembleSearchKey(entityName, conditions...) | ||||||
| 	instance, err := Retreive[T](searchKey) | 	instance, err := Retrieve[T](searchKey) | ||||||
| 	return instance, err | 	return instance, err | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -40,3 +45,50 @@ func AbolishSearch(entityName string) error { | |||||||
| 	pattern := fmt.Sprintf("%s:%s:*", TAG_SEARCH, strings.ToUpper(entityName)) | 	pattern := fmt.Sprintf("%s:%s:*", TAG_SEARCH, strings.ToUpper(entityName)) | ||||||
| 	return DeleteAll(pattern) | 	return DeleteAll(pattern) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 向缓存中保存指定模型名称的分页检索结果,会同时采用`CacheCount`中的方法保存检索结果的总数量。 | ||||||
|  | func CachePagedSearch[T any](instance T, total int64, relationNames []string, entityName string, conditions ...string) error { | ||||||
|  | 	searchKey := AssembleSearchKey(entityName, conditions...) | ||||||
|  | 	countKey := AssembleCountKey(entityName, conditions...) | ||||||
|  | 	err := Cache(searchKey, &instance, 5*time.Minute) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	cacheInstance := &_CountRecord{Count: total} | ||||||
|  | 	err = Cache(countKey, cacheInstance, 5*time.Minute) | ||||||
|  | 	for _, relationName := range relationNames { | ||||||
|  | 		CacheRelation(relationName, STORE_TYPE_KEY, searchKey) | ||||||
|  | 		CacheRelation(relationName, STORE_TYPE_KEY, countKey) | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 从缓存中获取指定模型名称的分页检索结果,会同时采用`RetrieveCount`中的方法获取检索结果的总数量。 | ||||||
|  | func RetrievePagedSearch[T any](entityName string, conditions ...string) (*T, int64, error) { | ||||||
|  | 	searchKey := AssembleSearchKey(entityName, conditions...) | ||||||
|  | 	countKey := AssembleCountKey(entityName, conditions...) | ||||||
|  | 	instance, err := Retrieve[T](searchKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, -1, err | ||||||
|  | 	} | ||||||
|  | 	count, err := Retrieve[_CountRecord](countKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, -1, err | ||||||
|  | 	} | ||||||
|  | 	if instance == nil || count == nil { | ||||||
|  | 		log.Warn("检索结果或者检索总数为空。", zap.String("searchKey", searchKey), zap.String("countKey", countKey)) | ||||||
|  | 		return nil, -1, nil | ||||||
|  | 	} | ||||||
|  | 	return instance, count.Count, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 从缓存中删除指定模型名称的分页检索结果,会同时采用`AbolishCountEntity`中的方法删除检索结果的总数量。 | ||||||
|  | func AbolishPagedSearch(entityName string) error { | ||||||
|  | 	pattern := fmt.Sprintf("%s:%s:*", TAG_SEARCH, strings.ToUpper(entityName)) | ||||||
|  | 	err := DeleteAll(pattern) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	pattern = fmt.Sprintf("%s:%s:*", TAG_COUNT, strings.ToUpper(entityName)) | ||||||
|  | 	return DeleteAll(pattern) | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								cache/session.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								cache/session.go
									
									
									
									
										vendored
									
									
								
							| @@ -21,9 +21,9 @@ func CacheSession(session *model.Session) error { | |||||||
| 	return Cache(key, session, config.ServiceSettings.MaxSessionLife) | 	return Cache(key, session, config.ServiceSettings.MaxSessionLife) | ||||||
| } | } | ||||||
|  |  | ||||||
| func RetreiveSession(token string) (*model.Session, error) { | func RetrieveSession(token string) (*model.Session, error) { | ||||||
| 	key := SessionKey(token) | 	key := SessionKey(token) | ||||||
| 	return Retreive[model.Session](key) | 	return Retrieve[model.Session](key) | ||||||
| } | } | ||||||
|  |  | ||||||
| func HasSession(token string) (bool, error) { | func HasSession(token string) (bool, error) { | ||||||
|   | |||||||
| @@ -31,8 +31,14 @@ type RedisSetting struct { | |||||||
|  |  | ||||||
| type ServiceSetting struct { | type ServiceSetting struct { | ||||||
| 	MaxSessionLife time.Duration | 	MaxSessionLife time.Duration | ||||||
| 	ItemsPageSize  int | 	ItemsPageSize  uint | ||||||
| 	CacheLifeTime  time.Duration | 	CacheLifeTime  time.Duration | ||||||
|  | 	HostSerial     int64 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //读取基准线损率 | ||||||
|  | type BaseLossSetting struct { | ||||||
|  | 	Base string | ||||||
| } | } | ||||||
|  |  | ||||||
| // 定义全局变量 | // 定义全局变量 | ||||||
| @@ -41,6 +47,7 @@ var ( | |||||||
| 	DatabaseSettings *DatabaseSetting | 	DatabaseSettings *DatabaseSetting | ||||||
| 	RedisSettings    *RedisSetting | 	RedisSettings    *RedisSetting | ||||||
| 	ServiceSettings  *ServiceSetting | 	ServiceSettings  *ServiceSetting | ||||||
|  | 	BaseLoss         *BaseLossSetting | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // 读取配置到全局变量 | // 读取配置到全局变量 | ||||||
| @@ -68,5 +75,10 @@ func SetupSetting() error { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	err = s.ReadSection("BaselineLineLossRatio", &BaseLoss) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,8 +3,12 @@ package controller | |||||||
| import ( | import ( | ||||||
| 	"electricity_bill_calc/exceptions" | 	"electricity_bill_calc/exceptions" | ||||||
| 	"electricity_bill_calc/model" | 	"electricity_bill_calc/model" | ||||||
|  | 	"electricity_bill_calc/repository" | ||||||
|  | 	"electricity_bill_calc/response" | ||||||
|  | 	"net/http" | ||||||
|  |  | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"go.uber.org/zap" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func _retreiveSession(c *fiber.Ctx) (*model.Session, error) { | func _retreiveSession(c *fiber.Ctx) (*model.Session, error) { | ||||||
| @@ -18,3 +22,26 @@ func _retreiveSession(c *fiber.Ctx) (*model.Session, error) { | |||||||
| 	} | 	} | ||||||
| 	return userSession, nil | 	return userSession, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 检查当前用户是否拥有指定园区,在判断完成之后直接产生响应 | ||||||
|  | func checkParkBelongs(parkId string, logger *zap.Logger, c *fiber.Ctx, result *response.Result) (bool, error) { | ||||||
|  | 	session := c.Locals("session") | ||||||
|  | 	if session == nil { | ||||||
|  | 		logger.Error("用户会话不存在。") | ||||||
|  | 		return false, result.Unauthorized("用户会话不存在。") | ||||||
|  | 	} | ||||||
|  | 	userSession, ok := session.(*model.Session) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false, result.Unauthorized("用户会话格式不正确,需要重新登录") | ||||||
|  | 	} | ||||||
|  | 	ok, err := repository.ParkRepository.IsParkBelongs(parkId, userSession.Uid) | ||||||
|  | 	switch { | ||||||
|  | 	case err != nil: | ||||||
|  | 		logger.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", userSession.Uid), zap.Error(err)) | ||||||
|  | 		return false, result.Error(http.StatusInternalServerError, err.Error()) | ||||||
|  | 	case err == nil && !ok: | ||||||
|  | 		logger.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", userSession.Uid)) | ||||||
|  | 		return false, result.Forbidden("您无权访问该园区。") | ||||||
|  | 	} | ||||||
|  | 	return true, nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,93 +1,97 @@ | |||||||
| package controller | package controller | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"electricity_bill_calc/logger" | ||||||
| 	"electricity_bill_calc/model" | 	"electricity_bill_calc/model" | ||||||
|  | 	"electricity_bill_calc/repository" | ||||||
| 	"electricity_bill_calc/response" | 	"electricity_bill_calc/response" | ||||||
| 	"electricity_bill_calc/security" |  | ||||||
| 	"electricity_bill_calc/service" | 	"electricity_bill_calc/service" | ||||||
|  | 	"electricity_bill_calc/types" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strconv" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
| 	"github.com/shopspring/decimal" | 	"go.uber.org/zap" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func InitializeChargesController(app *fiber.App) { | var chargeLog = logger.Named("Handler", "Charge") | ||||||
| 	app.Get("/charges", security.OPSAuthorize, listAllCharges) |  | ||||||
| 	app.Post("/charge", security.OPSAuthorize, recordNewCharge) | func InitializeChargeHandlers(router *fiber.App) { | ||||||
| 	app.Put("/charge/:uid/:seq", security.OPSAuthorize, modifyChargeState) | 	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) | 	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 { | 	if err != nil { | ||||||
| 		return result.NotAccept("查询参数[page]格式不正确。") | 		chargeLog.Error("检索用户的充值记录列表失败。", zap.Error(err)) | ||||||
|  | 		return result.Error(http.StatusInternalServerError, err.Error()) | ||||||
| 	} | 	} | ||||||
| 	requestKeyword := c.Query("keyword", "") | 	return result.Success( | ||||||
| 	requestBeginDate := c.Query("begin", "") | 		"已经获取到符合条件的计费记录。", | ||||||
| 	requestEndDate := c.Query("end", "") | 		response.NewPagedResponse(page, total).ToMap(), | ||||||
| 	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(), |  | ||||||
| 		fiber.Map{"records": charges}, | 		fiber.Map{"records": charges}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|  |  | ||||||
| type _NewChargeFormData struct { | // 创建一条新的用户充值记录 | ||||||
| 	UserId   string              `json:"userId" form:"userId"` | func createNewUserChargeRecord(c *fiber.Ctx) error { | ||||||
| 	Fee      decimal.NullDecimal `json:"fee" form:"fee"` | 	chargeLog.Info("创建一条新的用户充值记录。") | ||||||
| 	Discount decimal.NullDecimal `json:"discount" form:"discount"` |  | ||||||
| 	Amount   decimal.NullDecimal `json:"amount" form:"amount"` |  | ||||||
| 	ChargeTo model.Date          `json:"chargeTo" form:"chargeTo"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func recordNewCharge(c *fiber.Ctx) error { |  | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	formData := new(_NewChargeFormData) | 	createionForm := new(model.ChargeRecordCreationForm) | ||||||
| 	if err := c.BodyParser(formData); err != nil { | 	if err := c.BodyParser(createionForm); err != nil { | ||||||
| 		return result.UnableToParse("无法解析提交的数据。") | 		chargeLog.Error("无法解析创建充值记录的请求数据。", zap.Error(err)) | ||||||
|  | 		return result.Error(http.StatusBadRequest, err.Error()) | ||||||
| 	} | 	} | ||||||
| 	currentTime := time.Now() | 	fee, _ := createionForm.Fee.Decimal.Float64() | ||||||
| 	newRecord := &model.UserCharge{ | 	discount, _ := createionForm.Discount.Decimal.Float64() | ||||||
| 		UserId:    formData.UserId, | 	amount, _ := createionForm.Amount.Decimal.Float64() | ||||||
| 		Fee:       formData.Fee, | 	ok, err := service.ChargeService.RecordUserCharge( | ||||||
| 		Discount:  formData.Discount, | 		createionForm.UserId, | ||||||
| 		Amount:    formData.Amount, | 		&fee, | ||||||
| 		Settled:   true, | 		&discount, | ||||||
| 		SettledAt: ¤tTime, | 		&amount, | ||||||
| 		ChargeTo:  formData.ChargeTo, | 		createionForm.ChargeTo, | ||||||
| 	} | 		true, | ||||||
| 	err := service.ChargeService.CreateChargeRecord(newRecord, true) | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		chargeLog.Error("创建用户充值记录失败。", zap.Error(err)) | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 		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 modifyUserChargeState(c *fiber.Ctx) error { | ||||||
| } | 	chargeLog.Info("改变用户充值记录的状态。") | ||||||
|  |  | ||||||
| func modifyChargeState(c *fiber.Ctx) error { |  | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	formData := new(_StateChangeFormData) | 	uid := c.Params("uid") | ||||||
| 	if err := c.BodyParser(formData); err != nil { | 	seq, err := c.ParamsInt("seq") | ||||||
| 		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) |  | ||||||
| 	if err != nil { | 	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.Error(http.StatusInternalServerError, err.Error()) | ||||||
| 	} | 	} | ||||||
| 	return result.Updated("指定用户服务延期记录状态已经更新。") | 	if !ok { | ||||||
|  | 		chargeLog.Error("取消用户充值记录失败。") | ||||||
|  | 		return result.NotAccept("取消用户充值记录失败。") | ||||||
|  | 	} else { | ||||||
|  | 		return result.Success("取消用户充值记录成功。") | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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
									
								
							
							
						
						
									
										24
									
								
								controller/foundation.go
									
									
									
									
									
										Normal 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}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
| @@ -1,176 +1,132 @@ | |||||||
| package controller | package controller | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"electricity_bill_calc/exceptions" | 	"electricity_bill_calc/logger" | ||||||
| 	"electricity_bill_calc/response" | 	"electricity_bill_calc/response" | ||||||
| 	"electricity_bill_calc/security" | 	"electricity_bill_calc/security" | ||||||
| 	"electricity_bill_calc/service" | 	"electricity_bill_calc/service" | ||||||
| 	"net/http" | 	"errors" | ||||||
|  |  | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"go.uber.org/zap" | ||||||
|  | 	"net/http" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func InitializeGodModeController(router *fiber.App) { | var GmLog = logger.Named("Handler", "GM") | ||||||
| 	gmR := router.Group("/gm") |  | ||||||
| 	{ | func InitializeGmController(router *fiber.App) { | ||||||
| 		gmR.Delete("/report/:rid/summary", security.SingularityAuthorize, gmResetReportSummary) | 	router.Delete("/gm/tenement", security.SingularityAuthorize, deleteTenement) | ||||||
| 		gmR.Delete("/report/:rid/maintenance", security.SingularityAuthorize, gmResetReportMaintenance) | 	router.Delete("/gm/park", security.SingularityAuthorize, deletePark) | ||||||
| 		gmR.Delete("/report/:rid/meters", security.SingularityAuthorize, gmResetReportEndUserRecord) | 	router.Delete("/gm/report", security.SingularityAuthorize, deleteReports) | ||||||
| 		gmR.Post("/report/:rid/meters", security.SingularityAuthorize, gmResynchronizeReportEndUserRecord) | 	router.Delete("/gm/tenement/meter", security.SingularityAuthorize, deleteTenementMeterRelations) | ||||||
| 		gmR.Delete("/report/:rid", security.SingularityAuthorize, gmResetReport) | 	router.Delete("/gm/enterprise", security.SingularityAuthorize, deleteEnterprise) | ||||||
| 		gmR.Delete("/report/:rid/force", security.SingularityAuthorize, gmDeleteReport) | 	router.Delete("/gm/meter/pooling", security.SingularityAuthorize, deleteMeterPoolingRelations) | ||||||
| 		gmR.Delete("/park/:pid/maintenance/:mid", security.SingularityAuthorize, gmDeleteSpecificMaintenance) | 	router.Delete("gm/meter", security.SingularityAuthorize, deleteMeters) | ||||||
| 		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) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func gmResetReportSummary(c *fiber.Ctx) error { | //用于将参数转化为切片 | ||||||
| 	result := response.NewResult(c) | func getQueryValues(c *fiber.Ctx, paramName string) []string { | ||||||
| 	requestReportId := c.Params("rid") | 	values := c.Request().URI().QueryArgs().PeekMulti(paramName) | ||||||
| 	done, err := service.GodModeService.ClearReportSummary(requestReportId) | 	result := make([]string, len(values)) | ||||||
| 	if err != nil { | 	for i, v := range values { | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 		result[i] = string(v) | ||||||
| 	} | 	} | ||||||
| 	if !done { | 	return result | ||||||
| 		return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的园区总览部分。") |  | ||||||
| 	} |  | ||||||
| 	return result.Success("指定报表的园区总览已经重置。") |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func gmResetReportMaintenance(c *fiber.Ctx) error { | func deleteTenement(c *fiber.Ctx) error { | ||||||
|  | 	park := c.Query("park", "") | ||||||
|  | 	tenements := getQueryValues(c, "tenements") | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestReportId := c.Params("rid") | 	GmLog.Info("[天神模式]删除指定园区中的商户", zap.String("park", park), zap.Strings("tenements", tenements)) | ||||||
| 	done, err := service.GodModeService.ClearReportMaintenances(requestReportId) |  | ||||||
|  | 	err := service.GMService.DeleteTenements(park, tenements) | ||||||
| 	if err != nil { | 	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) | 	result := response.NewResult(c) | ||||||
| 	requestReportId := c.Params("rid") | 	GmLog.Info("[天神模式]删除指定园区", zap.Strings("parks", parks)) | ||||||
| 	done, err := service.GodModeService.ResynchronizeEndUser(requestReportId) |  | ||||||
|  | 	if len(parks) < 0 { | ||||||
|  | 		GmLog.Info("[天神模式]用户未指派园区参数或者未指定需要删除的园区。") | ||||||
|  | 		return result.Error(http.StatusBadRequest, error.Error(errors.New("必须至少指定一个需要删除的园区!"))) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := service.GMService.DeleteParks(parks) | ||||||
| 	if err != nil { | 	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.Success("指定园区已经删除。") | ||||||
| 		return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的抄表记录基本档案。") |  | ||||||
| 	} |  | ||||||
| 	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) | 	result := response.NewResult(c) | ||||||
| 	requestReportId := c.Params("rid") | 	GmLog.Info("[天神模式]删除符合条件的报表。", zap.Strings("reports", reports)) | ||||||
| 	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("指定报表的抄表记录已经重置。") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func gmResetReport(c *fiber.Ctx) error { | 	err := service.GMService.DeleteReports(pid, reports) | ||||||
| 	result := response.NewResult(c) |  | ||||||
| 	requestReportId := c.Params("rid") |  | ||||||
| 	done, err := service.GodModeService.ResetReport(requestReportId) |  | ||||||
| 	if err != nil { | 	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("指定报表已经重置。") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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, "未能成功删除指定报表。") |  | ||||||
| 	} | 	} | ||||||
| 	return result.Success("指定报表已经删除。") | 	return result.Success("指定报表已经删除。") | ||||||
| } | } | ||||||
|  |  | ||||||
| func gmDeleteSpecificMaintenance(c *fiber.Ctx) error { | func deleteEnterprise(c *fiber.Ctx) error { | ||||||
|  | 	uid := c.Query("uid") | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestParkId := c.Params("pid") | 	GmLog.Info("[天神模式]删除指定企业用户", zap.String("uid", uid)) | ||||||
| 	requestMaintenanceId := c.Params("mid") |  | ||||||
| 	done, err := service.GodModeService.RemoveSpecificMaintenance(requestParkId, requestMaintenanceId) | 	err := service.GMService.DeleteEnterprises(uid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 		GmLog.Error("[天神模式]删除指定企业用户失败", zap.Error(err)) | ||||||
|  | 		return result.Error(500, "删除指定企业用户失败。") | ||||||
| 	} | 	} | ||||||
| 	if !done { | 	return result.Success("指定企业用户已经删除。") | ||||||
| 		return result.Error(http.StatusInternalServerError, "未能成功删除指定的维护费用记录。") |  | ||||||
| 	} |  | ||||||
| 	return result.Success("指定维护费用记录已经删除。") |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func gmDeleteAllMaintenance(c *fiber.Ctx) error { | func deleteTenementMeterRelations(c *fiber.Ctx) error { | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestParkId := c.Params("pid") | 	parkId := c.Query("park") | ||||||
| 	done, err := service.GodModeService.RemoveAllMaintenance(requestParkId) | 	tId := getQueryValues(c, "tenements") | ||||||
| 	if err != nil { | 	metersId := getQueryValues(c, "meters") | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 	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.Success("删除成功") | ||||||
| 		return result.Error(http.StatusInternalServerError, "未能成功删除全部维护费用记录。") |  | ||||||
| 	} |  | ||||||
| 	return result.Success("全部维护费用记录已经删除。") |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func gmDeleteAllMeters(c *fiber.Ctx) error { | func deleteMeterPoolingRelations(c *fiber.Ctx) error { | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestParkId := c.Params("pid") | 	parkId := c.Query("park") | ||||||
| 	done, err := service.GodModeService.RemoveAllMeters(requestParkId) | 	mId := getQueryValues(c, "meters") | ||||||
| 	if err != nil { | 	GmLog.Info("[天神模式]删除指定园区中的表计公摊关系", zap.String("park id", parkId)) | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 	if err := service.GMService.DeleteMeterPooling(parkId, mId); err != nil { | ||||||
|  | 		meterLog.Error("[天神模式]删除指定园区中的表计公摊关系失败", zap.Error(err)) | ||||||
|  | 		return result.Error(500, "删除指定园区中的表计公摊关系失败。") | ||||||
| 	} | 	} | ||||||
| 	if !done { | 	return result.Success("指定表计公摊关系已经删除。") | ||||||
| 		return result.Error(http.StatusInternalServerError, "未能成功删除全部终端表计档案记录。") |  | ||||||
| 	} |  | ||||||
| 	return result.Success("全部终端表计档案记录已经删除。") |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func gmDeletePark(c *fiber.Ctx) error { | func deleteMeters(c *fiber.Ctx) error { | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestParkId := c.Params("pid") | 	parkId := c.Query("park") | ||||||
| 	done, err := service.GodModeService.RemovePark(requestParkId) | 	mId := getQueryValues(c, "meters") | ||||||
| 	if err != nil { | 	GmLog.Info("[天神模式]删除指定园区中的表计", zap.String("park id", parkId)) | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 	if err := service.GMService.DeleteMeters(parkId, mId); err != nil { | ||||||
|  | 		meterLog.Error("[天神模式]删除指定园区中的表计失败", zap.Error(err)) | ||||||
|  | 		return result.Error(500, "删除指定园区中的表计失败。") | ||||||
| 	} | 	} | ||||||
| 	if !done { | 	return result.Success("指定表计已经删除。") | ||||||
| 		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("指定的用户及其关联信息已经删除。") |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										227
									
								
								controller/invoice.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								controller/invoice.go
									
									
									
									
									
										Normal 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("已经删除了指定的发票记录。") | ||||||
|  | } | ||||||
| @@ -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
									
								
							
							
						
						
									
										502
									
								
								controller/meter.go
									
									
									
									
									
										Normal 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}) | ||||||
|  | } | ||||||
| @@ -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)}, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| @@ -1,185 +1,331 @@ | |||||||
| package controller | package controller | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"electricity_bill_calc/model" | 	"electricity_bill_calc/logger" | ||||||
|  | 	"electricity_bill_calc/repository" | ||||||
| 	"electricity_bill_calc/response" | 	"electricity_bill_calc/response" | ||||||
| 	"electricity_bill_calc/security" | 	"electricity_bill_calc/security" | ||||||
| 	"electricity_bill_calc/service" | 	"electricity_bill_calc/vo" | ||||||
| 	"electricity_bill_calc/tools" |  | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  |  | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
| 	"github.com/google/uuid" | 	"go.uber.org/zap" | ||||||
| 	"github.com/jinzhu/copier" |  | ||||||
| 	"github.com/shopspring/decimal" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func InitializeParkController(router *fiber.App) { | var parkLog = logger.Named("Handler", "Park") | ||||||
| 	router.Get("/parks", security.EnterpriseAuthorize, listAllParksUnderSessionUser) |  | ||||||
| 	router.Get("/parks/:uid", security.MustAuthenticated, listAllParksUnderSpecificUser) | func InitializeParkHandlers(router *fiber.App) { | ||||||
| 	router.Post("/park", security.EnterpriseAuthorize, createNewPark) | 	router.Get("/park", security.EnterpriseAuthorize, listParksBelongsToCurrentUser) | ||||||
| 	router.Put("/park/:pid", security.EnterpriseAuthorize, modifyPark) | 	router.Post("/park", security.EnterpriseAuthorize, createPark) | ||||||
|  | 	router.Get("/park/belongs/:uid", security.OPSAuthorize, listParksBelongsTo) | ||||||
| 	router.Get("/park/:pid", security.EnterpriseAuthorize, fetchParkDetail) | 	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.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) | func listParksBelongsToCurrentUser(c *fiber.Ctx) error { | ||||||
| 	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 { |  | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	userSession, err := _retreiveSession(c) | 	session, err := _retreiveSession(c) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		parkLog.Error("列出当前用的全部园区,无法获取当前用户的会话。") | ||||||
| 		return result.Unauthorized(err.Error()) | 		return result.Unauthorized(err.Error()) | ||||||
| 	} | 	} | ||||||
| 	keyword := c.Query("keyword") | 	parkLog.Info("列出当前用户下的全部园区", zap.String("user id", session.Uid)) | ||||||
| 	parks, err := service.ParkService.ListAllParkBelongsTo(userSession.Uid, keyword) | 	parks, err := repository.ParkRepository.ListAllParks(session.Uid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		parkLog.Error("无法获取园区列表。", zap.String("user id", session.Uid)) | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 		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) | 	result := response.NewResult(c) | ||||||
| 	requestUserId := c.Params("uid") | 	userId := c.Params("uid") | ||||||
| 	keyword := c.Query("keyword") | 	parkLog.Info("列出指定用户下的全部园区", zap.String("user id", userId)) | ||||||
| 	parks, err := service.ParkService.ListAllParkBelongsTo(requestUserId, keyword) | 	parks, err := repository.ParkRepository.ListAllParks(userId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		parkLog.Error("无法获取园区列表。", zap.String("user id", userId)) | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 		return result.Error(http.StatusInternalServerError, err.Error()) | ||||||
| 	} | 	} | ||||||
| 	return result.Json(http.StatusOK, "已获取到指定用户下的园区。", fiber.Map{"parks": parks}) | 	return result.Success("已获取到指定用户的下的园区", 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("指定园区资料已更新。") |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 获取指定园区的详细信息 | ||||||
| func fetchParkDetail(c *fiber.Ctx) error { | func fetchParkDetail(c *fiber.Ctx) error { | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestParkId := c.Params("pid") | 	parkId := c.Params("pid") | ||||||
| 	if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { | 	parkLog.Info("获取指定园区的详细信息", zap.String("park id", parkId)) | ||||||
| 		return err | 	park, err := repository.ParkRepository.RetrieveParkDetail(parkId) | ||||||
| 	} |  | ||||||
| 	park, err := service.ParkService.FetchParkDetail(requestParkId) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		parkLog.Error("无法获取园区信息。", zap.String("park id", parkId)) | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 		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 createPark(c *fiber.Ctx) error { | ||||||
| } |  | ||||||
|  |  | ||||||
| func changeParkEnableState(c *fiber.Ctx) error { |  | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	userSession, err := _retreiveSession(c) | 	session, err := _retreiveSession(c) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		parkLog.Error("创建一个新的园区,无法获取当前用户的会话。") | ||||||
| 		return result.Unauthorized(err.Error()) | 		return result.Unauthorized(err.Error()) | ||||||
| 	} | 	} | ||||||
| 	requestParkId := c.Params("pid") | 	parkLog.Info("创建一个新的园区", zap.String("user id", session.Uid)) | ||||||
| 	if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { | 	creationForm := new(vo.ParkInformationForm) | ||||||
| 		return err | 	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) | 	park, err := creationForm.TryIntoPark() | ||||||
| 	if err := c.BodyParser(formData); err != nil { |  | ||||||
| 		return result.UnableToParse("无法解析提交的数据。") |  | ||||||
| 	} |  | ||||||
| 	err = service.ParkService.ChangeParkState(userSession.Uid, requestParkId, formData.Enabled) |  | ||||||
| 	if err != nil { | 	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.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 { | func deleteSpecificPark(c *fiber.Ctx) error { | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	userSession, err := _retreiveSession(c) | 	parkId := c.Params("pid") | ||||||
|  | 	session, err := _retreiveSession(c) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		parkLog.Error("删除指定的园区,无法获取当前用户的会话。") | ||||||
| 		return result.Unauthorized(err.Error()) | 		return result.Unauthorized(err.Error()) | ||||||
| 	} | 	} | ||||||
| 	requestParkId := c.Params("pid") | 	if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass { | ||||||
| 	if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { |  | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	err = service.ParkService.DeletePark(userSession.Uid, requestParkId) | 	ok, err := repository.ParkRepository.DeletePark(parkId) | ||||||
| 	if err != nil { | 	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.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("已删除指定的建筑") | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,22 +1,22 @@ | |||||||
| package controller | package controller | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"electricity_bill_calc/repository" | ||||||
| 	"electricity_bill_calc/response" | 	"electricity_bill_calc/response" | ||||||
| 	"electricity_bill_calc/service" |  | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  |  | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func InitializeRegionController(router *fiber.App) { | func InitializeRegionHandlers(router *fiber.App) { | ||||||
| 	router.Get("/region/:rid", fetchRegions) | 	router.Get("/region/:rid", getSubRegions) | ||||||
| 	router.Get("/regions/:rid", fetchAllLeveledRegions) | 	router.Get("/regions/:rid", getParentRegions) | ||||||
| } | } | ||||||
|  |  | ||||||
| func fetchRegions(c *fiber.Ctx) error { | func getSubRegions(c *fiber.Ctx) error { | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestParentId := c.Params("rid") | 	requestParentId := c.Params("rid") | ||||||
| 	regions, err := service.RegionService.FetchSubRegions(requestParentId) | 	regions, err := repository.RegionRepository.FindSubRegions(requestParentId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 		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}) | 	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) | 	result := response.NewResult(c) | ||||||
| 	requestRegionCode := c.Params("rid") | 	requestRegionCode := c.Params("rid") | ||||||
| 	regions, err := service.RegionService.FetchAllParentRegions(requestRegionCode) | 	regions, err := repository.RegionRepository.FindParentRegions(requestRegionCode) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 		return result.Error(http.StatusInternalServerError, err.Error()) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,301 +1,425 @@ | |||||||
| package controller | package controller | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"electricity_bill_calc/exceptions" | 	"electricity_bill_calc/logger" | ||||||
| 	"electricity_bill_calc/model" | 	"electricity_bill_calc/model" | ||||||
|  | 	"electricity_bill_calc/repository" | ||||||
| 	"electricity_bill_calc/response" | 	"electricity_bill_calc/response" | ||||||
| 	"electricity_bill_calc/security" | 	"electricity_bill_calc/security" | ||||||
| 	"electricity_bill_calc/service" | 	"electricity_bill_calc/service" | ||||||
| 	"electricity_bill_calc/tools" | 	"electricity_bill_calc/tools" | ||||||
| 	"net/http" | 	"electricity_bill_calc/types" | ||||||
| 	"strconv" | 	"electricity_bill_calc/vo" | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
| 	"github.com/jinzhu/copier" | 	"github.com/jinzhu/copier" | ||||||
| 	"github.com/samber/lo" | 	"github.com/samber/lo" | ||||||
| 	"github.com/shopspring/decimal" | 	"go.uber.org/zap" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func InitializeReportController(router *fiber.App) { | var reportLog = logger.Named("Handler", "Report") | ||||||
| 	router.Get("/reports/with/drafts", security.EnterpriseAuthorize, fetchNewestReportOfParkWithDraft) |  | ||||||
| 	router.Post("/park/:pid/report", security.EnterpriseAuthorize, initializeNewReport) | func InitializeReportHandlers(router *fiber.App) { | ||||||
| 	router.Get("/report/:rid/step/state", security.EnterpriseAuthorize, fetchReportStepStates) | 	router.Get("/reports", security.MustAuthenticated, reportComprehensiveSearch) | ||||||
| 	router.Get("/report/:rid/summary", security.EnterpriseAuthorize, fetchReportParkSummary) | 	router.Post("/report", security.EnterpriseAuthorize, initNewReportCalculateTask) | ||||||
| 	router.Put("/report/:rid/summary", security.EnterpriseAuthorize, fillReportSummary) | 	router.Get("/report/draft", security.EnterpriseAuthorize, listDraftReportIndicies) | ||||||
| 	router.Get("/report/:rid/summary/calculate", security.EnterpriseAuthorize, testCalculateReportSummary) | 	//TODO: 2023-07-20将calcualte错误请求改为正确的calculate请求 | ||||||
| 	router.Post("/report/:rid/summary/calculate", security.EnterpriseAuthorize, progressReportSummary) | 	router.Post("/report/calculate", security.EnterpriseAuthorize, testCalculateReportSummary) | ||||||
| 	router.Put("/report/:rid/step/meter/register", security.EnterpriseAuthorize, progressEndUserRegister) | 	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.Post("/report/:rid/publish", security.EnterpriseAuthorize, publishReport) | ||||||
| 	router.Get("/reports", security.MustAuthenticated, searchReports) | 	router.Put("/report/:rid/calculate", security.EnterpriseAuthorize, initiateCalculateTask) | ||||||
| 	router.Get("/report/:rid", security.MustAuthenticated, fetchReportPublicity) | 	router.Get("/report/:rid/publics", security.MustAuthenticated, listPublicMetersInReport) | ||||||
| 	router.Post("/report/:rid/calculate", security.EnterpriseAuthorize, calculateReport) | 	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 { | 	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 { | 	if err != nil { | ||||||
| 		return false, result.NotFound(err.Error()) | 		log.Error("无法检查核算报表的所有权", zap.Error(err)) | ||||||
|  | 		return false, result.Error(fiber.StatusInternalServerError, "无法检查核算报表的所有权。") | ||||||
| 	} | 	} | ||||||
| 	if requestReport == nil { | 	if !ok { | ||||||
| 		return false, result.NotFound("指定报表未能找到。") | 		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) | 	result := response.NewResult(c) | ||||||
| 	userSession, err := _retreiveSession(c) | 	session, err := _retreiveSession(c) | ||||||
| 	if err != nil { | 	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 { | 	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) | 	result := response.NewResult(c) | ||||||
| 	requestParkId := c.Params("pid") | 	session, err := _retreiveSession(c) | ||||||
| 	userSession, err := _retreiveSession(c) |  | ||||||
| 	if err != nil { | 	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 | 		return err | ||||||
| 	} | 	} | ||||||
| 	requestPeriod := c.Query("period") | 	ok, err := repository.ReportRepository.CreateReport(&form) | ||||||
| 	reportPeriod, err := time.Parse("2006-01", requestPeriod) |  | ||||||
| 	if err != nil { | 	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 !ok { | ||||||
| 	if err != nil { | 		reportLog.Error("未能完成核算报表的保存。") | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 		return result.NotAccept("未能完成核算报表的保存。") | ||||||
| 	} | 	} | ||||||
| 	if !valid { | 	return result.Success("已经成功创建核算报表。") | ||||||
| 		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}) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func fetchReportStepStates(c *fiber.Ctx) error { | // 更新指定的核算任务 | ||||||
|  | func updateReportCalculateTask(c *fiber.Ctx) error { | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestReportId := c.Params("rid") | 	reportId := c.Params("rid") | ||||||
| 	if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { | 	if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	requestReport, err := service.ReportService.RetreiveReportIndex(requestReportId) | 	var form vo.ReportModifyForm | ||||||
| 	if err != nil { | 	if err := c.BodyParser(&form); err != nil { | ||||||
| 		return result.NotFound(err.Error()) | 		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) | 	result := response.NewResult(c) | ||||||
| 	requestReportId := c.Params("rid") | 	reportId := c.Params("rid") | ||||||
| 	if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { | 	if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	summary, err := service.ReportService.RetreiveReportSummary(requestReportId) | 	err := service.ReportService.DispatchReportCalculate(reportId) | ||||||
| 	if err != nil { | 	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 { | 	if summary == nil { | ||||||
| 		return result.NotFound("指定报表未能找到。") | 		reportLog.Error("未找到核算报表的园区电量信息") | ||||||
|  | 		return result.NotFound("未找到核算报表的园区电量信息。") | ||||||
| 	} | 	} | ||||||
| 	return result.Json(http.StatusOK, "已经获取到指定报表中的园区概况。", fiber.Map{"summary": summary}) | 	var summaryResponse vo.SimplifiedReportSummary | ||||||
| } | 	copier.Copy(&summaryResponse, summary) | ||||||
|  | 	return result.Success( | ||||||
| type ReportSummaryFormData struct { | 		"已经获取到核算报表的园区电量信息。", | ||||||
| 	Overall     decimal.Decimal `json:"overall" form:"overall"` | 		fiber.Map{"summary": summaryResponse}, | ||||||
| 	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("指定电费公示报表中的园区概况基本数据已经完成更新。") |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 对提供的园区电量信息进行试计算,返回试计算结果 | ||||||
| func testCalculateReportSummary(c *fiber.Ctx) error { | func testCalculateReportSummary(c *fiber.Ctx) error { | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestReportId := c.Params("rid") | 	reportLog.Info("试计算园区电量信息") | ||||||
| 	if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { | 	var form vo.TestCalculateForm | ||||||
| 		return err | 	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 { | 	if err != nil { | ||||||
| 		return result.NotFound(err.Error()) | 		reportLog.Error("无法获取当前用户的会话信息", zap.Error(err)) | ||||||
|  | 		return result.Unauthorized("无法获取当前用户的会话信息。") | ||||||
| 	} | 	} | ||||||
| 	summary.CalculatePrices() | 	status, err := repository.ReportRepository.GetReportTaskStatus(session.Uid) | ||||||
| 	calcResults := tools.ConvertStructToMap(summary) | 	if err != nil { | ||||||
| 	return result.Json( | 		reportLog.Error("无法获取核算报表计算状态", zap.Error(err)) | ||||||
| 		http.StatusOK, | 		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{ | 		fiber.Map{ | ||||||
| 			"result": lo.PickByKeys( | 			"detail": vo.NewReportDetailQueryResponse(user, park, report), | ||||||
| 				calcResults, |  | ||||||
| 				[]string{"overallPrice", "criticalPrice", "peakPrice", "flat", "flatFee", "flatPrice", "valleyPrice", "consumptionFee"}, |  | ||||||
| 			), |  | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|  |  | ||||||
| func progressReportSummary(c *fiber.Ctx) error { | // 获取指定核算报表的总览信息 | ||||||
|  | func getReportSummary(c *fiber.Ctx) error { | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestReportId := c.Params("rid") | 	reportId := c.Params("rid") | ||||||
| 	if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { | 	report, err := repository.ReportRepository.RetrieveReportSummary(reportId) | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	err := service.ReportService.CalculateSummaryAndFinishStep(requestReportId) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if nfErr, ok := err.(exceptions.NotFoundError); ok { | 		reportLog.Error("无法获取核算报表的总览信息", zap.Error(err)) | ||||||
| 			return result.NotFound(nfErr.Error()) | 		return result.Error(fiber.StatusInternalServerError, "无法获取核算报表的总览信息。") | ||||||
| 		} else { |  | ||||||
| 			return result.Error(http.StatusInternalServerError, err.Error()) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return result.Success("已经完成园区概况的计算,并可以进行到下一步骤。") | 	if report == nil { | ||||||
| } | 		reportLog.Error("未找到核算报表的总览信息") | ||||||
|  | 		return result.NotFound("未找到核算报表的总览信息。") | ||||||
| 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()) |  | ||||||
| 	} | 	} | ||||||
|  | 	var summaryResponse vo.ParkSummaryResponse | ||||||
|  | 	copier.Copy(&summaryResponse, report) | ||||||
| 	return result.Success( | 	return result.Success( | ||||||
| 		"已经取得符合条件的公示报表记录。", | 		"已经获取到核算报表的总览信息。", | ||||||
| 		response.NewPagedResponse(requestPage, totalItems).ToMap(), | 		fiber.Map{"summary": summaryResponse}, | ||||||
| 		fiber.Map{"reports": records}, |  | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|  |  | ||||||
| func fetchReportPublicity(c *fiber.Ctx) error { | // 获取指定报表中分页的公共表计的核算摘要信息 | ||||||
|  | func listPublicMetersInReport(c *fiber.Ctx) error { | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestReportId := c.Params("rid") | 	reportId := c.Params("rid") | ||||||
| 	publicity, err := service.ReportService.AssembleReportPublicity(requestReportId) | 	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 err != nil { | ||||||
| 		if nfErr, ok := err.(exceptions.NotFoundError); ok { | 		reportLog.Error("无法获取核算报表中的公共表计信息", zap.Error(err)) | ||||||
| 			return result.NotFound(nfErr.Error()) | 		return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的公共表计信息。") | ||||||
| 		} else { |  | ||||||
| 			return result.Error(http.StatusInternalServerError, err.Error()) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	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) | 	result := response.NewResult(c) | ||||||
| 	requestReportId := c.Params("rid") | 	reportId := c.Params("rid") | ||||||
| 	if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { | 	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 | 		return err | ||||||
| 	} | 	} | ||||||
| 	err := service.CalculateService.ComprehensivelyCalculateReport(requestReportId) | 	ok, err := repository.ReportRepository.PublishReport(reportId) | ||||||
| 	if err != nil { | 	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}, | ||||||
|  | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,31 +1,36 @@ | |||||||
| package controller | package controller | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"electricity_bill_calc/logger" | ||||||
| 	"electricity_bill_calc/model" | 	"electricity_bill_calc/model" | ||||||
| 	"electricity_bill_calc/response" | 	"electricity_bill_calc/response" | ||||||
| 	"electricity_bill_calc/security" | 	"electricity_bill_calc/security" | ||||||
| 	"electricity_bill_calc/service" | 	"electricity_bill_calc/service" | ||||||
| 	"net/http" |  | ||||||
|  |  | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"go.uber.org/zap" | ||||||
|  | 	"net/http" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | var StatisticsWithdrawLog = logger.Named("Handler", "StatisticsWithdraw") | ||||||
|  |  | ||||||
| func InitializeStatisticsController(router *fiber.App) { | func InitializeStatisticsController(router *fiber.App) { | ||||||
| 	router.Get("/audits", security.OPSAuthorize, currentAuditAmount) | 	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 { | func currentAuditAmount(c *fiber.Ctx) error { | ||||||
|  | 	StatisticsWithdrawLog.Info("开始获取当前系统中待审核的内容数量") | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	amount, err := service.WithdrawService.AuditWaits() | 	amount, err := service.WithdrawService.AuditWaits() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		StatisticsWithdrawLog.Error("获取当前系统中待审核的内容数量出错", zap.Error(err)) | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 		return result.Error(http.StatusInternalServerError, err.Error()) | ||||||
| 	} | 	} | ||||||
| 	return result.Json(http.StatusOK, "已经获取到指定的统计信息。", fiber.Map{ |  | ||||||
| 		"amounts": map[string]int64{ | 	return result.Success("已经获取到指定的统计信息", | ||||||
| 			"withdraw": amount, | 		fiber.Map{"withdraw": amount}) | ||||||
| 		}, |  | ||||||
| 	}) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func statReports(c *fiber.Ctx) error { | func statReports(c *fiber.Ctx) error { | ||||||
| @@ -42,28 +47,35 @@ func statReports(c *fiber.Ctx) error { | |||||||
| 	if session.Type != 0 { | 	if session.Type != 0 { | ||||||
| 		enterprises, err = service.StatisticsService.EnabledEnterprises() | 		enterprises, err = service.StatisticsService.EnabledEnterprises() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | 			StatisticsWithdrawLog.Error(err.Error()) | ||||||
| 			return result.Error(http.StatusInternalServerError, err.Error()) | 			return result.Error(http.StatusInternalServerError, err.Error()) | ||||||
| 		} | 		} | ||||||
| 		parks, err = service.StatisticsService.EnabledParks() | 		parks, err = service.StatisticsService.EnabledParks() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | 			StatisticsWithdrawLog.Error(err.Error()) | ||||||
| 			return result.Error(http.StatusInternalServerError, 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 { | 		if err != nil { | ||||||
|  | 			StatisticsWithdrawLog.Error(err.Error()) | ||||||
| 			return result.Error(http.StatusInternalServerError, err.Error()) | 			return result.Error(http.StatusInternalServerError, err.Error()) | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		parks, err = service.StatisticsService.EnabledParks(session.Uid) | 		parks, err = service.StatisticsService.EnabledParks(session.Uid) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | 			StatisticsWithdrawLog.Error(err.Error()) | ||||||
| 			return result.Error(http.StatusInternalServerError, 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 { | 		if err != nil { | ||||||
|  | 			StatisticsWithdrawLog.Error(err.Error()) | ||||||
| 			return result.Error(http.StatusInternalServerError, err.Error()) | 			return result.Error(http.StatusInternalServerError, err.Error()) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return result.Json(http.StatusOK, "已经完成园区报告的统计。", fiber.Map{ | 	return result.Success("已经完成园区报告的统计。", fiber.Map{ | ||||||
| 		"statistics": fiber.Map{ | 		"statistics": fiber.Map{ | ||||||
| 			"enterprises": enterprises, | 			"enterprises": enterprises, | ||||||
| 			"parks":       parks, | 			"parks":       parks, | ||||||
|   | |||||||
							
								
								
									
										288
									
								
								controller/tenement.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								controller/tenement.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										142
									
								
								controller/top_up.go
									
									
									
									
									
										Normal 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( | ||||||
|  | 		"已经删除一条指定的商户充值记录", | ||||||
|  | 	) | ||||||
|  | } | ||||||
| @@ -3,131 +3,91 @@ package controller | |||||||
| import ( | import ( | ||||||
| 	"electricity_bill_calc/cache" | 	"electricity_bill_calc/cache" | ||||||
| 	"electricity_bill_calc/exceptions" | 	"electricity_bill_calc/exceptions" | ||||||
| 	"electricity_bill_calc/global" | 	"electricity_bill_calc/logger" | ||||||
| 	"electricity_bill_calc/model" | 	"electricity_bill_calc/model" | ||||||
|  | 	"electricity_bill_calc/repository" | ||||||
| 	"electricity_bill_calc/response" | 	"electricity_bill_calc/response" | ||||||
| 	"electricity_bill_calc/security" | 	"electricity_bill_calc/security" | ||||||
| 	"electricity_bill_calc/service" | 	"electricity_bill_calc/service" | ||||||
| 	"electricity_bill_calc/tools" | 	"electricity_bill_calc/tools" | ||||||
| 	"fmt" | 	"electricity_bill_calc/vo" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  |  | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
| 	"github.com/shopspring/decimal" | 	"go.uber.org/zap" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func InitializeUserController(router *fiber.App) { | var userLog = logger.Named("Handler", "User") | ||||||
| 	router.Delete("/password/:uid", security.OPSAuthorize, invalidUserPassword) |  | ||||||
| 	router.Delete("/login", security.MustAuthenticated, logout) | func InitializeUserHandlers(router *fiber.App) { | ||||||
| 	router.Put("/password", resetUserPassword) | 	router.Delete("/login", security.MustAuthenticated, doLogout) | ||||||
| 	router.Get("/accounts", security.ManagementAuthorize, listPagedUser) | 	router.Post("/login", doLogin) | ||||||
| 	router.Post("/login", login) | 	router.Get("/account", security.OPSAuthorize, searchUsers) | ||||||
| 	router.Put("/account/enabled/state", security.OPSAuthorize, switchUserEnabling) | 	router.Post("/account", security.OPSAuthorize, createOPSAccount) | ||||||
| 	router.Post("/account", security.OPSAuthorize, createOPSAndManagementAccount) | 	router.Get("/account/:uid", security.MustAuthenticated, fetchUserInformation) | ||||||
| 	router.Get("/account/:uid", security.MustAuthenticated, getUserDetail) | 	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.Post("/enterprise", security.OPSAuthorize, createEnterpriseAccount) | ||||||
| 	router.Put("/account/:uid", security.OPSAuthorize, modifyAccountDetail) |  | ||||||
| 	router.Get("/enterprise/quick/search", security.OPSAuthorize, quickSearchEnterprise) | 	router.Get("/enterprise/quick/search", security.OPSAuthorize, quickSearchEnterprise) | ||||||
| 	router.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"` | 	Username string `json:"uname"` | ||||||
| 	Password string `json:"upass"` | 	Password string `json:"upass"` | ||||||
| 	Type     int8   `json:"type"` | 	Type     int16  `json:"type"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func login(c *fiber.Ctx) error { | func doLogin(c *fiber.Ctx) error { | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c)                 //创建一个相应结果对象 | ||||||
| 	loginData := new(_LoginFormData) | 	loginData := new(_LoginForm)                    //创建一个解析登录表单数据的实体 | ||||||
| 	if err := c.BodyParser(loginData); err != nil { | 	if err := c.BodyParser(loginData); err != nil { //解析请求体中的Json数据到loginData里,如果解析出错就返回错误 | ||||||
| 		return result.Error(http.StatusInternalServerError, "表单解析失败。") | 		userLog.Error("表单解析失败!", zap.Error(err)) | ||||||
|  | 		return result.Error(http.StatusInternalServerError, "表单解析失败。") //返回一个内部服务器错误的相应结果 | ||||||
| 	} | 	} | ||||||
| 	var ( | 	var ( | ||||||
| 		session *model.Session | 		session *model.Session | ||||||
| 		err     error | 		err     error | ||||||
| 	) | 	) | ||||||
| 	if loginData.Type == model.USER_TYPE_ENT { | 	userLog.Info("有用户请求登录。", zap.String("username", loginData.Username), zap.Int16("type", loginData.Type)) //记录日志相关信息 | ||||||
| 		session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password) | 	if loginData.Type == model.USER_TYPE_ENT {                                                              //根据登录类型选择不同的处理方法 | ||||||
|  | 		session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password) //企业用户 | ||||||
| 	} else { | 	} 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 err != nil { | ||||||
| 		if authError, ok := err.(*exceptions.AuthenticationError); ok { | 		if authError, ok := err.(*exceptions.AuthenticationError); ok { //检查错误是否为身份验证错误 | ||||||
| 			if authError.NeedReset { | 			if authError.NeedReset { //如果需要重置密码则返回对应结果 | ||||||
| 				return result.LoginNeedReset() | 				return result.LoginNeedReset() | ||||||
| 			} | 			} | ||||||
| 			return result.Error(int(authError.Code), authError.Message) | 			return result.Error(int(authError.Code), authError.Message) //返回身份验证错误相应 | ||||||
| 		} else { | 		} 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) | 	result := response.NewResult(c) | ||||||
| 	session := c.Locals("session") | 	session, err := _retreiveSession(c) | ||||||
| 	if session == nil { | 	if err != nil { | ||||||
| 		return result.Success("用户会话已结束。") | 		return result.Success("用户会话已结束。") | ||||||
| 	} | 	} | ||||||
| 	_, err := cache.ClearSession(session.(*model.Session).Token) | 	_, err = cache.ClearSession(session.Token) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		userLog.Error("用户登出处理失败!", zap.Error(err)) | ||||||
| 		return result.Error(http.StatusInternalServerError, err.Error()) | 		return result.Error(http.StatusInternalServerError, err.Error()) | ||||||
| 	} | 	} | ||||||
| 	return result.Success("用户已成功登出系统。") | 	return result.Success("用户已成功登出系统。") | ||||||
| } | } | ||||||
|  |  | ||||||
| func invalidUserPassword(c *fiber.Ctx) error { | func searchUsers(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 { |  | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestPage, err := strconv.Atoi(c.Query("page", "1")) | 	requestPage, err := strconv.Atoi(c.Query("page", "1")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -145,213 +105,230 @@ func listPagedUser(c *fiber.Ctx) error { | |||||||
| 	} else { | 	} else { | ||||||
| 		requestUserStat = &state | 		requestUserStat = &state | ||||||
| 	} | 	} | ||||||
| 	users, total, err := service.UserService.ListUserDetail(requestKeyword, requestUserType, requestUserStat, requestPage) | 	users, total, err := repository.UserRepository.FindUser( | ||||||
|  | 		&requestKeyword, | ||||||
|  | 		int16(requestUserType), | ||||||
|  | 		requestUserStat, | ||||||
|  | 		uint(requestPage), | ||||||
|  | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return result.NotFound(err.Error()) | 		return result.NotFound(err.Error()) | ||||||
| 	} | 	} | ||||||
| 	return result.Json( | 	return result.Success( | ||||||
| 		http.StatusOK, |  | ||||||
| 		"已取得符合条件的用户集合。", | 		"已取得符合条件的用户集合。", | ||||||
| 		response.NewPagedResponse(requestPage, total).ToMap(), | 		response.NewPagedResponse(requestPage, total).ToMap(), | ||||||
| 		fiber.Map{"accounts": users}, | 		fiber.Map{"accounts": users}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|  |  | ||||||
| type _UserStateChangeFormData struct { | func getAccountExpiration(c *fiber.Ctx) error { | ||||||
| 	UserID  string `json:"uid" form:"uid"` |  | ||||||
| 	Enabled bool   `json:"enabled" form:"enabled"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func switchUserEnabling(c *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 { |  | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	session, err := _retreiveSession(c) | 	session, err := _retreiveSession(c) | ||||||
| 	if err != nil { | 	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 { | 	if err != nil { | ||||||
| 		return result.NotFound(err.Error()) | 		return result.NotFound("未找到指定的用户档案") | ||||||
| 	} | 	} | ||||||
| 	return result.Json( | 	return result.Success( | ||||||
| 		http.StatusOK, |  | ||||||
| 		"已经取得用户的服务期限信息", | 		"已经取得用户的服务期限信息", | ||||||
| 		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}) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,81 +1,134 @@ | |||||||
| package controller | package controller | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"electricity_bill_calc/exceptions" | 	"electricity_bill_calc/logger" | ||||||
|  | 	"electricity_bill_calc/repository" | ||||||
| 	"electricity_bill_calc/response" | 	"electricity_bill_calc/response" | ||||||
| 	"electricity_bill_calc/security" | 	"electricity_bill_calc/security" | ||||||
| 	"electricity_bill_calc/service" | 	"electricity_bill_calc/vo" | ||||||
| 	"net/http" |  | ||||||
| 	"strconv" |  | ||||||
|  |  | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"go.uber.org/zap" | ||||||
|  | 	"net/http" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func InitializeWithdrawController(router *fiber.App) { | var withdrawLog = logger.Named("Handler", "Withdraw") | ||||||
| 	router.Delete("/publicity/:pid", security.EnterpriseAuthorize, applyReportWithdraw) |  | ||||||
| 	router.Get("/withdraws", security.OPSAuthorize, fetchWithdrawsWaitingAutdit) | func InitializeWithdrawHandlers(router *fiber.App) { | ||||||
| 	router.Put("/withdraw/:rid", security.OPSAuthorize, auditWithdraw) | 	router.Get("/withdraw", security.OPSAuthorize, withdraw) | ||||||
|  | 	router.Put("/withdraw/:rid", security.OPSAuthorize, reviewRequestWithdraw) | ||||||
|  | 	router.Delete("/withdraw/:rid", security.EnterpriseAuthorize, recallReport) | ||||||
| } | } | ||||||
|  |  | ||||||
| func applyReportWithdraw(c *fiber.Ctx) error { | //用于分页检索用户的核算报表 | ||||||
|  | func withdraw(c *fiber.Ctx) error { | ||||||
|  | 	//记录日志 | ||||||
|  | 	withdrawLog.Info("带分页的待审核的核算撤回申请列表") | ||||||
|  | 	//获取请求参数 | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestReportId := c.Params("pid") | 	keyword := c.Query("keyword", "") | ||||||
| 	if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { | 	page := c.QueryInt("page", 1) | ||||||
| 		return err | 	withdrawLog.Info("参数为: ", zap.String("keyword", keyword), zap.Int("page", page)) | ||||||
| 	} | 	//中间数据库操作暂且省略。。。。 | ||||||
| 	deleted, err := service.WithdrawService.ApplyWithdraw(requestReportId) | 	//首先进行核算报表的分页查询 | ||||||
|  | 	withdraws, total, err := repository.WithdrawRepository.FindWithdraw(uint(page), &keyword) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if nfErr, ok := err.(exceptions.NotFoundError); ok { | 		withdrawLog.Error("检索用户核算报表失败。", zap.Error(err)) | ||||||
| 			return result.NotFound(nfErr.Error()) | 		return result.Error(http.StatusInternalServerError, err.Error()) | ||||||
| 		} else if ioErr, ok := err.(exceptions.ImproperOperateError); ok { |  | ||||||
| 			return result.NotAccept(ioErr.Error()) |  | ||||||
| 		} else { |  | ||||||
| 			return result.Error(http.StatusInternalServerError, err.Error()) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	if !deleted { | 	//TODO: 2023-07-18 此处返回值是个示例,具体返回值需要查询数据库(完成) | ||||||
| 		return result.Error(http.StatusInternalServerError, "未能完成公示报表的申请撤回操作。") | 	return result.Success( | ||||||
| 	} | 		"withdraw请求成功", | ||||||
| 	return result.Success("指定的公示报表已经申请撤回。") | 		response.NewPagedResponse(page, total).ToMap(), | ||||||
| } | 		fiber.Map{"records": withdraws}, | ||||||
|  |  | ||||||
| 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}, |  | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|  |  | ||||||
| type WithdrawAuditFormData struct { | //用于审核撤回报表 | ||||||
| 	Audit bool `json:"audit" form:"audit"` | func reviewRequestWithdraw(c *fiber.Ctx) error { | ||||||
| } | 	Rid := c.Params("rid", "") | ||||||
|  | 	Data := new(vo.ReviewWithdraw) | ||||||
| func auditWithdraw(c *fiber.Ctx) error { |  | ||||||
| 	result := response.NewResult(c) | 	result := response.NewResult(c) | ||||||
| 	requestReportId := c.Params("rid") |  | ||||||
| 	formData := new(WithdrawAuditFormData) | 	if err := c.BodyParser(&Data); err != nil { | ||||||
| 	if err := c.BodyParser(formData); err != nil { | 		withdrawLog.Error("无法解析审核指定报表的请求数据", zap.Error(err)) | ||||||
| 		return result.UnableToParse("无法解析提交的数据。") | 		return result.BadRequest("无法解析审核指定报表的请求数据。") | ||||||
| 	} | 	} | ||||||
| 	err := service.WithdrawService.AuditWithdraw(requestReportId, formData.Audit) |  | ||||||
| 	if err != nil { | 	if Data.Audit == true { //审核通过 | ||||||
| 		if nfErr, ok := err.(exceptions.NotFoundError); ok { | 		ok, err := repository.WithdrawRepository.ReviewTrueReportWithdraw(Rid) | ||||||
| 			return result.NotFound(nfErr.Error()) | 		if err != nil { | ||||||
|  | 			withdrawLog.Error("审核同意撤回报表失败") | ||||||
|  | 			return result.Error(http.StatusInternalServerError, err.Error()) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !ok { | ||||||
|  | 			withdrawLog.Error("审核同意撤回报表失败") | ||||||
|  | 			return result.NotAccept("审核同意撤回报表失败") | ||||||
| 		} else { | 		} 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, "其他错误") | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| package excel | package excel | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"electricity_bill_calc/model" |  | ||||||
| 	"electricity_bill_calc/tools" | 	"electricity_bill_calc/tools" | ||||||
|  | 	"electricity_bill_calc/types" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| @@ -19,11 +19,10 @@ import ( | |||||||
| type ExcelTemplateGenerator interface { | type ExcelTemplateGenerator interface { | ||||||
| 	Close() | 	Close() | ||||||
| 	WriteTo(w io.Writer) (int64, error) | 	WriteTo(w io.Writer) (int64, error) | ||||||
| 	WriteMeterData(meters []model.EndUserDetail) error |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type ColumnRecognizer struct { | type ColumnRecognizer struct { | ||||||
| 	Pattern    []string | 	Pattern    [][]string | ||||||
| 	Tag        string | 	Tag        string | ||||||
| 	MatchIndex int | 	MatchIndex int | ||||||
| 	MustFill   bool | 	MustFill   bool | ||||||
| @@ -45,7 +44,7 @@ type ExcelAnalysisError struct { | |||||||
| 	Err AnalysisError `json:"error"` | 	Err AnalysisError `json:"error"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewColumnRecognizer(tag string, patterns ...string) ColumnRecognizer { | func NewColumnRecognizer(tag string, patterns ...[]string) ColumnRecognizer { | ||||||
| 	return ColumnRecognizer{ | 	return ColumnRecognizer{ | ||||||
| 		Pattern:    patterns, | 		Pattern:    patterns, | ||||||
| 		Tag:        tag, | 		Tag:        tag, | ||||||
| @@ -67,9 +66,17 @@ func (e ExcelAnalysisError) Error() string { | |||||||
|  |  | ||||||
| func (r *ColumnRecognizer) Recognize(cellValue string) bool { | func (r *ColumnRecognizer) Recognize(cellValue string) bool { | ||||||
| 	matches := make([]bool, 0) | 	matches := make([]bool, 0) | ||||||
| 	for _, p := range r.Pattern { | 	for _, pG := range r.Pattern { | ||||||
| 		matches = append(matches, strings.Contains(cellValue, p)) | 		groupMatch := make([]bool, 0) | ||||||
|  | 		for _, p := range pG { | ||||||
|  | 			groupMatch = append(groupMatch, strings.Contains(cellValue, p)) | ||||||
|  | 		} | ||||||
|  | 		// 这句表示在每一个匹配组中,只要有一个匹配项,就算匹配成功 | ||||||
|  | 		matches = append(matches, lo.Reduce(groupMatch, func(acc, elem bool, index int) bool { | ||||||
|  | 			return acc || elem | ||||||
|  | 		}, false)) | ||||||
| 	} | 	} | ||||||
|  | 	// 这句表示在尊有的匹配组中,必须全部的匹配组都完成匹配,才算匹配成功 | ||||||
| 	return lo.Reduce(matches, func(acc, elem bool, index int) bool { | 	return lo.Reduce(matches, func(acc, elem bool, index int) bool { | ||||||
| 		return acc && elem | 		return acc && elem | ||||||
| 	}, true) | 	}, true) | ||||||
| @@ -189,6 +196,54 @@ func (a *ExcelAnalyzer[T]) Analysis(bean T) ([]T, []ExcelAnalysisError) { | |||||||
| 							} else { | 							} else { | ||||||
| 								actualField.SetBool(false) | 								actualField.SetBool(false) | ||||||
| 							} | 							} | ||||||
|  | 						case "types.Date": | ||||||
|  | 							if len(matchValue) == 0 { | ||||||
|  | 								actualField.Set(reflect.ValueOf(types.NewEmptyDate())) | ||||||
|  | 							} else { | ||||||
|  | 								v, err := types.ParseDate(matchValue) | ||||||
|  | 								if err != nil { | ||||||
|  | 									errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期格式。%w", err)}}) | ||||||
|  | 									actualField.Set(reflect.ValueOf(types.NewEmptyDate())) | ||||||
|  | 								} else { | ||||||
|  | 									actualField.Set(reflect.ValueOf(v)) | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						case "*types.Date": | ||||||
|  | 							if len(matchValue) == 0 { | ||||||
|  | 								actualField.Set(reflect.ValueOf(nil)) | ||||||
|  | 							} else { | ||||||
|  | 								v, err := types.ParseDate(matchValue) | ||||||
|  | 								if err != nil { | ||||||
|  | 									errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期格式。%w", err)}}) | ||||||
|  | 									actualField.Set(reflect.ValueOf(nil)) | ||||||
|  | 								} else { | ||||||
|  | 									actualField.Set(reflect.ValueOf(&v)) | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						case "types.DateTime": | ||||||
|  | 							if len(matchValue) == 0 { | ||||||
|  | 								actualField.Set(reflect.ValueOf(types.NewEmptyDateTime())) | ||||||
|  | 							} else { | ||||||
|  | 								v, err := types.ParseDateTime(matchValue) | ||||||
|  | 								if err != nil { | ||||||
|  | 									errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期时间格式。%w", err)}}) | ||||||
|  | 									actualField.Set(reflect.ValueOf(types.NewEmptyDateTime())) | ||||||
|  | 								} else { | ||||||
|  | 									actualField.Set(reflect.ValueOf(v)) | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						case "*types.DateTime": | ||||||
|  | 							if len(matchValue) == 0 { | ||||||
|  | 								actualField.Set(reflect.ValueOf(nil)) | ||||||
|  | 							} else { | ||||||
|  | 								v, err := types.ParseDateTime(matchValue) | ||||||
|  | 								if err != nil { | ||||||
|  | 									errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期时间格式。%w", err)}}) | ||||||
|  | 									actualField.Set(reflect.ValueOf(nil)) | ||||||
|  | 								} else { | ||||||
|  | 									actualField.Set(reflect.ValueOf(&v)) | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|   | |||||||
| @@ -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) |  | ||||||
| } |  | ||||||
| @@ -1,21 +1,139 @@ | |||||||
| package excel | package excel | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"electricity_bill_calc/logger" | ||||||
| 	"electricity_bill_calc/model" | 	"electricity_bill_calc/model" | ||||||
|  | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
|  |  | ||||||
|  | 	"github.com/samber/lo" | ||||||
|  | 	"github.com/xuri/excelize/v2" | ||||||
|  | 	"go.uber.org/zap" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var meter04kVExcelRecognizers = []*ColumnRecognizer{ | var meterArchiveRecognizers = []*ColumnRecognizer{ | ||||||
| 	{Pattern: []string{"表号"}, Tag: "code", MatchIndex: -1, MustFill: true}, | 	{Pattern: [][]string{{"表号"}}, Tag: "code", MatchIndex: -1, MustFill: true}, | ||||||
| 	{Pattern: []string{"户名"}, Tag: "name", MatchIndex: -1}, | 	{Pattern: [][]string{{"表址", "地址", "户址"}}, Tag: "address", MatchIndex: -1}, | ||||||
| 	{Pattern: []string{"户址"}, Tag: "address", MatchIndex: -1}, | 	{Pattern: [][]string{{"类型"}}, Tag: "meterType", MatchIndex: -1, MustFill: true}, | ||||||
| 	{Pattern: []string{"联系人"}, Tag: "contact", MatchIndex: -1}, | 	{Pattern: [][]string{{"建筑"}}, Tag: "building", MatchIndex: -1}, | ||||||
| 	{Pattern: []string{"电话"}, Tag: "phone", MatchIndex: -1}, | 	{Pattern: [][]string{{"楼层"}}, Tag: "onFloor", MatchIndex: -1}, | ||||||
| 	{Pattern: []string{"倍率"}, Tag: "ratio", MatchIndex: -1, MustFill: true}, | 	{Pattern: [][]string{{"面积"}}, Tag: "area", MatchIndex: -1}, | ||||||
| 	{Pattern: []string{"序号"}, Tag: "seq", MatchIndex: -1, MustFill: true}, | 	{Pattern: [][]string{{"倍率"}}, Tag: "ratio", MatchIndex: -1, MustFill: true}, | ||||||
| 	{Pattern: []string{"公用设备"}, Tag: "public", 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) { | func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.MeterImportRow], error) { | ||||||
| 	return NewExcelAnalyzer[model.Meter04KV](file, meter04kVExcelRecognizers) | 	return NewExcelAnalyzer[model.MeterImportRow](file, meterArchiveRecognizers) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type MeterArchiveExcelTemplateGenerator struct { | ||||||
|  | 	file *excelize.File | ||||||
|  | 	log  *zap.Logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewMeterArchiveExcelTemplateGenerator() *MeterArchiveExcelTemplateGenerator { | ||||||
|  | 	return &MeterArchiveExcelTemplateGenerator{ | ||||||
|  | 		file: excelize.NewFile(), | ||||||
|  | 		log:  logger.Named("Excel", "MeterArchive"), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (MeterArchiveExcelTemplateGenerator) titles() *[]interface{} { | ||||||
|  | 	return &[]interface{}{ | ||||||
|  | 		"序号", | ||||||
|  | 		"表址", | ||||||
|  | 		"表号", | ||||||
|  | 		"表计类型", | ||||||
|  | 		"倍率", | ||||||
|  | 		"所在建筑", | ||||||
|  | 		"所在楼层", | ||||||
|  | 		"辖盖面积", | ||||||
|  | 		"抄表时间", | ||||||
|  | 		"有功(总)", | ||||||
|  | 		"有功(尖)", | ||||||
|  | 		"有功(峰)", | ||||||
|  | 		"有功(平)", | ||||||
|  | 		"有功(谷)", | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (g *MeterArchiveExcelTemplateGenerator) Close() { | ||||||
|  | 	g.file.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (g MeterArchiveExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) { | ||||||
|  | 	return g.file.WriteTo(w) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (g *MeterArchiveExcelTemplateGenerator) WriteTemplateData(buildings []*model.ParkBuilding) error { | ||||||
|  | 	var err error | ||||||
|  | 	defaultSheet := g.file.GetSheetName(0) | ||||||
|  | 	g.log.Debug("选定默认输出表格", zap.String("sheet", defaultSheet)) | ||||||
|  | 	err = g.file.SetColWidth(defaultSheet, "B", "I", 20) | ||||||
|  | 	if err != nil { | ||||||
|  | 		g.log.Error("未能设定长型列宽。", zap.Error(err)) | ||||||
|  | 		return fmt.Errorf("未能设定长型列宽,%w", err) | ||||||
|  | 	} | ||||||
|  | 	err = g.file.SetColWidth(defaultSheet, "J", "N", 15) | ||||||
|  | 	if err != nil { | ||||||
|  | 		g.log.Error("未能设定短型列宽。", zap.Error(err)) | ||||||
|  | 		return fmt.Errorf("未能设定短型列宽,%w", err) | ||||||
|  | 	} | ||||||
|  | 	err = g.file.SetSheetRow(defaultSheet, "A1", g.titles()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		g.log.Error("未能输出模板标题。", zap.Error(err)) | ||||||
|  | 		return fmt.Errorf("未能输出模板标题,%w", err) | ||||||
|  | 	} | ||||||
|  | 	err = g.file.SetRowHeight(defaultSheet, 1, 20) | ||||||
|  | 	if err != nil { | ||||||
|  | 		g.log.Error("未能设定标题行高度。", zap.Error(err)) | ||||||
|  | 		return fmt.Errorf("未能设定标题行高度,%w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dateTimeExp := "yyyy-mm-dd hh:mm" | ||||||
|  | 	dateTimeColStyle, err := g.file.NewStyle(&excelize.Style{ | ||||||
|  | 		CustomNumFmt: &dateTimeExp, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		g.log.Error("未能创建日期时间格式。", zap.Error(err)) | ||||||
|  | 		return fmt.Errorf("未能创建日期时间格式,%w", err) | ||||||
|  | 	} | ||||||
|  | 	g.file.SetCellStyle(defaultSheet, "I2", "I1048576", dateTimeColStyle) | ||||||
|  |  | ||||||
|  | 	numExp := "0.0000" | ||||||
|  | 	numColStyle, err := g.file.NewStyle(&excelize.Style{ | ||||||
|  | 		CustomNumFmt: &numExp, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		g.log.Error("未能创建抄表数字格式。", zap.Error(err)) | ||||||
|  | 		return fmt.Errorf("未能创建抄表数字格式,%w", err) | ||||||
|  | 	} | ||||||
|  | 	g.file.SetCellStyle(defaultSheet, "J2", "N1048576", numColStyle) | ||||||
|  |  | ||||||
|  | 	meterInstallationTypeValidation := excelize.NewDataValidation(false) | ||||||
|  | 	meterInstallationTypeValidation.SetDropList([]string{"商户表", "公共表", "楼道表"}) | ||||||
|  | 	meterInstallationTypeValidation.Sqref = "D2:D1048576" | ||||||
|  | 	err = g.file.AddDataValidation(defaultSheet, meterInstallationTypeValidation) | ||||||
|  | 	if err != nil { | ||||||
|  | 		g.log.Error("未能设定表计类型选择器。", zap.Error(err)) | ||||||
|  | 		return fmt.Errorf("未能设定表计类型选择器,%w", err) | ||||||
|  | 	} | ||||||
|  | 	buildingValidation := excelize.NewDataValidation(true) | ||||||
|  | 	buildingNames := lo.Map(buildings, func(b *model.ParkBuilding, _ int) string { | ||||||
|  | 		return b.Name | ||||||
|  | 	}) | ||||||
|  | 	buildingValidation.SetDropList(buildingNames) | ||||||
|  | 	buildingValidation.Sqref = "F2:F1048576" | ||||||
|  | 	err = g.file.AddDataValidation(defaultSheet, buildingValidation) | ||||||
|  | 	if err != nil { | ||||||
|  | 		g.log.Error("未能设定所在建筑选择器。", zap.Error(err)) | ||||||
|  | 		return fmt.Errorf("未能设定所在建筑选择器,%w", err) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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
									
								
							
							
						
						
									
										131
									
								
								excel/meter_reading.go
									
									
									
									
									
										Normal 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 | ||||||
|  | } | ||||||
| @@ -31,5 +31,5 @@ func NewUnauthorizedError(msg string) *UnauthorizedError { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (e UnauthorizedError) Error() string { | func (e UnauthorizedError) Error() string { | ||||||
| 	return fmt.Sprintf("Unauthorized: %s", e.Message) | 	return fmt.Sprintf("用户未获得授权: %s", e.Message) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,5 +15,5 @@ func NewIllegalArgumentsError(msg string, arguments ...string) IllegalArgumentsE | |||||||
| } | } | ||||||
|  |  | ||||||
| func (e IllegalArgumentsError) Error() string { | func (e IllegalArgumentsError) Error() string { | ||||||
| 	return fmt.Sprintf("Illegal Arguments, %s", e.Message) | 	return fmt.Sprintf("使用了非法参数, %s", e.Message) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,5 +15,5 @@ func NewImproperOperateError(msg string, arguments ...string) ImproperOperateErr | |||||||
| } | } | ||||||
|  |  | ||||||
| func (e ImproperOperateError) Error() string { | func (e ImproperOperateError) Error() string { | ||||||
| 	return fmt.Sprintf("Improper Operate, %s", e.Message) | 	return fmt.Sprintf("操作不恰当, %s", e.Message) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								exceptions/insufficient_data.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								exceptions/insufficient_data.go
									
									
									
									
									
										Normal 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) | ||||||
|  | } | ||||||
| @@ -11,7 +11,7 @@ func NewNotFoundError(msg string) *NotFoundError { | |||||||
| } | } | ||||||
|  |  | ||||||
| func NewNotFoundErrorFromError(msg string, err error) *NotFoundError { | func NewNotFoundErrorFromError(msg string, err error) *NotFoundError { | ||||||
| 	return &NotFoundError{Message: fmt.Sprintf("%s,%v", msg, err)} | 	return &NotFoundError{Message: fmt.Sprintf("所需数据未找到,%s,%v", msg, err)} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (e NotFoundError) Error() string { | func (e NotFoundError) Error() string { | ||||||
|   | |||||||
| @@ -1,11 +1,130 @@ | |||||||
| package exceptions | package exceptions | ||||||
|  |  | ||||||
| type UnsuccessfulOperationError struct{} | import ( | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
| func NewUnsuccessfulOperationError() *UnsuccessfulOperationError { | type OperationType int16 | ||||||
| 	return &UnsuccessfulOperationError{} |  | ||||||
|  | const ( | ||||||
|  | 	OPERATE_CREATE OperationType = iota | ||||||
|  | 	OPERATE_UPDATE | ||||||
|  | 	OPERATE_DELETE | ||||||
|  | 	OEPRATE_QUERY | ||||||
|  | 	OPERATE_CALCULATE | ||||||
|  | 	OPERATE_DB | ||||||
|  | 	OPERATE_DB_TRANSACTION | ||||||
|  | 	OPERATE_CUSTOM OperationType = 98 | ||||||
|  | 	OPERATE_OTHER  OperationType = 99 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type UnsuccessfulOperationError struct { | ||||||
|  | 	Operate     OperationType | ||||||
|  | 	Description string | ||||||
|  | 	Message     string | ||||||
| } | } | ||||||
|  |  | ||||||
| func (UnsuccessfulOperationError) Error() string { | func NewUnsuccessfulOperationError(oeprate OperationType, describe, message string) *UnsuccessfulOperationError { | ||||||
| 	return "Unsuccessful Operation" | 	return &UnsuccessfulOperationError{ | ||||||
|  | 		Operate:     oeprate, | ||||||
|  | 		Description: describe, | ||||||
|  | 		Message:     message, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewUnsuccessCreateError(message string) *UnsuccessfulOperationError { | ||||||
|  | 	return &UnsuccessfulOperationError{ | ||||||
|  | 		Operate: OPERATE_CREATE, | ||||||
|  | 		Message: message, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewUnsuccessUpdateError(message string) *UnsuccessfulOperationError { | ||||||
|  | 	return &UnsuccessfulOperationError{ | ||||||
|  | 		Operate: OPERATE_UPDATE, | ||||||
|  | 		Message: message, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewUnsuccessDeleteError(message string) *UnsuccessfulOperationError { | ||||||
|  | 	return &UnsuccessfulOperationError{ | ||||||
|  | 		Operate: OPERATE_DELETE, | ||||||
|  | 		Message: message, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewUnsuccessQueryError(message string) *UnsuccessfulOperationError { | ||||||
|  | 	return &UnsuccessfulOperationError{ | ||||||
|  | 		Operate: OEPRATE_QUERY, | ||||||
|  | 		Message: message, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewUnsuccessCalculateError(message string) *UnsuccessfulOperationError { | ||||||
|  | 	return &UnsuccessfulOperationError{ | ||||||
|  | 		Operate: OPERATE_CALCULATE, | ||||||
|  | 		Message: message, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewUnsuccessDBError(message string) *UnsuccessfulOperationError { | ||||||
|  | 	return &UnsuccessfulOperationError{ | ||||||
|  | 		Operate: OPERATE_DB, | ||||||
|  | 		Message: message, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewUnsuccessDBTransactionError(message string) *UnsuccessfulOperationError { | ||||||
|  | 	return &UnsuccessfulOperationError{ | ||||||
|  | 		Operate: OPERATE_DB_TRANSACTION, | ||||||
|  | 		Message: message, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewUnsuccessCustomError(describe, message string) *UnsuccessfulOperationError { | ||||||
|  | 	return &UnsuccessfulOperationError{ | ||||||
|  | 		Operate:     OPERATE_CUSTOM, | ||||||
|  | 		Description: describe, | ||||||
|  | 		Message:     message, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewUnsuccessOtherError(message string) *UnsuccessfulOperationError { | ||||||
|  | 	return &UnsuccessfulOperationError{ | ||||||
|  | 		Operate: OPERATE_OTHER, | ||||||
|  | 		Message: message, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e UnsuccessfulOperationError) Error() string { | ||||||
|  | 	var builder strings.Builder | ||||||
|  | 	switch e.Operate { | ||||||
|  | 	case OPERATE_CREATE: | ||||||
|  | 		builder.WriteString("创建") | ||||||
|  | 	case OPERATE_UPDATE: | ||||||
|  | 		builder.WriteString("更新") | ||||||
|  | 	case OPERATE_DELETE: | ||||||
|  | 		builder.WriteString("删除") | ||||||
|  | 	case OEPRATE_QUERY: | ||||||
|  | 		builder.WriteString("查询") | ||||||
|  | 	case OPERATE_CALCULATE: | ||||||
|  | 		builder.WriteString("计算") | ||||||
|  | 	case OPERATE_DB: | ||||||
|  | 		builder.WriteString("数据库") | ||||||
|  | 	case OPERATE_DB_TRANSACTION: | ||||||
|  | 		builder.WriteString("数据库事务") | ||||||
|  | 	case OPERATE_CUSTOM: | ||||||
|  | 		builder.WriteString(e.Description) | ||||||
|  | 	case OPERATE_OTHER: | ||||||
|  | 		builder.WriteString("其他") | ||||||
|  | 	default: | ||||||
|  | 		builder.WriteString("未知") | ||||||
|  | 	} | ||||||
|  | 	builder.WriteString("操作不成功,") | ||||||
|  | 	if len(e.Message) > 0 { | ||||||
|  | 		builder.WriteString(e.Message) | ||||||
|  | 	} else { | ||||||
|  | 		builder.WriteString("未知原因") | ||||||
|  | 	} | ||||||
|  | 	return builder.String() | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										115
									
								
								global/db.go
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								global/db.go
									
									
									
									
									
								
							| @@ -1,60 +1,91 @@ | |||||||
| package global | package global | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"database/sql" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"electricity_bill_calc/config" | 	"electricity_bill_calc/config" | ||||||
| 	"electricity_bill_calc/logger" | 	"electricity_bill_calc/logger" | ||||||
|  |  | ||||||
| 	"github.com/uptrace/bun" | 	"github.com/jackc/pgx/v5" | ||||||
| 	"github.com/uptrace/bun/dialect/pgdialect" | 	"github.com/jackc/pgx/v5/pgxpool" | ||||||
| 	"github.com/uptrace/bun/driver/pgdriver" | 	"github.com/samber/lo" | ||||||
| 	"go.uber.org/zap/zapcore" | 	"go.uber.org/zap" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	DB *bun.DB | 	DB *pgxpool.Pool | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func SetupDatabaseConnection() error { | func SetupDatabaseConnection() error { | ||||||
| 	// connStr := fmt.Sprintf( | 	connString := fmt.Sprintf( | ||||||
| 	// 	"host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai connect_timeout=0 tcp_user_timeout=180000", | 		"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.Host, | 		config.DatabaseSettings.User, | ||||||
| 	// 	config.DatabaseSettings.User, | 		config.DatabaseSettings.Pass, | ||||||
| 	// 	config.DatabaseSettings.Pass, | 		config.DatabaseSettings.Host, | ||||||
| 	// 	config.DatabaseSettings.DB, | 		config.DatabaseSettings.Port, | ||||||
| 	// 	config.DatabaseSettings.Port, | 		config.DatabaseSettings.DB, | ||||||
| 	// ) | 		0, | ||||||
| 	pgconn := pgdriver.NewConnector( | 		"elec_service_go", | ||||||
| 		pgdriver.WithNetwork("tcp"), | 		config.DatabaseSettings.MaxOpenConns, | ||||||
| 		pgdriver.WithAddr(fmt.Sprintf("%s:%d", config.DatabaseSettings.Host, | 		config.DatabaseSettings.MaxIdleConns, | ||||||
| 			config.DatabaseSettings.Port)), | 		"60m", | ||||||
| 		pgdriver.WithUser(config.DatabaseSettings.User), | 		"10m", | ||||||
| 		pgdriver.WithInsecure(true), | 		"10s", | ||||||
| 		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), |  | ||||||
| 	) | 	) | ||||||
| 	sqldb := sql.OpenDB(pgconn) | 	poolConfig, err := pgxpool.ParseConfig(connString) | ||||||
| 	DB = bun.NewDB(sqldb, pgdialect.New()) | 	if err != nil { | ||||||
| 	DB.AddQueryHook(logger.NewQueryHook(logger.QueryHookOptions{ | 		logger.Named("DB INIT").Error("数据库连接初始化失败。", zap.Error(err)) | ||||||
| 		LogSlow:         3 * time.Second, | 		return err | ||||||
| 		Logger:          logger.Named("PG"), | 	} | ||||||
| 		QueryLevel:      zapcore.DebugLevel, | 	poolConfig.ConnConfig.Tracer = QueryLogger{logger: logger.Named("PG")} | ||||||
| 		ErrorLevel:      zapcore.ErrorLevel, | 	DB, _ = pgxpool.NewWithConfig(context.Background(), poolConfig) | ||||||
| 		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() |  | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type QueryLogger struct { | ||||||
|  | 	logger *zap.Logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ql QueryLogger) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context { | ||||||
|  | 	ql.logger.Info(fmt.Sprintf("将要执行查询: %s", data.SQL)) | ||||||
|  | 	ql.logger.Info("查询参数", lo.Map(data.Args, func(elem any, index int) zap.Field { | ||||||
|  | 		return zap.Any(fmt.Sprintf("[Arg %d]: ", index), elem) | ||||||
|  | 	})...) | ||||||
|  | 	//	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)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -15,10 +15,13 @@ var ( | |||||||
|  |  | ||||||
| func SetupRedisConnection() error { | func SetupRedisConnection() error { | ||||||
| 	var err error | 	var err error | ||||||
|  | 	a := fmt.Sprintf("%s:%d", config.RedisSettings.Host, config.RedisSettings.Port) | ||||||
|  | 	fmt.Println(a) | ||||||
| 	Rd, err = rueidis.NewClient(rueidis.ClientOption{ | 	Rd, err = rueidis.NewClient(rueidis.ClientOption{ | ||||||
| 		InitAddress: []string{fmt.Sprintf("%s:%d", config.RedisSettings.Host, config.RedisSettings.Port)}, | 		InitAddress: []string{"127.0.0.1:6379"}, | ||||||
| 		Password:    config.RedisSettings.Password, | 		Password:    "", | ||||||
| 		SelectDB:    config.RedisSettings.DB, | 		SelectDB:    config.RedisSettings.DB, | ||||||
|  | 		DisableCache:true, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
|   | |||||||
							
								
								
									
										74
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										74
									
								
								go.mod
									
									
									
									
									
								
							| @@ -4,67 +4,83 @@ go 1.19 | |||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/deckarep/golang-set/v2 v2.1.0 | 	github.com/deckarep/golang-set/v2 v2.1.0 | ||||||
| 	github.com/fufuok/utils v0.7.13 | 	github.com/fufuok/utils v0.10.2 | ||||||
| 	github.com/gofiber/fiber/v2 v2.38.1 | 	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/google/uuid v1.3.0 | ||||||
|  | 	github.com/jackc/pgx/v5 v5.3.1 | ||||||
| 	github.com/jinzhu/copier v0.3.5 | 	github.com/jinzhu/copier v0.3.5 | ||||||
| 	github.com/liamylian/jsontime/v2 v2.0.0 | 	github.com/liamylian/jsontime/v2 v2.0.0 | ||||||
| 	github.com/mozillazg/go-pinyin v0.19.0 | 	github.com/mozillazg/go-pinyin v0.20.0 | ||||||
| 	github.com/rueian/rueidis v0.0.73 | 	github.com/rueian/rueidis v0.0.100 | ||||||
| 	github.com/samber/lo v1.27.0 | 	github.com/samber/lo v1.38.1 | ||||||
| 	github.com/shopspring/decimal v1.3.1 | 	github.com/shopspring/decimal v1.3.1 | ||||||
| 	github.com/spf13/viper v1.12.0 | 	github.com/spf13/viper v1.16.0 | ||||||
| 	github.com/valyala/fasthttp v1.40.0 | 	github.com/valyala/fasthttp v1.47.0 | ||||||
| 	github.com/xuri/excelize/v2 v2.6.1 | 	github.com/xuri/excelize/v2 v2.7.1 | ||||||
| 	go.uber.org/zap v1.23.0 | 	go.uber.org/zap v1.24.0 | ||||||
| 	gopkg.in/natefinch/lumberjack.v2 v2.0.0 | 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | 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/jinzhu/inflection v1.0.0 // indirect | ||||||
| 	github.com/klauspost/compress v1.15.0 // indirect | 	github.com/jmoiron/sqlx v1.3.5 // indirect | ||||||
| 	github.com/rogpeppe/go-internal v1.8.0 // indirect | 	github.com/klauspost/compress v1.16.6 // indirect | ||||||
|  | 	github.com/mattn/go-colorable v0.1.13 // indirect | ||||||
|  | 	github.com/mattn/go-isatty v0.0.19 // indirect | ||||||
|  | 	github.com/mattn/go-runewidth v0.0.14 // indirect | ||||||
|  | 	github.com/philhofer/fwd v1.1.2 // indirect | ||||||
|  | 	github.com/rivo/uniseg v0.4.4 // indirect | ||||||
|  | 	github.com/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/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect | ||||||
| 	github.com/valyala/bytebufferpool v1.0.0 // indirect | 	github.com/valyala/bytebufferpool v1.0.0 // indirect | ||||||
| 	github.com/valyala/tcplisten v1.0.0 // indirect | 	github.com/valyala/tcplisten v1.0.0 // indirect | ||||||
| 	github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect | 	github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect | ||||||
| 	github.com/vmihailenco/tagparser/v2 v2.0.0 // 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 | 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect | ||||||
| 	mellium.im/sasl v0.3.0 // indirect | 	mellium.im/sasl v0.3.0 // indirect | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | 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/hashicorp/hcl v1.0.0 // indirect | ||||||
| 	github.com/json-iterator/go v1.1.12 // indirect | 	github.com/json-iterator/go v1.1.12 // indirect | ||||||
| 	github.com/magiconair/properties v1.8.6 // indirect | 	github.com/magiconair/properties v1.8.7 // indirect | ||||||
| 	github.com/mitchellh/mapstructure v1.5.0 // indirect | 	github.com/mitchellh/mapstructure v1.5.0 // indirect | ||||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||||
| 	github.com/modern-go/reflect2 v1.0.2 // indirect | 	github.com/modern-go/reflect2 v1.0.2 // indirect | ||||||
| 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect | 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect | ||||||
| 	github.com/pelletier/go-toml v1.9.5 // 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/mscfb v1.0.4 // indirect | ||||||
| 	github.com/richardlehane/msoleps v1.0.3 // indirect | 	github.com/richardlehane/msoleps v1.0.3 // indirect | ||||||
| 	github.com/spf13/afero v1.8.2 // indirect | 	github.com/spf13/afero v1.9.5 // indirect | ||||||
| 	github.com/spf13/cast v1.5.0 // indirect | 	github.com/spf13/cast v1.5.1 // indirect | ||||||
| 	github.com/spf13/jwalterweatherman v1.1.0 // indirect | 	github.com/spf13/jwalterweatherman v1.1.0 // indirect | ||||||
| 	github.com/spf13/pflag v1.0.5 // indirect | 	github.com/spf13/pflag v1.0.5 // indirect | ||||||
| 	github.com/subosito/gotenv v1.3.0 // indirect | 	github.com/subosito/gotenv v1.4.2 // indirect | ||||||
| 	github.com/uptrace/bun v1.1.8 | 	github.com/uptrace/bun v1.1.8 | ||||||
| 	github.com/uptrace/bun/dialect/pgdialect v1.1.8 | 	github.com/uptrace/bun/dialect/pgdialect v1.1.8 | ||||||
| 	github.com/uptrace/bun/driver/pgdriver 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/efp v0.0.0-20230422071738-01f4e37c47e9 // indirect | ||||||
| 	github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect | 	github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83 // indirect | ||||||
| 	go.uber.org/atomic v1.10.0 // indirect | 	go.uber.org/atomic v1.11.0 // indirect | ||||||
| 	go.uber.org/multierr v1.8.0 // indirect | 	go.uber.org/multierr v1.11.0 // indirect | ||||||
| 	golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect | 	golang.org/x/crypto v0.10.0 // indirect | ||||||
| 	golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect | 	golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect | ||||||
| 	golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect | 	golang.org/x/net v0.10.0 // indirect | ||||||
| 	golang.org/x/sys v0.0.0-20220907062415-87db552b00fd // indirect | 	golang.org/x/sys v0.9.0 // indirect | ||||||
| 	golang.org/x/text v0.3.7 // indirect | 	golang.org/x/text v0.10.0 // indirect | ||||||
| 	gopkg.in/ini.v1 v1.66.4 // indirect | 	gopkg.in/ini.v1 v1.67.0 // indirect | ||||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										164
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								go.sum
									
									
									
									
									
								
							| @@ -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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= | ||||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | 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/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 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= | ||||||
| github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= | 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/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= | ||||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | 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= | 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/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 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= | ||||||
| github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= | 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.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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||||
| github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= | 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/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/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.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 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= | ||||||
| github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= | 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 h1:FGx8Mnfg0ZB8HdVz1X60JJ2kFu1rtcsFDYUxUTzNKkU= | ||||||
| github.com/fufuok/utils v0.7.13/go.mod h1:ztIaorPqZGdbvmW3YlwQp80K8rKJmEy6xa1KwpJSsmk= | 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 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-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-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 h1:GEQ/Yt3Wsf2a30iTqtLXlBYJZso0JXPovt/tmj5H9jU= | ||||||
| github.com/gofiber/fiber/v2 v2.38.1/go.mod h1:t0NlbaXzuGH7I+7M4paE848fNWInZ7mfxI/Er1fTth8= | 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/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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||||
| github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/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/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-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/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 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= | ||||||
| github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= | 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 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= | ||||||
| github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= | 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.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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | ||||||
| github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | 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/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 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= | ||||||
| github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= | 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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= | ||||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | 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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | ||||||
| github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= | 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/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.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | 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 h1:3if2kDW/boymUdO+4Qj/m4uaXMBSF6np9KEgg90cwH0= | ||||||
| github.com/liamylian/jsontime/v2 v2.0.0/go.mod h1:UHp1oAPqCBfspokvGmaGe0IAl2IgOpgOgDaKPcvcGGY= | 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 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= | ||||||
| github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= | 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 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= | ||||||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | 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= | 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/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 h1:p+J8/kjJ558KPvVGYLvqBhxf8jbZA2exSLCs2uUVN8c= | ||||||
| github.com/mozillazg/go-pinyin v0.19.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc= | 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 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= | ||||||
| github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= | 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 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= | ||||||
| github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= | 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/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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | 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.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= | ||||||
| github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= | github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= | ||||||
| github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= | 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.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 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= | ||||||
| github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= | 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 h1:+r0Z6C6HMnkquPgY3zaHVpTqmCyJL56Z36GSlyBrufk= | ||||||
| github.com/rueian/rueidis v0.0.73/go.mod h1:FwnfDILF2GETrvXcYFlhIiru/7NmSIm1f+7C5kutO0I= | 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 h1:GOyDWxsblvqYobqsmUuMddPa2/mMzkKyojlXol4+LaQ= | ||||||
| github.com/samber/lo v1.27.0/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg= | 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 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= | ||||||
| github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= | 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 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= | ||||||
| github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= | 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 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= | ||||||
| github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= | 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 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= | ||||||
| github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= | 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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||||||
| github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | 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 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= | ||||||
| github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= | 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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | 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.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.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 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 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= | ||||||
| github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= | 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/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 h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= | ||||||
| github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= | 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= | 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/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 h1:CRq/00MfruPGFLTQKY8b+8SfdK60TxNztjRMnH0t1Yc= | ||||||
| github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= | 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 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= | ||||||
| github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= | ||||||
| github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= | 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/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 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-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 h1:ICBdtw803rmhLN3zfvyEGH3cwSmZv+kde7LhTDT659k= | ||||||
| github.com/xuri/excelize/v2 v2.6.1/go.mod h1:tL+0m6DNwSXj/sILHbQTYsLi9IF4TW59H2EF3Yrx1AU= | 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 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-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.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||||
| github.com/yuin/goldmark v1.1.27/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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||||
| github.com/yuin/goldmark v1.2.1/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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||||
| go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | ||||||
| go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | 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.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | ||||||
| go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= | 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.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/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 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= | ||||||
| go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= | 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 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= | ||||||
| go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= | 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-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-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-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-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-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-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-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-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-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 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-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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||||
| golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | 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-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 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-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-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-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 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.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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | ||||||
| golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | 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.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.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||||
| golang.org/x/mod v0.4.1/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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-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-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-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 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= | ||||||
| golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= | 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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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-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-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-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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | 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-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-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-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-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 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-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-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.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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
| golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= | 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.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-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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
| golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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-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-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-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-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-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-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-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||||
| golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/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.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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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/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 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= | ||||||
| gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | 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 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= | ||||||
| gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= | 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.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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||||||
| gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||||||
|   | |||||||
| @@ -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) |  | ||||||
| } |  | ||||||
| @@ -1,8 +1,10 @@ | |||||||
| package logger | package logger | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"electricity_bill_calc/types" | ||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
|  | 	"github.com/shopspring/decimal" | ||||||
| 	"go.uber.org/zap" | 	"go.uber.org/zap" | ||||||
| 	"go.uber.org/zap/zapcore" | 	"go.uber.org/zap/zapcore" | ||||||
| ) | ) | ||||||
| @@ -40,7 +42,7 @@ func init() { | |||||||
|  |  | ||||||
| 	logger = zap.New(core).Named("App") | 	logger = zap.New(core).Named("App") | ||||||
| 	sugaredLogger = logger.Sugar() | 	sugaredLogger = logger.Sugar() | ||||||
| 	logger.Info("Logger initialized.") | 	logger.Info("日志系统初始化完成。") | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetLogger() *zap.Logger { | func GetLogger() *zap.Logger { | ||||||
| @@ -130,3 +132,53 @@ func With(fields ...zap.Field) *zap.Logger { | |||||||
| func WithSugar(fields ...zap.Field) *zap.SugaredLogger { | func WithSugar(fields ...zap.Field) *zap.SugaredLogger { | ||||||
| 	return logger.With(fields...).Sugar() | 	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) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -62,9 +62,13 @@ func NewLogMiddleware(config LogMiddlewareConfig) fiber.Handler { | |||||||
| 		fields := []zap.Field{ | 		fields := []zap.Field{ | ||||||
| 			zap.Namespace("context"), | 			zap.Namespace("context"), | ||||||
| 			zap.String("pid", strconv.Itoa(os.Getpid())), | 			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.String("time", stop.Sub(start).String()), | ||||||
| 			zap.Object("response", Resp(c.Response())), | 			// zap.Object("response", Resp(c.Response())), | ||||||
| 			zap.Object("request", Req(c)), | 			// zap.Object("request", Req(c)), | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if u := c.Locals("userId"); u != nil { | 		if u := c.Locals("userId"); u != nil { | ||||||
|   | |||||||
| @@ -1,9 +1,12 @@ | |||||||
| package logger | package logger | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"log" | 	"log" | ||||||
|  | 	"math" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"gopkg.in/natefinch/lumberjack.v2" | 	"gopkg.in/natefinch/lumberjack.v2" | ||||||
| ) | ) | ||||||
| @@ -14,10 +17,11 @@ func newRollingWriter() io.Writer { | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	now := time.Now() | ||||||
| 	return &lumberjack.Logger{ | 	return &lumberjack.Logger{ | ||||||
| 		Filename:   "log/service.log", | 		Filename:   fmt.Sprintf("log/service_%s.log", now.Format("2006-01-02_15")), | ||||||
| 		MaxBackups: 366 * 10, // files | 		MaxBackups: math.MaxInt, // files | ||||||
| 		MaxSize:    200,      // megabytes | 		MaxSize:    200,         // megabytes | ||||||
| 		MaxAge:     366 * 10, // days | 		MaxAge:     math.MaxInt, // days | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										152
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								main.go
									
									
									
									
									
								
							| @@ -1,25 +1,18 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"database/sql" |  | ||||||
| 	"electricity_bill_calc/cache" | 	"electricity_bill_calc/cache" | ||||||
| 	"electricity_bill_calc/config" | 	"electricity_bill_calc/config" | ||||||
| 	"electricity_bill_calc/global" | 	"electricity_bill_calc/global" | ||||||
| 	"electricity_bill_calc/logger" | 	"electricity_bill_calc/logger" | ||||||
| 	"electricity_bill_calc/migration" |  | ||||||
| 	"electricity_bill_calc/model" | 	"electricity_bill_calc/model" | ||||||
|  | 	"electricity_bill_calc/repository" | ||||||
| 	"electricity_bill_calc/router" | 	"electricity_bill_calc/router" | ||||||
| 	"electricity_bill_calc/service" | 	"electricity_bill_calc/service" | ||||||
| 	"encoding/csv" | 	"electricity_bill_calc/types" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| 	"strconv" |  | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/samber/lo" |  | ||||||
| 	"github.com/shopspring/decimal" |  | ||||||
| 	"github.com/uptrace/bun/migrate" |  | ||||||
| 	"go.uber.org/zap" | 	"go.uber.org/zap" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -27,145 +20,70 @@ func init() { | |||||||
| 	l := logger.Named("Init") | 	l := logger.Named("Init") | ||||||
| 	err := config.SetupSetting() | 	err := config.SetupSetting() | ||||||
| 	if err != nil { | 	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() | 	err = global.SetupDatabaseConnection() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		l.Fatal("Main Database connect failed.", zap.Error(err)) | 		l.Fatal("主数据库连接失败!", 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.Info("主数据库已经连接。") | ||||||
|  |  | ||||||
| 	err = global.SetupRedisConnection() | 	err = global.SetupRedisConnection() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		l.Fatal("Main Cache Database connect failed.", zap.Error(err)) | 		l.Fatal("主缓存数据库连接失败!", zap.Error(err)) | ||||||
| 	} | 	} | ||||||
| 	l.Info("Main Cache Database connected!") | 	l.Info("主缓存数据库已经连接。") | ||||||
|  |  | ||||||
| 	err = initializeRegions() |  | ||||||
| 	if err != nil { |  | ||||||
| 		l.Fatal("Regions initialize failed.", zap.Error(err)) |  | ||||||
| 	} |  | ||||||
| 	l.Info("Regions synchronized.") |  | ||||||
|  |  | ||||||
| 	err = intializeSingularity() | 	err = intializeSingularity() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		l.Fatal("Singularity account intialize failed.", zap.Error(err)) | 		l.Fatal("奇点账号初始化失败。", zap.Error(err)) | ||||||
| 	} | 	} | ||||||
| 	l.Info("Singularity account intialized.") | 	l.Info("奇点账号已经完成初始化。") | ||||||
| } |  | ||||||
|  |  | ||||||
| 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 |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func intializeSingularity() error { | func intializeSingularity() error { | ||||||
| 	singularityExists, err := service.UserService.IsUserExists("000") | 	l := logger.Named("Init", "Singularity") | ||||||
|  | 	singularityExists, err := repository.UserRepository.IsUserExists("000") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("singularity detect failed: %w", err) | 		l.Error("检测奇点账号失败。", zap.Error(err)) | ||||||
|  | 		return fmt.Errorf("检测奇点账号失败: %w", err) | ||||||
| 	} | 	} | ||||||
| 	if singularityExists { | 	if singularityExists { | ||||||
|  | 		l.Info("奇点账号已经存在,跳过剩余初始化步骤。") | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	singularity := &model.User{ | 	singularityId := "000" | ||||||
| 		Id:       "000", | 	singularityExpires, err := types.ParseDate("2099-12-31") | ||||||
|  | 	if err != nil { | ||||||
|  | 		l.Error("奇点用户账号过期时间解析失败。", zap.Error(err)) | ||||||
|  | 		return fmt.Errorf("奇点用户账号过期时间解析失败: %w", err) | ||||||
|  | 	} | ||||||
|  | 	singularity := &model.ManagementAccountCreationForm{ | ||||||
|  | 		Id:       &singularityId, | ||||||
| 		Username: "singularity", | 		Username: "singularity", | ||||||
|  | 		Name:     "Singularity", | ||||||
| 		Type:     2, | 		Type:     2, | ||||||
| 		Enabled:  true, | 		Enabled:  true, | ||||||
|  | 		Expires:  singularityExpires, | ||||||
| 	} | 	} | ||||||
| 	singularityName := "Singularity" | 	verifyCode, err := service.UserService.CreateUserAccount( | ||||||
| 	singularityExpires, err := model.ParseDate("2099-12-31") | 		singularity.IntoUser(), | ||||||
|  | 		singularity.IntoUserDetail()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("singularity expires time parse failed: %w", err) | 		l.Error("创建奇点账号失败。", zap.Error(err)) | ||||||
| 	} | 		return fmt.Errorf("创建奇点账号失败: %w", err) | ||||||
| 	singularityDetail := &model.UserDetail{ |  | ||||||
| 		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) |  | ||||||
| 	} | 	} | ||||||
| 	logger.Info( | 	logger.Info( | ||||||
| 		fmt.Sprintf("Singularity account created, use %s as verify code to reset password.", verifyCode), | 		fmt.Sprintf("奇点账号已经完成创建, 首次登录需要使用验证码 [%s] 重置密码。", *verifyCode), | ||||||
| 		zap.String("account", "singularity"), | 		zap.String("账号名称", "singularity"), | ||||||
| 		zap.String("verifyCode", verifyCode), | 		zap.String("验证码", *verifyCode), | ||||||
| 	) | 	) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func DBConnectionKeepLive() { | // 清理Redis缓存中的孤儿键。 | ||||||
| 	for range time.Tick(30 * time.Second) { |  | ||||||
| 		err := global.DB.Ping() |  | ||||||
| 		if err != nil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func RedisOrphanCleanup() { | func RedisOrphanCleanup() { | ||||||
| 	cleanLogger := logger.Named("Cache").With(zap.String("function", "Cleanup")) | 	cleanLogger := logger.Named("Cache").With(zap.String("function", "Cleanup")) | ||||||
| 	for range time.Tick(2 * time.Minute) { | 	for range time.Tick(2 * time.Minute) { | ||||||
| @@ -179,8 +97,6 @@ func RedisOrphanCleanup() { | |||||||
| } | } | ||||||
|  |  | ||||||
| func main() { | func main() { | ||||||
| 	// 本次停用检测的原因是:使用Ping来保持数据库链接看起来没有什么用处。 |  | ||||||
| 	// go DBConnectionKeepLive() |  | ||||||
| 	go RedisOrphanCleanup() | 	go RedisOrphanCleanup() | ||||||
| 	app := router.App() | 	app := router.App() | ||||||
| 	app.Listen(fmt.Sprintf(":%d", config.ServerSettings.HttpPort)) | 	app.Listen(fmt.Sprintf(":%d", config.ServerSettings.HttpPort)) | ||||||
|   | |||||||
| @@ -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) |  | ||||||
| ); |  | ||||||
| @@ -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; |  | ||||||
| @@ -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); |  | ||||||
| @@ -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; |  | ||||||
| @@ -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); |  | ||||||
| @@ -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 |  | ||||||
|     ); |  | ||||||
| @@ -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; |  | ||||||
| @@ -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); |  | ||||||
| @@ -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)) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										213
									
								
								model/calculate/calculate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								model/calculate/calculate.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										32
									
								
								model/charge.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										10
									
								
								model/cunsumption.go
									
									
									
									
									
										Normal 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"` | ||||||
|  | } | ||||||
| @@ -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
									
								
							
							
						
						
									
										90
									
								
								model/enums.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										45
									
								
								model/invoice.go
									
									
									
									
									
										Normal 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, "") | ||||||
|  | } | ||||||
| @@ -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
									
								
							
							
						
						
									
										106
									
								
								model/meter.go
									
									
									
									
									
										Normal 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"` | ||||||
|  | } | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -1,89 +1,45 @@ | |||||||
| package model | package model | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"electricity_bill_calc/types" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/jinzhu/copier" |  | ||||||
| 	"github.com/shopspring/decimal" | 	"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 { | type Park struct { | ||||||
| 	bun.BaseModel      `bun:"table:park,alias:p"` | 	Id               string              `json:"id"` | ||||||
| 	CreatedAndModified `bun:"extend"` | 	UserId           string              `json:"userId"` | ||||||
| 	Deleted            `bun:"extend"` | 	Name             string              `json:"name"` | ||||||
| 	Id                 string              `bun:",pk,notnull" json:"id"` | 	Abbr             string              `json:"-"` | ||||||
| 	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"` |  | ||||||
| 	Area             decimal.NullDecimal `json:"area"` | 	Area             decimal.NullDecimal `json:"area"` | ||||||
| 	TenementQuantity decimal.NullDecimal `json:"tenement"` | 	TenementQuantity decimal.NullDecimal `json:"tenement"` | ||||||
| 	Capacity         decimal.NullDecimal `json:"capacity"` | 	Capacity         decimal.NullDecimal `json:"capacity"` | ||||||
| 	Category         int8                `bun:"type:smallint,notnull" json:"category"` | 	Category         int16               `json:"category"` | ||||||
| 	SubmeterType     int8                `bun:"meter_04kv_type,type:smallint,notnull" json:"meter04kvType"` | 	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"` | 	Region           *string             `json:"region"` | ||||||
| 	Address          *string             `json:"address"` | 	Address          *string             `json:"address"` | ||||||
| 	Contact          *string             `json:"contact"` | 	Contact          *string             `json:"contact"` | ||||||
| 	Phone            *string             `json:"phone"` | 	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 { | type ParkPeriodStatistics struct { | ||||||
| 	Id     string `bun:"park__id,notnull" json:"id"` | 	Id string `json:"id"` | ||||||
| 	Name   string `bun:"park__name,notnull" json:"name"` | 	Name string `json:"name"` | ||||||
| 	Period *Date  `bun:"type:date" json:"period"` | 	Period *types.DateRange | ||||||
| } |  | ||||||
|  |  | ||||||
| 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 |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								model/park_building.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								model/park_building.go
									
									
									
									
									
										Normal 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"` | ||||||
|  | } | ||||||
| @@ -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
									
								
							
							
						
						
									
										56
									
								
								model/reading.go
									
									
									
									
									
										Normal 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"` | ||||||
|  | } | ||||||
| @@ -1,11 +1,8 @@ | |||||||
| package model | package model | ||||||
|  |  | ||||||
| import "github.com/uptrace/bun" |  | ||||||
|  |  | ||||||
| type Region struct { | type Region struct { | ||||||
| 	bun.BaseModel `bun:"table:region,alias:r"` | 	Code   string `json:"code"` | ||||||
| 	Code          string `bun:",pk,notnull" json:"code"` | 	Name   string `json:"name"` | ||||||
| 	Name          string `bun:",notnull" json:"name"` | 	Level  int32  `json:"level"` | ||||||
| 	Level         int    `bun:",notnull" json:"level"` | 	Parent string `json:"parent"` | ||||||
| 	Parent        string `bun:",notnull" json:"parent"` |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										194
									
								
								model/report.go
									
									
									
									
									
								
							
							
						
						
									
										194
									
								
								model/report.go
									
									
									
									
									
								
							| @@ -1,99 +1,139 @@ | |||||||
| package model | package model | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"electricity_bill_calc/types" | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/uptrace/bun" | 	"github.com/shopspring/decimal" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | type ReportIndex struct { | ||||||
| 	REPORT_NOT_WITHDRAW int8 = iota | 	Id                          string          `json:"id"` | ||||||
| 	REPORT_WITHDRAW_APPLIED | 	Park                        string          `json:"parkId" db:"park_id"` | ||||||
| 	REPORT_WITHDRAW_DENIED | 	Period                      types.DateRange `json:"period"` | ||||||
| 	REPORT_WITHDRAW_GRANTED | 	Category                    int16           `json:"category"` | ||||||
| ) | 	MeterType                   int16           `json:"meter04kvType" db:"meter_04kv_type"` | ||||||
|  | 	PricePolicy                 int16           `json:"pricePolicy"` | ||||||
| type Report struct { | 	BasisPooled                 int16           `json:"basisPooled"` | ||||||
| 	bun.BaseModel         `bun:"table:report,alias:r"` | 	AdjustPooled                int16           `json:"adjustPooled"` | ||||||
| 	CreatedAndModified    `bun:"extend"` | 	LossPooled                  int16           `json:"lossPooled"` | ||||||
| 	Id                    string            `bun:",pk,notnull" json:"id"` | 	PublicPooled                int16           `json:"publicPooled"` | ||||||
| 	ParkId                string            `bun:",notnull" json:"parkId"` | 	Published                   bool            `json:"published"` | ||||||
| 	Period                time.Time         `bun:"type:date,notnull" json:"period" time_format:"simple_date" time_location:"shanghai"` | 	PublishedAt                 *types.DateTime `json:"publishedAt" db:"published_at"` | ||||||
| 	Category              int8              `bun:"type:smallint,notnull" json:"category"` | 	Withdraw                    int16           `json:"withdraw"` | ||||||
| 	SubmeterType          int8              `bun:"meter_04kv_type,type:smallint,notnull" json:"meter04kvType"` | 	LastWithdrawAppliedAt       *types.DateTime `json:"lastWithdrawAppliedAt" db:"last_withdraw_applied_at"` | ||||||
| 	StepState             Steps             `bun:"type:jsonb,notnull" json:"stepState"` | 	LastWithdrawAuditAt         *types.DateTime `json:"lastWithdrawAuditAt" db:"last_withdraw_audit_at"` | ||||||
| 	Published             bool              `bun:",notnull" json:"published"` | 	Status                      *int16          `json:"status"` | ||||||
| 	PublishedAt           *time.Time        `bun:"type:timestamptz,nullzero" json:"publishedAt" time_format:"simple_datetime" time_location:"shanghai"` | 	Message                     *string         `json:"message"` | ||||||
| 	Withdraw              int8              `bun:"type:smallint,notnull" json:"withdraw"` | 	CreatedAt                   types.DateTime  `json:"createdAt" db:"created_at"` | ||||||
| 	LastWithdrawAppliedAt *time.Time        `bun:"type:timestamptz,nullzero" json:"lastWithdrawAppliedAt" time_format:"simple_datetime" time_location:"shanghai"` | 	LastModifiedAt              types.DateTime  `json:"lastModifiedAt" db:"last_modified_at"` | ||||||
| 	LastWithdrawAuditAt   *time.Time        `bun:"type:timestamptz,nullzero" json:"lastWithdrawAuditAt" time_format:"simple_datetime" time_location:"shanghai"` | 	AuthorizedLossRate          float64         `json:"authorized_loss_rate" db:"authorized_loss_rate"` | ||||||
| 	Park                  *Park             `bun:"rel:belongs-to,join:park_id=id" json:"-"` | 	AuthorizedLossRateIncrement float64         `json:"authorized_loss_rate_increment" db:"authorized_loss_rate_increment"` | ||||||
| 	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 Steps struct { | type ReportSummary struct { | ||||||
| 	Summary     bool `json:"summary"` | 	ReportId                     string              `json:"reportId" db:"report_id"` | ||||||
| 	WillDiluted bool `json:"willDiluted"` | 	OverallArea                  decimal.Decimal     `json:"overallArea" db:"overall_area"` | ||||||
| 	Submeter    bool `json:"submeter"` | 	Overall                      ConsumptionUnit     `json:"overall"` | ||||||
| 	Calculate   bool `json:"calculate"` | 	ConsumptionFee               decimal.NullDecimal `json:"consumptionFee" db:"consumption_fee"` | ||||||
| 	Preview     bool `json:"preview"` | 	Critical                     ConsumptionUnit     `json:"critical"` | ||||||
| 	Publish     bool `json:"publish"` | 	Peak                         ConsumptionUnit     `json:"peak"` | ||||||
|  | 	Flat                         ConsumptionUnit     `json:"flat"` | ||||||
|  | 	Valley                       ConsumptionUnit     `json:"valley"` | ||||||
|  | 	Loss                         decimal.NullDecimal `json:"loss"` | ||||||
|  | 	LossFee                      decimal.NullDecimal `json:"lossFee" db:"loss_fee"` | ||||||
|  | 	LossProportion               decimal.NullDecimal `json:"lossProportion" db:"loss_proportion"` | ||||||
|  | 	AuthorizeLoss                *ConsumptionUnit    `json:"authorizeLoss" db:"authorize_loss"` | ||||||
|  | 	BasicFee                     decimal.Decimal     `json:"basicFee" db:"basic_fee"` | ||||||
|  | 	BasicPooledPriceConsumption  decimal.NullDecimal `json:"basicPooledPriceConsumption" db:"basic_pooled_price_consumption"` | ||||||
|  | 	BasicPooledPriceArea         decimal.NullDecimal `json:"basicPooledPriceArea" db:"basic_pooled_price_area"` | ||||||
|  | 	AdjustFee                    decimal.Decimal     `json:"adjustFee" db:"adjust_fee"` | ||||||
|  | 	AdjustPooledPriceConsumption decimal.NullDecimal `json:"adjustPooledPriceConsumption" db:"adjust_pooled_price_consumption"` | ||||||
|  | 	AdjustPooledPriceArea        decimal.NullDecimal `json:"adjustPooledPriceArea" db:"adjust_pooled_price_area"` | ||||||
|  | 	LossDilutedPrice             decimal.NullDecimal `json:"lossDilutedPrice" db:"loss_diluted_price"` | ||||||
|  | 	TotalConsumption             decimal.Decimal     `json:"totalConsumption" db:"total_consumption"` | ||||||
|  | 	FinalDilutedOverall          decimal.NullDecimal `json:"finalDilutedOverall" db:"final_diluted_overall"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewSteps() Steps { | func (rs ReportSummary) GetConsumptionFee() decimal.Decimal { | ||||||
| 	return Steps{ | 	if !rs.ConsumptionFee.Valid { | ||||||
| 		Summary:     false, | 		return rs.Overall.Fee.Sub(rs.BasicFee).Sub(rs.AdjustFee) | ||||||
| 		WillDiluted: false, |  | ||||||
| 		Submeter:    false, |  | ||||||
| 		Calculate:   false, |  | ||||||
| 		Preview:     false, |  | ||||||
| 		Publish:     false, |  | ||||||
| 	} | 	} | ||||||
|  | 	return rs.ConsumptionFee.Decimal | ||||||
| } | } | ||||||
|  |  | ||||||
| type ParkNewestReport struct { | type ReportPublicConsumption struct { | ||||||
| 	Park   Park    `bun:"extends" json:"park"` | 	ReportId         string          `json:"reportId" db:"report_id"` | ||||||
| 	Report *Report `bun:"extends" json:"report"` | 	MeterId          string          `json:"parkMeterId" db:"park_meter_id"` | ||||||
|  | 	Overall          ConsumptionUnit `json:"overall"` | ||||||
|  | 	Critical         ConsumptionUnit `json:"critical"` | ||||||
|  | 	Peak             ConsumptionUnit `json:"peak"` | ||||||
|  | 	Flat             ConsumptionUnit `json:"flat"` | ||||||
|  | 	Valley           ConsumptionUnit `json:"valley"` | ||||||
|  | 	LossAdjust       ConsumptionUnit `json:"lossAdjust"` | ||||||
|  | 	ConsumptionTotal decimal.Decimal `json:"consumptionTotal" db:"consumption_total"` | ||||||
|  | 	LossAdjustTotal  decimal.Decimal `json:"lossAdjustTotal" db:"loss_adjust_total"` | ||||||
|  | 	FinalTotal       decimal.Decimal `json:"finalTotal" db:"final_total"` | ||||||
|  | 	PublicPooled     int16           `json:"publicPooled" db:"public_pooled"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *ParkNewestReport) AfterLoad() { | type ReportDetailedPublicConsumption struct { | ||||||
| 	if p.Report != nil && len(p.Report.Id) == 0 { | 	MeterDetail | ||||||
| 		p.Report = nil | 	ReportPublicConsumption | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type ReportIndexSimplified struct { | type ReportPooledConsumption struct { | ||||||
| 	bun.BaseModel         `bun:"table:report,alias:r"` | 	ReportId   string          `json:"reportId" db:"report_id"` | ||||||
| 	Id                    string     `bun:",pk,notnull" json:"id"` | 	MeterId    string          `json:"pooledMeterId" db:"pooled_meter_id"` | ||||||
| 	ParkId                string     `bun:",notnull" json:"parkId"` | 	Overall    ConsumptionUnit `json:"overall"` | ||||||
| 	Period                Date       `bun:"type:date,notnull" json:"period"` | 	Critical   ConsumptionUnit `json:"critical"` | ||||||
| 	StepState             Steps      `bun:"type:jsonb,notnull" json:"stepState"` | 	Peak       ConsumptionUnit `json:"peak"` | ||||||
| 	Published             bool       `bun:",notnull" json:"published"` | 	Flat       ConsumptionUnit `json:"flat"` | ||||||
| 	PublishedAt           *time.Time `bun:"type:timestampz" json:"publishedAt" time_format:"simple_datetime" time_location:"shanghai"` | 	Valley     ConsumptionUnit `json:"valley"` | ||||||
| 	Withdraw              int8       `bun:"type:smallint,notnull" json:"withdraw"` | 	PooledArea decimal.Decimal `json:"pooledArea" db:"pooled_area"` | ||||||
| 	LastWithdrawAppliedAt *time.Time `bun:"type:timestamptz" json:"lastWithdrawAppliedAt" time_format:"simple_datetime" time_location:"shanghai"` | 	Diluted    []NestedMeter   `json:"diluted"` | ||||||
| 	LastWithdrawAuditAt   *time.Time `bun:"type:timestamptz" json:"lastWithdrawAuditAt" time_format:"simple_datetime" time_location:"shanghai"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type JoinedReportForWithdraw struct { | type ReportDetailedPooledConsumption struct { | ||||||
| 	Report Report               `bun:"extends" json:"report"` | 	MeterDetail | ||||||
| 	Park   ParkSimplified       `bun:"extends" json:"park"` | 	ReportPooledConsumption | ||||||
| 	User   UserDetailSimplified `bun:"extends" json:"user"` | 	PublicPooled int16 `json:"publicPooled"` | ||||||
| } | } | ||||||
|  |  | ||||||
| var _ bun.BeforeAppendModelHook = (*Report)(nil) | type ReportDetailNestedMeterConsumption struct { | ||||||
|  | 	Meter       MeterDetail `json:"meter"` | ||||||
| func (p *Report) BeforeAppendModel(ctx context.Context, query bun.Query) error { | 	Consumption NestedMeter `json:"consumption"` | ||||||
| 	oprTime := time.Now() | } | ||||||
| 	switch query.(type) { |  | ||||||
| 	case *bun.InsertQuery: | type ReportTenement struct { | ||||||
| 		p.CreatedAt = oprTime | 	ReportId        string          `json:"reportId" db:"report_id"` | ||||||
| 		p.LastModifiedAt = &oprTime | 	Tenement        string          `json:"tenementId" db:"tenement_id"` | ||||||
| 	case *bun.UpdateQuery: | 	Detail          Tenement        `json:"tenementDetail" db:"tenement_detail"` | ||||||
| 		p.LastModifiedAt = &oprTime | 	Period          types.DateRange `json:"calcPeriod" db:"calc_period"` | ||||||
| 	} | 	Overall         ConsumptionUnit `json:"overall"` | ||||||
| 	return nil | 	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"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -5,7 +5,7 @@ import "time" | |||||||
| type Session struct { | type Session struct { | ||||||
| 	Uid       string    `json:"uid"` | 	Uid       string    `json:"uid"` | ||||||
| 	Name      string    `json:"name"` | 	Name      string    `json:"name"` | ||||||
| 	Type      int8      `json:"type"` | 	Type      int16     `json:"type"` | ||||||
| 	Token     string    `json:"token"` | 	Token     string    `json:"token"` | ||||||
| 	ExpiresAt time.Time `json:"expiresAt" time_format:"simple_datetime" time_location:"shanghai"` | 	ExpiresAt time.Time `json:"expiresAt" time_format:"simple_datetime" time_location:"shanghai"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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
									
								
							
							
						
						
									
										33
									
								
								model/tenement.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										33
									
								
								model/top_up.go
									
									
									
									
									
										Normal 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 | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										118
									
								
								model/types.go
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								model/types.go
									
									
									
									
									
								
							| @@ -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 |  | ||||||
| } |  | ||||||
							
								
								
									
										130
									
								
								model/user.go
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								model/user.go
									
									
									
									
									
								
							| @@ -1,48 +1,114 @@ | |||||||
| package model | package model | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"electricity_bill_calc/types" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/uptrace/bun" | 	"github.com/shopspring/decimal" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	USER_TYPE_ENT int8 = iota | 	USER_TYPE_ENT int16 = iota | ||||||
| 	USER_TYPE_SUP | 	USER_TYPE_SUP | ||||||
| 	USER_TYPE_OPS | 	USER_TYPE_OPS | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type User struct { | type ManagementAccountCreationForm struct { | ||||||
| 	bun.BaseModel `bun:"table:user,alias:u"` | 	Id       *string    `json:"id"` | ||||||
| 	Created       `bun:"extend"` | 	Username string     `json:"username"` | ||||||
| 	Id            string        `bun:",pk,notnull" json:"id"` | 	Name     string     `json:"name"` | ||||||
| 	Username      string        `bun:",notnull" json:"username"` | 	Contact  *string    `json:"contact"` | ||||||
| 	Password      string        `bun:",notnull" json:"-"` | 	Phone    *string    `json:"phone"` | ||||||
| 	ResetNeeded   bool          `bun:",notnull" json:"resetNeeded"` | 	Type     int16      `json:"type"` | ||||||
| 	Type          int8          `bun:"type:smallint,notnull" json:"type"` | 	Enabled  bool       `json:"enabled"` | ||||||
| 	Enabled       bool          `bun:",notnull" json:"enabled"` | 	Expires  types.Date `json:"expires"` | ||||||
| 	Detail        *UserDetail   `bun:"rel:has-one,join:id=id" json:"-"` |  | ||||||
| 	Charges       []*UserCharge `bun:"rel:has-many,join:id=user_id" json:"-"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type UserWithCredentials struct { | func (m ManagementAccountCreationForm) IntoUser() *User { | ||||||
| 	bun.BaseModel `bun:"table:user,alias:u"` | 	return &User{ | ||||||
| 	Created       `bun:"extend"` | 		Id:          *m.Id, | ||||||
| 	Id            string `bun:",pk,notnull" json:"id"` | 		Username:    m.Username, | ||||||
| 	Username      string `bun:",notnull" json:"username"` | 		Password:    "", | ||||||
| 	Password      string `bun:",notnull" json:"credential"` | 		ResetNeeded: false, | ||||||
| 	ResetNeeded   bool   `bun:",notnull" json:"resetNeeded"` | 		UserType:    m.Type, | ||||||
| 	Type          int8   `bun:"type:smallint,notnull" json:"type"` | 		Enabled:     m.Enabled, | ||||||
| 	Enabled       bool   `bun:",notnull" json:"enabled"` | 		CreatedAt:   nil, | ||||||
| } |  | ||||||
|  |  | ||||||
| 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() |  | ||||||
| 	} | 	} | ||||||
| 	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"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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
									
								
							
							
						
						
									
										84
									
								
								model/withdraw.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										241
									
								
								repository/calculate.go
									
									
									
									
									
										Normal 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), | ||||||
|  | 			// TODO:2023.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
									
								
							
							
						
						
									
										152
									
								
								repository/charge.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										449
									
								
								repository/god_mode.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										348
									
								
								repository/invoice.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										991
									
								
								repository/meter.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										419
									
								
								repository/park.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										79
									
								
								repository/region.go
									
									
									
									
									
										Normal 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, ®ions, 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, ®ion, regionQuerySql, regionParams...); err != nil { | ||||||
|  | 		r.log.Error("获取指定行政区划信息失败!", zap.Error(err)) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return ®ion, 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
									
								
							
							
						
						
									
										846
									
								
								repository/report.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										458
									
								
								repository/tenement.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										166
									
								
								repository/top_up.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										419
									
								
								repository/user.go
									
									
									
									
									
										Normal 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
		Reference in New Issue
	
	Block a user