Compare commits

..

No commits in common. "0.2" and "master" have entirely different histories.
0.2 ... master

148 changed files with 7459 additions and 13923 deletions

View File

@ -1,28 +0,0 @@
<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
View File

@ -1,11 +0,0 @@
<?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>

View File

@ -1,8 +0,0 @@
<?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
View File

@ -1,6 +0,0 @@
<?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
View File

@ -1,8 +0,0 @@
<?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
View File

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

View File

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

Binary file not shown.

40
cache/abstract.go vendored
View File

@ -40,7 +40,7 @@ func Cache[T interface{}](key string, value *T, expires time.Duration) error {
}
// 从Redis缓存中获取一个数据
func Retrieve[T interface{}](key string) (*T, error) {
func Retreive[T interface{}](key string) (*T, error) {
getCmd := global.Rd.B().Get().Key(key).Build()
result := global.Rd.Do(global.Ctx, getCmd)
if result.Error() != nil {
@ -81,23 +81,23 @@ func Delete(key string) (bool, error) {
return count > 0, err
}
func dissembleScan(result rueidis.RedisResult) (uint64, []string, error) {
func dissembleScan(result rueidis.RedisResult) (int64, []string, error) {
var (
err error
cursor uint64
cursor int64
keys = make([]string, 0)
)
results, err := result.ToArray()
if err != nil {
return 0, keys, err
return -1, keys, err
}
cursor, err = results[0].AsUint64()
cursor, err = results[0].AsInt64()
if err != nil {
return 0, keys, err
return -1, keys, err
}
keys, err = results[1].AsStrSlice()
if err != nil {
return 0, keys, err
return -1, keys, err
}
return cursor, keys, err
}
@ -106,7 +106,7 @@ func dissembleScan(result rueidis.RedisResult) (uint64, []string, error) {
func DeleteAll(pattern string) error {
var (
err error
cursor uint64
cursor int64
keys = make([]string, 0)
sKeys []string
)
@ -137,27 +137,3 @@ func CacheKey(category string, ids ...string) string {
}
return b.String()
}
type ToString[T any] interface {
ToString() string
*T
}
// 用于生成一个内容可以为空的Redis缓存键这个键的类型必须实现了`ToString`接口。
func NullableConditionKey[P any, T ToString[P]](value T, defaultStr ...string) string {
defaultStr = append(defaultStr, "UNDEF")
if value == nil {
return defaultStr[0]
} else {
return value.ToString()
}
}
// 用于生成一个内容可以为空的字符串指针类型的Redis缓存键。
func NullableStringKey(value *string, defaultStr ...string) string {
defaultStr = append(defaultStr, "UNDEF")
if value == nil {
return defaultStr[0]
}
return *value
}

10
cache/count.go vendored
View File

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

12
cache/entity.go vendored
View File

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

6
cache/exists.go vendored
View File

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

12
cache/relation.go vendored
View File

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

62
cache/search.go vendored
View File

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

4
cache/session.go vendored
View File

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

View File

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

View File

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

View File

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

237
controller/end_user.go Normal file
View File

@ -0,0 +1,237 @@
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},
)
}

View File

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

View File

@ -1,132 +1,176 @@
package controller
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"errors"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"net/http"
"github.com/gofiber/fiber/v2"
)
var GmLog = logger.Named("Handler", "GM")
func InitializeGmController(router *fiber.App) {
router.Delete("/gm/tenement", security.SingularityAuthorize, deleteTenement)
router.Delete("/gm/park", security.SingularityAuthorize, deletePark)
router.Delete("/gm/report", security.SingularityAuthorize, deleteReports)
router.Delete("/gm/tenement/meter", security.SingularityAuthorize, deleteTenementMeterRelations)
router.Delete("/gm/enterprise", security.SingularityAuthorize, deleteEnterprise)
router.Delete("/gm/meter/pooling", security.SingularityAuthorize, deleteMeterPoolingRelations)
router.Delete("gm/meter", security.SingularityAuthorize, deleteMeters)
}
//用于将参数转化为切片
func getQueryValues(c *fiber.Ctx, paramName string) []string {
values := c.Request().URI().QueryArgs().PeekMulti(paramName)
result := make([]string, len(values))
for i, v := range values {
result[i] = string(v)
func InitializeGodModeController(router *fiber.App) {
gmR := router.Group("/gm")
{
gmR.Delete("/report/:rid/summary", security.SingularityAuthorize, gmResetReportSummary)
gmR.Delete("/report/:rid/maintenance", security.SingularityAuthorize, gmResetReportMaintenance)
gmR.Delete("/report/:rid/meters", security.SingularityAuthorize, gmResetReportEndUserRecord)
gmR.Post("/report/:rid/meters", security.SingularityAuthorize, gmResynchronizeReportEndUserRecord)
gmR.Delete("/report/:rid", security.SingularityAuthorize, gmResetReport)
gmR.Delete("/report/:rid/force", security.SingularityAuthorize, gmDeleteReport)
gmR.Delete("/park/:pid/maintenance/:mid", security.SingularityAuthorize, gmDeleteSpecificMaintenance)
gmR.Delete("/park/:pid/maintenance", security.SingularityAuthorize, gmDeleteAllMaintenance)
gmR.Delete("/park/:pid/meters", security.SingularityAuthorize, gmDeleteAllMeters)
gmR.Delete("/park/:pid/force", security.SingularityAuthorize, gmDeletePark)
gmR.Delete("/enterprise/:uid/force", security.SingularityAuthorize, gmDeleteUser)
}
return result
}
func deleteTenement(c *fiber.Ctx) error {
park := c.Query("park", "")
tenements := getQueryValues(c, "tenements")
func gmResetReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
GmLog.Info("[天神模式]删除指定园区中的商户", zap.String("park", park), zap.Strings("tenements", tenements))
err := service.GMService.DeleteTenements(park, tenements)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ClearReportSummary(requestReportId)
if err != nil {
GmLog.Error("[天神模式]删除指定园区中的商户失败", zap.Error(err))
return result.Error(500, err.Error())
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("指定商户已经删除。")
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的园区总览部分。")
}
return result.Success("指定报表的园区总览已经重置。")
}
func deletePark(c *fiber.Ctx) error {
parks := getQueryValues(c, "parks")
func gmResetReportMaintenance(c *fiber.Ctx) error {
result := response.NewResult(c)
GmLog.Info("[天神模式]删除指定园区", zap.Strings("parks", parks))
if len(parks) < 0 {
GmLog.Info("[天神模式]用户未指派园区参数或者未指定需要删除的园区。")
return result.Error(http.StatusBadRequest, error.Error(errors.New("必须至少指定一个需要删除的园区!")))
}
err := service.GMService.DeleteParks(parks)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ClearReportMaintenances(requestReportId)
if err != nil {
GmLog.Error("[天神模式]删除指定园区失败", zap.Error(err))
return result.Error(500, err.Error())
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("指定园区已经删除。")
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的配电维护费部分。")
}
return result.Success("指定报表的配电维护费已经重置。")
}
func deleteReports(c *fiber.Ctx) error {
pid := c.Query("park")
reports := getQueryValues(c, "reports")
func gmResynchronizeReportEndUserRecord(c *fiber.Ctx) error {
result := response.NewResult(c)
GmLog.Info("[天神模式]删除符合条件的报表。", zap.Strings("reports", reports))
err := service.GMService.DeleteReports(pid, reports)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ResynchronizeEndUser(requestReportId)
if err != nil {
GmLog.Error("[天神模式]删除指定园区中的报表失败。", zap.Error(err))
return result.Error(500, err.Error())
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的抄表记录基本档案。")
}
return result.Success("指定报表的抄表记录基本档案已经重新同步。")
}
func gmResetReportEndUserRecord(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ResetEndUserRegisterRecords(requestReportId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的抄表记录部分。")
}
return result.Success("指定报表的抄表记录已经重置。")
}
func gmResetReport(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ResetReport(requestReportId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表。")
}
return result.Success("指定报表已经重置。")
}
func gmDeleteReport(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.DeleteReport(requestReportId)
if err != nil {
if ipErr, ok := err.(exceptions.ImproperOperateError); ok {
return result.NotAccept(ipErr.Message)
} else {
return result.Error(http.StatusInternalServerError, err.Error())
}
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除指定报表。")
}
return result.Success("指定报表已经删除。")
}
func deleteEnterprise(c *fiber.Ctx) error {
uid := c.Query("uid")
func gmDeleteSpecificMaintenance(c *fiber.Ctx) error {
result := response.NewResult(c)
GmLog.Info("[天神模式]删除指定企业用户", zap.String("uid", uid))
err := service.GMService.DeleteEnterprises(uid)
requestParkId := c.Params("pid")
requestMaintenanceId := c.Params("mid")
done, err := service.GodModeService.RemoveSpecificMaintenance(requestParkId, requestMaintenanceId)
if err != nil {
GmLog.Error("[天神模式]删除指定企业用户失败", zap.Error(err))
return result.Error(500, "删除指定企业用户失败。")
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("指定企业用户已经删除。")
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除指定的维护费用记录。")
}
return result.Success("指定维护费用记录已经删除。")
}
func deleteTenementMeterRelations(c *fiber.Ctx) error {
func gmDeleteAllMaintenance(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Query("park")
tId := getQueryValues(c, "tenements")
metersId := getQueryValues(c, "meters")
GmLog.Info("删除指定园区中的商户与表计的关联关系", zap.String("park id", parkId))
if err := service.GMService.DeleteTenementMeterRelations(parkId, tId, metersId); err != nil {
meterLog.Error("无法删除指定园区中的商户与表计的关联关系", zap.Error(err))
return result.NotAccept(err.Error())
requestParkId := c.Params("pid")
done, err := service.GodModeService.RemoveAllMaintenance(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("删除成功")
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除全部维护费用记录。")
}
return result.Success("全部维护费用记录已经删除。")
}
func deleteMeterPoolingRelations(c *fiber.Ctx) error {
func gmDeleteAllMeters(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Query("park")
mId := getQueryValues(c, "meters")
GmLog.Info("[天神模式]删除指定园区中的表计公摊关系", zap.String("park id", parkId))
if err := service.GMService.DeleteMeterPooling(parkId, mId); err != nil {
meterLog.Error("[天神模式]删除指定园区中的表计公摊关系失败", zap.Error(err))
return result.Error(500, "删除指定园区中的表计公摊关系失败。")
requestParkId := c.Params("pid")
done, err := service.GodModeService.RemoveAllMeters(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("指定表计公摊关系已经删除。")
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除全部终端表计档案记录。")
}
return result.Success("全部终端表计档案记录已经删除。")
}
func deleteMeters(c *fiber.Ctx) error {
func gmDeletePark(c *fiber.Ctx) error {
result := response.NewResult(c)
parkId := c.Query("park")
mId := getQueryValues(c, "meters")
GmLog.Info("[天神模式]删除指定园区中的表计", zap.String("park id", parkId))
if err := service.GMService.DeleteMeters(parkId, mId); err != nil {
meterLog.Error("[天神模式]删除指定园区中的表计失败", zap.Error(err))
return result.Error(500, "删除指定园区中的表计失败。")
requestParkId := c.Params("pid")
done, err := service.GodModeService.RemovePark(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("指定表计已经删除。")
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除指定的园区。")
}
return result.Success("指定的园区已经删除。")
}
func gmDeleteUser(c *fiber.Ctx) error {
result := response.NewResult(c)
requestUserId := c.Params("uid")
done, err := service.GodModeService.DeleteUser(requestUserId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除指定的用户。")
}
return result.Success("指定的用户及其关联信息已经删除。")
}

View File

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

View File

@ -0,0 +1,202 @@
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},
)
}

View File

@ -1,502 +0,0 @@
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})
}

188
controller/meter04kv.go Normal file
View File

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

View File

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

View File

@ -1,22 +1,22 @@
package controller
import (
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"electricity_bill_calc/service"
"net/http"
"github.com/gofiber/fiber/v2"
)
func InitializeRegionHandlers(router *fiber.App) {
router.Get("/region/:rid", getSubRegions)
router.Get("/regions/:rid", getParentRegions)
func InitializeRegionController(router *fiber.App) {
router.Get("/region/:rid", fetchRegions)
router.Get("/regions/:rid", fetchAllLeveledRegions)
}
func getSubRegions(c *fiber.Ctx) error {
func fetchRegions(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParentId := c.Params("rid")
regions, err := repository.RegionRepository.FindSubRegions(requestParentId)
regions, err := service.RegionService.FetchSubRegions(requestParentId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
@ -26,10 +26,10 @@ func getSubRegions(c *fiber.Ctx) error {
return result.Json(http.StatusOK, "已经获取到相关的行政区划。", fiber.Map{"regions": regions})
}
func getParentRegions(c *fiber.Ctx) error {
func fetchAllLeveledRegions(c *fiber.Ctx) error {
result := response.NewResult(c)
requestRegionCode := c.Params("rid")
regions, err := repository.RegionRepository.FindParentRegions(requestRegionCode)
regions, err := service.RegionService.FetchAllParentRegions(requestRegionCode)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}

View File

@ -1,425 +1,301 @@
package controller
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/exceptions"
"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"
"net/http"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
"github.com/samber/lo"
"go.uber.org/zap"
"github.com/shopspring/decimal"
)
var reportLog = logger.Named("Handler", "Report")
func InitializeReportHandlers(router *fiber.App) {
router.Get("/reports", security.MustAuthenticated, reportComprehensiveSearch)
router.Post("/report", security.EnterpriseAuthorize, initNewReportCalculateTask)
router.Get("/report/draft", security.EnterpriseAuthorize, listDraftReportIndicies)
//TODO: 2023-07-20将calcualte错误请求改为正确的calculate请求
router.Post("/report/calculate", security.EnterpriseAuthorize, testCalculateReportSummary)
router.Get("/report/calculate/status", security.EnterpriseAuthorize, listCalculateTaskStatus)
router.Get("/report/:rid", security.EnterpriseAuthorize, getReportDetail)
router.Put("/report/:rid", security.EnterpriseAuthorize, updateReportCalculateTask)
func InitializeReportController(router *fiber.App) {
router.Get("/reports/with/drafts", security.EnterpriseAuthorize, fetchNewestReportOfParkWithDraft)
router.Post("/park/:pid/report", security.EnterpriseAuthorize, initializeNewReport)
router.Get("/report/:rid/step/state", security.EnterpriseAuthorize, fetchReportStepStates)
router.Get("/report/:rid/summary", security.EnterpriseAuthorize, fetchReportParkSummary)
router.Put("/report/:rid/summary", security.EnterpriseAuthorize, fillReportSummary)
router.Get("/report/:rid/summary/calculate", security.EnterpriseAuthorize, testCalculateReportSummary)
router.Post("/report/:rid/summary/calculate", security.EnterpriseAuthorize, progressReportSummary)
router.Put("/report/:rid/step/meter/register", security.EnterpriseAuthorize, progressEndUserRegister)
router.Post("/report/:rid/publish", security.EnterpriseAuthorize, publishReport)
router.Put("/report/:rid/calculate", security.EnterpriseAuthorize, initiateCalculateTask)
router.Get("/report/:rid/publics", security.MustAuthenticated, listPublicMetersInReport)
router.Get("/report/:rid/summary", security.MustAuthenticated, getReportSummary)
router.Get("/report/:rid/summary/filled", security.EnterpriseAuthorize, getParkFilledSummary)
router.Get("/report/:rid/pooled", security.MustAuthenticated, listPooledMetersInReport)
router.Get("/report/:rid/pooled/:code/submeter", security.MustAuthenticated, listSubmetersInPooledMeter)
router.Get("/report/:rid/tenement", security.MustAuthenticated, listTenementsInReport)
router.Get("/report/:rid/tenement/:tid", security.MustAuthenticated, getTenementDetailInReport)
router.Get("/reports", security.MustAuthenticated, searchReports)
router.Get("/report/:rid", security.MustAuthenticated, fetchReportPublicity)
router.Post("/report/:rid/calculate", security.EnterpriseAuthorize, calculateReport)
}
// 检查指定报表是否属于当前用户
func checkReportBelongs(reportId string, log *zap.Logger, c *fiber.Ctx, result *response.Result) (bool, error) {
session, err := _retreiveSession(c)
func ensureReportBelongs(c *fiber.Ctx, result *response.Result, requestReportId string) (bool, error) {
_, err := _retreiveSession(c)
if err != nil {
log.Error("无法获取当前用户的会话信息", zap.Error(err))
return false, result.Unauthorized("无法获取当前用户的会话信息。")
return false, result.Unauthorized(err.Error())
}
ok, err := repository.ReportRepository.IsBelongsTo(reportId, session.Uid)
requestReport, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
log.Error("无法检查核算报表的所有权", zap.Error(err))
return false, result.Error(fiber.StatusInternalServerError, "无法检查核算报表的所有权。")
return false, result.NotFound(err.Error())
}
if !ok {
log.Error("核算报表不属于当前用户")
return false, result.Forbidden("核算报表不属于当前用户。")
if requestReport == nil {
return false, result.NotFound("指定报表未能找到。")
}
return true, nil
return ensureParkBelongs(c, result, requestReport.ParkId)
}
// 获取当前登录用户下所有园区的尚未发布的核算报表索引
func listDraftReportIndicies(c *fiber.Ctx) error {
func fetchNewestReportOfParkWithDraft(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
userSession, err := _retreiveSession(c)
if err != nil {
reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return result.Unauthorized("无法获取当前用户的会话信息。")
return result.Unauthorized(err.Error())
}
reportLog.Info("检索指定用户下的未发布核算报表索引", zap.String("User", session.Uid))
indicies, err := service.ReportService.ListDraftReportIndicies(session.Uid)
parks, err := service.ReportService.FetchParksWithNewestReport(userSession.Uid)
if err != nil {
reportLog.Error("无法获取当前用户的核算报表索引", zap.Error(err))
return result.NotFound("当前用户下未找到核算报表索引。")
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success(
"已经获取到指定用户的报表索引。",
fiber.Map{"reports": indicies},
)
return result.Json(http.StatusOK, "已获取到指定用户下所有园区的最新报表记录。", fiber.Map{"parks": parks})
}
// 初始化一个新的核算任务
func initNewReportCalculateTask(c *fiber.Ctx) error {
func initializeNewReport(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
requestParkId := c.Params("pid")
userSession, err := _retreiveSession(c)
if err != nil {
reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return result.Unauthorized("无法获取当前用户的会话信息。")
return result.Unauthorized(err.Error())
}
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 {
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
}
ok, err := repository.ReportRepository.CreateReport(&form)
requestPeriod := c.Query("period")
reportPeriod, err := time.Parse("2006-01", requestPeriod)
if err != nil {
reportLog.Error("无法创建核算报表", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法创建核算报表。")
return result.NotAccept("提供的初始化期数格式不正确。")
}
if !ok {
reportLog.Error("未能完成核算报表的保存。")
return result.NotAccept("未能完成核算报表的保存。")
valid, err := service.ReportService.IsNewPeriodValid(userSession.Uid, requestParkId, reportPeriod)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("已经成功创建核算报表。")
if !valid {
return result.NotAccept("只能初始化已发布报表下一个月份的新报表。")
}
newId, err := service.ReportService.InitializeNewReport(requestParkId, reportPeriod)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("新一期报表初始化成功。", fiber.Map{"reportId": newId})
}
// 更新指定的核算任务
func updateReportCalculateTask(c *fiber.Ctx) error {
func fetchReportStepStates(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass {
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
var form vo.ReportModifyForm
if err := c.BodyParser(&form); err != nil {
reportLog.Error("无法解析更新核算报表的请求数据。", zap.Error(err))
return result.BadRequest("无法解析更新核算报表的请求数据。")
}
ok, err := repository.ReportRepository.UpdateReportSummary(reportId, &form)
requestReport, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
reportLog.Error("无法更新核算报表", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法更新核算报表。")
return result.NotFound(err.Error())
}
if !ok {
reportLog.Error("未能完成核算报表的更新。")
return result.NotAccept("未能完成核算报表的更新。")
}
return result.Success("已经成功更新核算报表。")
return result.Json(http.StatusOK, "已经获取到指定报表的填写状态。", fiber.Map{"steps": requestReport.StepState})
}
// 启动指定的核算任务
func initiateCalculateTask(c *fiber.Ctx) error {
func fetchReportParkSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass {
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
err := service.ReportService.DispatchReportCalculate(reportId)
summary, err := service.ReportService.RetreiveReportSummary(requestReportId)
if err != nil {
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, "无法获取核算报表的园区电量信息。")
return result.NotFound(err.Error())
}
if summary == nil {
reportLog.Error("未找到核算报表的园区电量信息")
return result.NotFound("未找到核算报表的园区电量信息。")
return result.NotFound("指定报表未能找到。")
}
var summaryResponse vo.SimplifiedReportSummary
copier.Copy(&summaryResponse, summary)
return result.Success(
"已经获取到核算报表的园区电量信息。",
fiber.Map{"summary": summaryResponse},
)
return result.Json(http.StatusOK, "已经获取到指定报表中的园区概况。", fiber.Map{"summary": summary})
}
type ReportSummaryFormData struct {
Overall decimal.Decimal `json:"overall" form:"overall"`
OverallFee decimal.Decimal `json:"overallFee" form:"overallFee"`
Critical decimal.Decimal `json:"critical" form:"critical"`
CriticalFee decimal.Decimal `json:"criticalFee" form:"criticalFee"`
Peak decimal.Decimal `json:"peak" form:"peak"`
PeakFee decimal.Decimal `json:"peakFee" form:"peakFee"`
Valley decimal.Decimal `json:"valley" form:"valley"`
ValleyFee decimal.Decimal `json:"valleyFee" form:"valleyFee"`
BasicFee decimal.Decimal `json:"basicFee" form:"basicFee"`
AdjustFee decimal.Decimal `json:"adjustFee" from:"adjustFee"`
}
func fillReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
formData := new(ReportSummaryFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
originSummary, err := service.ReportService.RetreiveReportSummary(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
copier.Copy(originSummary, formData)
originSummary.ReportId = requestReportId
err = service.ReportService.UpdateReportSummary(originSummary)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定电费公示报表中的园区概况基本数据已经完成更新。")
}
// 对提供的园区电量信息进行试计算,返回试计算结果
func testCalculateReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
reportLog.Info("试计算园区电量信息")
var form vo.TestCalculateForm
if err := c.BodyParser(&form); err != nil {
reportLog.Error("无法解析试计算核算报表的请求数据。", zap.Error(err))
return result.BadRequest("无法解析试计算核算报表的请求数据。")
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
return result.Success(
"电量电费试计算已经完成。",
fiber.Map{"summary": form.Calculate()},
)
}
// 获取指定园区中尚未发布的核算报表计算状态
func listCalculateTaskStatus(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
summary, err := service.ReportService.RetreiveReportSummary(requestReportId)
if err != nil {
reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return result.Unauthorized("无法获取当前用户的会话信息。")
return result.NotFound(err.Error())
}
status, err := repository.ReportRepository.GetReportTaskStatus(session.Uid)
if err != nil {
reportLog.Error("无法获取核算报表计算状态", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表计算状态。")
}
statusResponse := make([]*vo.ReportCalculateTaskStatusResponse, 0)
copier.Copy(&statusResponse, &status)
return result.Success(
"已经获取到核算报表计算状态。",
fiber.Map{"status": statusResponse},
)
}
// 获取指定报表的详细信息
func getReportDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
reportLog.Info("获取核算报表的详细信息", zap.String("Report", reportId))
user, park, report, err := service.ReportService.RetrieveReportIndexDetail(reportId)
if err != nil {
reportLog.Error("无法获取核算报表的详细信息", zap.Error(err))
return result.NotFound("无法获取核算报表的详细信息。")
}
return result.Success(
"已经获取到核算报表的详细信息。",
summary.CalculatePrices()
calcResults := tools.ConvertStructToMap(summary)
return result.Json(
http.StatusOK,
"已完成园区概况的试计算。",
fiber.Map{
"detail": vo.NewReportDetailQueryResponse(user, park, report),
"result": lo.PickByKeys(
calcResults,
[]string{"overallPrice", "criticalPrice", "peakPrice", "flat", "flatFee", "flatPrice", "valleyPrice", "consumptionFee"},
),
},
)
}
// 获取指定核算报表的总览信息
func getReportSummary(c *fiber.Ctx) error {
func progressReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
report, err := repository.ReportRepository.RetrieveReportSummary(reportId)
if err != nil {
reportLog.Error("无法获取核算报表的总览信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表的总览信息。")
}
if report == nil {
reportLog.Error("未找到核算报表的总览信息")
return result.NotFound("未找到核算报表的总览信息。")
}
var summaryResponse vo.ParkSummaryResponse
copier.Copy(&summaryResponse, report)
return result.Success(
"已经获取到核算报表的总览信息。",
fiber.Map{"summary": summaryResponse},
)
}
// 获取指定报表中分页的公共表计的核算摘要信息
func listPublicMetersInReport(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
reportLog.Info("获取核算报表中的公共表计信息", zap.String("Report", reportId))
page := c.QueryInt("page", 1)
keyword := tools.EmptyToNil(c.Query("keyword"))
meters, total, err := repository.ReportRepository.ListPublicMetersInReport(reportId, uint(page), keyword)
if err != nil {
reportLog.Error("无法获取核算报表中的公共表计信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的公共表计信息。")
}
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 listPooledMetersInReport(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
reportLog.Info("获取核算报表中的公摊表计信息", zap.String("Report", reportId))
page := c.QueryInt("page", 1)
keyword := tools.EmptyToNil(c.Query("keyword"))
meters, total, err := repository.ReportRepository.ListPooledMetersInReport(reportId, uint(page), keyword)
if err != nil {
reportLog.Error("无法获取核算报表中的公摊表计信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的公摊表计信息。")
}
meterResponse := lo.Map(meters, func(meter *model.ReportDetailedPooledConsumption, _ int) *vo.ReportPooledQueryResponse {
m := &vo.ReportPooledQueryResponse{}
m.FromReportDetailPooledConsumption(meter)
return m
})
return result.Success(
"已经获取到指定核算报表中的分页公摊表计的核算信息。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"pooled": meterResponse},
)
}
// 列出指定报表中指定公共表计下各个分摊表计的消耗数据
func listSubmetersInPooledMeter(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
meterId := c.Params("code")
if len(meterId) == 0 {
reportLog.Error("未提供公共表计的编号")
return result.BadRequest("未提供公共表计的编号。")
}
meters, err := repository.ReportRepository.ListPooledMeterDetailInReport(reportId, meterId)
if err != nil {
reportLog.Error("无法获取核算报表中的公共表计信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的公共表计信息。")
}
meterResponse := lo.Map(meters, func(meter *model.ReportDetailNestedMeterConsumption, _ int) *vo.ReportPooledQueryResponse {
m := &vo.ReportPooledQueryResponse{}
m.FromReportDetailNestedMeterConsumption(meter)
return m
})
return result.Success(
"已经获取到指定核算报表中的公共表计的核算信息。",
fiber.Map{"meters": meterResponse},
)
}
// 获取指定报表中分页的商户核算电量电费概要数据
func listTenementsInReport(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
page := c.QueryInt("page", 1)
keyword := tools.EmptyToNil(c.Query("keyword"))
tenements, total, err := repository.ReportRepository.ListTenementInReport(reportId, uint(page), keyword)
if err != nil {
reportLog.Error("无法获取核算报表中的商户信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的商户信息。")
}
tenementsResponse := lo.Map(tenements, func(tenement *model.ReportTenement, _ int) *vo.ReportTenementSummaryResponse {
t := &vo.ReportTenementSummaryResponse{}
t.FromReportTenement(tenement)
return t
})
return result.Success(
"已经获取到指定核算报表中的分页商户的核算信息。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"tenements": tenementsResponse},
)
}
// 获取指定报表中指定商户的详细核算信息
func getTenementDetailInReport(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
tenementId := c.Params("tid")
detail, err := repository.ReportRepository.GetTenementDetailInReport(reportId, tenementId)
if err != nil {
reportLog.Error("无法获取核算报表中的商户信息", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的商户信息。")
}
var detailResponse vo.ReportTenementDetailResponse
detailResponse.FromReportTenement(detail)
return result.Success(
"已经获取到指定核算报表中的商户的详细核算信息。",
fiber.Map{"detail": detailResponse},
)
}
// 发布指定的核算报表
func publishReport(c *fiber.Ctx) error {
result := response.NewResult(c)
reportId := c.Params("rid")
if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass {
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
ok, err := repository.ReportRepository.PublishReport(reportId)
err := service.ReportService.CalculateSummaryAndFinishStep(requestReportId)
if err != nil {
reportLog.Error("无法发布核算报表", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "发布核算报表出错。")
if nfErr, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound(nfErr.Error())
} else {
return result.Error(http.StatusInternalServerError, err.Error())
}
}
if !ok {
reportLog.Error("未能完成核算报表的发布。")
return result.NotAccept("未能完成核算报表的发布。")
}
return result.Success("已经成功发布核算报表。")
return result.Success("已经完成园区概况的计算,并可以进行到下一步骤。")
}
// 对核算报表进行综合检索
func reportComprehensiveSearch(c *fiber.Ctx) error {
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)
user := tools.EmptyToNil(c.Query("user"))
session, err := _retreiveSession(c)
if err != nil {
reportLog.Error("无法获取当前用户的会话信息", zap.Error(err))
return result.Unauthorized("无法获取当前用户的会话信息。")
return result.Unauthorized(err.Error())
}
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 {
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
}
}
var requestUser *string
if session.Type == model.USER_TYPE_ENT {
requestUser = lo.ToPtr(tools.DefaultTo(user, session.Uid))
} else {
requestUser = user
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)
}
page := c.QueryInt("page", 1)
keyword := tools.EmptyToNil(c.Query("keyword"))
startDate, err := types.ParseDatep(c.Query("period_start"))
requestKeyword := c.Query("keyword")
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
reportLog.Error("无法解析核算报表查询的开始日期", zap.Error(err))
return result.BadRequest("无法解析核算报表查询的开始日期。")
return result.NotAccept("查询参数[page]格式不正确。")
}
endDate, err := types.ParseDatep(c.Query("period_end"))
requestAllReports, err := strconv.ParseBool(c.Query("all", "false"))
if err != nil {
reportLog.Error("无法解析核算报表查询的结束日期", zap.Error(err))
return result.BadRequest("无法解析核算报表查询的结束日期。")
return result.NotAccept("查询参数[all]格式不正确。")
}
reports, total, err := service.ReportService.QueryReports(requestUser, park, uint(page), keyword, startDate, endDate)
records, totalItems, err := service.ReportService.SearchReport(requestUser, requestPark, requestKeyword, requestPeriod, requestPage, !requestAllReports)
if err != nil {
reportLog.Error("无法查询核算报表", zap.Error(err))
return result.Error(fiber.StatusInternalServerError, "无法查询核算报表。")
return result.NotFound(err.Error())
}
return result.Success(
"已经获取到指定核算报表的分页信息。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"reports": reports},
"已经取得符合条件的公示报表记录。",
response.NewPagedResponse(requestPage, totalItems).ToMap(),
fiber.Map{"reports": records},
)
}
func fetchReportPublicity(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
publicity, err := service.ReportService.AssembleReportPublicity(requestReportId)
if err != nil {
if nfErr, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound(nfErr.Error())
} else {
return result.Error(http.StatusInternalServerError, err.Error())
}
}
return result.Success("已经取得指定公示报表的发布版本。", tools.ConvertStructToMap(publicity))
}
func calculateReport(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
err := service.CalculateService.ComprehensivelyCalculateReport(requestReportId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("指定公示报表中的数据已经计算完毕。")
}

View File

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

View File

@ -1,288 +0,0 @@
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,
},
)
}

View File

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

View File

@ -3,91 +3,131 @@ package controller
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/logger"
"electricity_bill_calc/global"
"electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"electricity_bill_calc/tools"
"electricity_bill_calc/vo"
"fmt"
"net/http"
"strconv"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"github.com/shopspring/decimal"
)
var userLog = logger.Named("Handler", "User")
func InitializeUserHandlers(router *fiber.App) {
router.Delete("/login", security.MustAuthenticated, doLogout)
router.Post("/login", doLogin)
router.Get("/account", security.OPSAuthorize, searchUsers)
router.Post("/account", security.OPSAuthorize, createOPSAccount)
router.Get("/account/:uid", security.MustAuthenticated, fetchUserInformation)
router.Put("/account/:uid", security.OPSAuthorize, modifyUserInformation)
router.Put("/account/enabled/state", security.OPSAuthorize, changeUserState)
router.Get("/expiration", security.EnterpriseAuthorize, getAccountExpiration)
router.Post("/enterprise", security.OPSAuthorize, createEnterpriseAccount)
router.Get("/enterprise/quick/search", security.OPSAuthorize, quickSearchEnterprise)
router.Put("/password", resetUserPassword)
func InitializeUserController(router *fiber.App) {
router.Delete("/password/:uid", security.OPSAuthorize, invalidUserPassword)
router.Delete("/login", security.MustAuthenticated, logout)
router.Put("/password", resetUserPassword)
router.Get("/accounts", security.ManagementAuthorize, listPagedUser)
router.Post("/login", login)
router.Put("/account/enabled/state", security.OPSAuthorize, switchUserEnabling)
router.Post("/account", security.OPSAuthorize, createOPSAndManagementAccount)
router.Get("/account/:uid", security.MustAuthenticated, getUserDetail)
router.Post("/enterprise", security.OPSAuthorize, createEnterpriseAccount)
router.Put("/account/:uid", security.OPSAuthorize, modifyAccountDetail)
router.Get("/enterprise/quick/search", security.OPSAuthorize, quickSearchEnterprise)
router.Get("/expiration", security.EnterpriseAuthorize, fetchExpiration)
}
type _LoginForm struct {
type _LoginFormData struct {
Username string `json:"uname"`
Password string `json:"upass"`
Type int16 `json:"type"`
Type int8 `json:"type"`
}
func doLogin(c *fiber.Ctx) error {
result := response.NewResult(c) //创建一个相应结果对象
loginData := new(_LoginForm) //创建一个解析登录表单数据的实体
if err := c.BodyParser(loginData); err != nil { //解析请求体中的Json数据到loginData里如果解析出错就返回错误
userLog.Error("表单解析失败!", zap.Error(err))
return result.Error(http.StatusInternalServerError, "表单解析失败。") //返回一个内部服务器错误的相应结果
func login(c *fiber.Ctx) error {
result := response.NewResult(c)
loginData := new(_LoginFormData)
if err := c.BodyParser(loginData); err != nil {
return result.Error(http.StatusInternalServerError, "表单解析失败。")
}
var (
session *model.Session
err error
)
userLog.Info("有用户请求登录。", zap.String("username", loginData.Username), zap.Int16("type", loginData.Type)) //记录日志相关信息
if loginData.Type == model.USER_TYPE_ENT { //根据登录类型选择不同的处理方法
session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password) //企业用户
if loginData.Type == model.USER_TYPE_ENT {
session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password)
} else {
userLog.Info("该用户是管理用户")
session, err = service.UserService.ProcessManagementUserLogin(loginData.Username, loginData.Password) //管理用户
session, err = service.UserService.ProcessManagementUserLogin(loginData.Username, loginData.Password)
}
if err != nil {
if authError, ok := err.(*exceptions.AuthenticationError); ok { //检查错误是否为身份验证错误
if authError.NeedReset { //如果需要重置密码则返回对应结果
if authError, ok := err.(*exceptions.AuthenticationError); ok {
if authError.NeedReset {
return result.LoginNeedReset()
}
return result.Error(int(authError.Code), authError.Message) //返回身份验证错误相应
return result.Error(int(authError.Code), authError.Message)
} else {
userLog.Error("用户登录请求处理失败!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error()) //返回内部服务器错误
return result.Error(http.StatusInternalServerError, err.Error())
}
}
return result.LoginSuccess(session) //返回登录成功相应结果,包含会话信息
return result.LoginSuccess(session)
}
func doLogout(c *fiber.Ctx) error {
func logout(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
session := c.Locals("session")
if session == nil {
return result.Success("用户会话已结束。")
}
_, err = cache.ClearSession(session.Token)
_, err := cache.ClearSession(session.(*model.Session).Token)
if err != nil {
userLog.Error("用户登出处理失败!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("用户已成功登出系统。")
}
func searchUsers(c *fiber.Ctx) error {
func invalidUserPassword(c *fiber.Ctx) error {
result := response.NewResult(c)
targetUserId := c.Params("uid")
verifyCode, err := service.UserService.InvalidUserPassword(targetUserId)
if _, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound("未找到指定用户。")
}
if _, ok := err.(exceptions.UnsuccessfulOperationError); ok {
return result.NotAccept("未能成功更新用户的密码。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusAccepted, "用户密码已经失效", fiber.Map{"verify": verifyCode})
}
type _ResetPasswordFormData struct {
VerifyCode string `json:"verifyCode"`
Username string `json:"uname"`
NewPassword string `json:"newPass"`
}
func resetUserPassword(c *fiber.Ctx) error {
result := response.NewResult(c)
resetForm := new(_ResetPasswordFormData)
if err := c.BodyParser(resetForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
verified, err := service.UserService.VerifyUserPassword(resetForm.Username, resetForm.VerifyCode)
if _, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound("指定的用户不存在。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !verified {
return result.Error(http.StatusUnauthorized, "验证码不正确。")
}
completed, err := service.UserService.ResetUserPassword(resetForm.Username, resetForm.NewPassword)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if completed {
return result.Updated("用户凭据已更新。")
}
return result.NotAccept("用户凭据未能成功更新。")
}
func listPagedUser(c *fiber.Ctx) error {
result := response.NewResult(c)
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
@ -105,230 +145,213 @@ func searchUsers(c *fiber.Ctx) error {
} else {
requestUserStat = &state
}
users, total, err := repository.UserRepository.FindUser(
&requestKeyword,
int16(requestUserType),
requestUserStat,
uint(requestPage),
)
users, total, err := service.UserService.ListUserDetail(requestKeyword, requestUserType, requestUserStat, requestPage)
if err != nil {
return result.NotFound(err.Error())
}
return result.Success(
return result.Json(
http.StatusOK,
"已取得符合条件的用户集合。",
response.NewPagedResponse(requestPage, total).ToMap(),
fiber.Map{"accounts": users},
)
}
func getAccountExpiration(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
userLog.Error("未找到有效的用户会话。", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
userDetail, err := repository.UserRepository.FindUserDetailById(session.Uid)
if err != nil {
return result.NotFound("未找到指定的用户档案")
}
return result.Success(
"已经取得用户的服务期限信息",
fiber.Map{"expiration": userDetail.ServiceExpiration.Format("2006-01-02")},
)
type _UserStateChangeFormData struct {
UserID string `json:"uid" form:"uid"`
Enabled bool `json:"enabled" form:"enabled"`
}
func createOPSAccount(c *fiber.Ctx) error {
userLog.Info("请求创建运维或监管账户。")
func switchUserEnabling(c *fiber.Ctx) error {
result := response.NewResult(c)
creationForm := new(vo.MGTAndOPSAccountCreationForm)
if err := c.BodyParser(creationForm); err != nil {
userLog.Error("表单解析失败!", zap.Error(err))
switchForm := new(_UserStateChangeFormData)
if err := c.BodyParser(switchForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
exists, err := repository.UserRepository.IsUsernameExists(creationForm.Username)
err := service.UserService.SwitchUserState(switchForm.UserID, switchForm.Enabled)
if err != nil {
userLog.Error("检查用户名是否已经被使用时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
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("指定的用户名已经被使用了。")
}
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})
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 fetchUserInformation(c *fiber.Ctx) error {
userLog.Info("请求获取用户详细信息。")
func getUserDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
targetUserId := c.Params("uid")
exists, err := repository.UserRepository.IsUserExists(targetUserId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
exists, err := service.UserService.IsUserExists(targetUserId)
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})
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})
}
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("用户的状态已经更新。")
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 {
userLog.Info("请求创建企业账户。")
result := response.NewResult(c)
creationForm := new(vo.EnterpriseAccountCreationForm)
creationForm := new(_EnterpriseCreationFormData)
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())
}
exists, err := service.UserService.IsUsernameExists(creationForm.Username)
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))
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 {
userLog.Error("查询用户时发生错误!", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("已查询到存在符合条件的企业", fiber.Map{"users": users})
cache.AbolishRelation("user")
return result.Json(http.StatusCreated, "用户已经成功创建。", fiber.Map{"verify": verifyCode})
}
func resetUserPassword(c *fiber.Ctx) error {
userLog.Info("请求重置用户密码。")
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)
repasswordForm := new(vo.RepasswordForm)
if err := c.BodyParser(repasswordForm); err != nil {
userLog.Error("表单解析失败!", zap.Error(err))
targetUserId := c.Params("uid")
modForm := new(_AccountModificationFormData)
if err := c.BodyParser(modForm); err != nil {
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())
}
exists, err := service.UserService.IsUserExists(targetUserId)
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("未能更新用户凭据。")
newUserInfo := new(model.UserDetail)
newUserInfo.Id = targetUserId
newUserInfo.Name = &modForm.Name
if len(modForm.Name) > 0 {
abbr := tools.PinyinAbbr(modForm.Name)
newUserInfo.Abbr = &abbr
}
return result.Updated("用户凭据已经更新。", fiber.Map{"verify": verifyCode})
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)
session, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
user, err := service.UserService.FetchUserDetail(session.Uid)
if err != nil {
return result.NotFound(err.Error())
}
return result.Json(
http.StatusOK,
"已经取得用户的服务期限信息",
fiber.Map{"expiration": user.ServiceExpiration.Format("2006-01-02")},
)
}

View File

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

View File

@ -1,8 +1,8 @@
package excel
import (
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"electricity_bill_calc/types"
"encoding/json"
"errors"
"fmt"
@ -19,10 +19,11 @@ import (
type ExcelTemplateGenerator interface {
Close()
WriteTo(w io.Writer) (int64, error)
WriteMeterData(meters []model.EndUserDetail) error
}
type ColumnRecognizer struct {
Pattern [][]string
Pattern []string
Tag string
MatchIndex int
MustFill bool
@ -44,7 +45,7 @@ type ExcelAnalysisError struct {
Err AnalysisError `json:"error"`
}
func NewColumnRecognizer(tag string, patterns ...[]string) ColumnRecognizer {
func NewColumnRecognizer(tag string, patterns ...string) ColumnRecognizer {
return ColumnRecognizer{
Pattern: patterns,
Tag: tag,
@ -66,17 +67,9 @@ func (e ExcelAnalysisError) Error() string {
func (r *ColumnRecognizer) Recognize(cellValue string) bool {
matches := make([]bool, 0)
for _, pG := range r.Pattern {
groupMatch := make([]bool, 0)
for _, p := range pG {
groupMatch = append(groupMatch, strings.Contains(cellValue, p))
}
// 这句表示在每一个匹配组中,只要有一个匹配项,就算匹配成功
matches = append(matches, lo.Reduce(groupMatch, func(acc, elem bool, index int) bool {
return acc || elem
}, false))
for _, p := range r.Pattern {
matches = append(matches, strings.Contains(cellValue, p))
}
// 这句表示在尊有的匹配组中,必须全部的匹配组都完成匹配,才算匹配成功
return lo.Reduce(matches, func(acc, elem bool, index int) bool {
return acc && elem
}, true)
@ -196,54 +189,6 @@ func (a *ExcelAnalyzer[T]) Analysis(bean T) ([]T, []ExcelAnalysisError) {
} else {
actualField.SetBool(false)
}
case "types.Date":
if len(matchValue) == 0 {
actualField.Set(reflect.ValueOf(types.NewEmptyDate()))
} else {
v, err := types.ParseDate(matchValue)
if err != nil {
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期格式。%w", err)}})
actualField.Set(reflect.ValueOf(types.NewEmptyDate()))
} else {
actualField.Set(reflect.ValueOf(v))
}
}
case "*types.Date":
if len(matchValue) == 0 {
actualField.Set(reflect.ValueOf(nil))
} else {
v, err := types.ParseDate(matchValue)
if err != nil {
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期格式。%w", err)}})
actualField.Set(reflect.ValueOf(nil))
} else {
actualField.Set(reflect.ValueOf(&v))
}
}
case "types.DateTime":
if len(matchValue) == 0 {
actualField.Set(reflect.ValueOf(types.NewEmptyDateTime()))
} else {
v, err := types.ParseDateTime(matchValue)
if err != nil {
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期时间格式。%w", err)}})
actualField.Set(reflect.ValueOf(types.NewEmptyDateTime()))
} else {
actualField.Set(reflect.ValueOf(v))
}
}
case "*types.DateTime":
if len(matchValue) == 0 {
actualField.Set(reflect.ValueOf(nil))
} else {
v, err := types.ParseDateTime(matchValue)
if err != nil {
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期时间格式。%w", err)}})
actualField.Set(reflect.ValueOf(nil))
} else {
actualField.Set(reflect.ValueOf(&v))
}
}
}
}
}

35
excel/end_user.go Normal file
View File

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

View File

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

View File

@ -0,0 +1,90 @@
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
}

108
excel/meter_pv_template.go Normal file
View File

@ -0,0 +1,108 @@
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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,91 +1,60 @@
package global
import (
"context"
"database/sql"
"fmt"
"time"
"electricity_bill_calc/config"
"electricity_bill_calc/logger"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/samber/lo"
"go.uber.org/zap"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/driver/pgdriver"
"go.uber.org/zap/zapcore"
)
var (
DB *pgxpool.Pool
DB *bun.DB
)
func SetupDatabaseConnection() error {
connString := fmt.Sprintf(
"postgres://%s:%s@%s:%d/%s?sslmode=disable&connect_timeout=%d&application_name=%s&pool_max_conns=%d&pool_min_conns=%d&pool_max_conn_lifetime=%s&pool_max_conn_idle_time=%s&pool_health_check_period=%s",
config.DatabaseSettings.User,
config.DatabaseSettings.Pass,
config.DatabaseSettings.Host,
config.DatabaseSettings.Port,
config.DatabaseSettings.DB,
0,
"elec_service_go",
config.DatabaseSettings.MaxOpenConns,
config.DatabaseSettings.MaxIdleConns,
"60m",
"10m",
"10s",
// connStr := fmt.Sprintf(
// "host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai connect_timeout=0 tcp_user_timeout=180000",
// config.DatabaseSettings.Host,
// config.DatabaseSettings.User,
// config.DatabaseSettings.Pass,
// config.DatabaseSettings.DB,
// config.DatabaseSettings.Port,
// )
pgconn := pgdriver.NewConnector(
pgdriver.WithNetwork("tcp"),
pgdriver.WithAddr(fmt.Sprintf("%s:%d", config.DatabaseSettings.Host,
config.DatabaseSettings.Port)),
pgdriver.WithUser(config.DatabaseSettings.User),
pgdriver.WithInsecure(true),
pgdriver.WithPassword(config.DatabaseSettings.Pass),
pgdriver.WithDatabase(config.DatabaseSettings.DB),
pgdriver.WithDialTimeout(30*time.Second),
pgdriver.WithReadTimeout(3*time.Minute),
pgdriver.WithWriteTimeout(10*time.Minute),
)
poolConfig, err := pgxpool.ParseConfig(connString)
if err != nil {
logger.Named("DB INIT").Error("数据库连接初始化失败。", zap.Error(err))
return err
}
poolConfig.ConnConfig.Tracer = QueryLogger{logger: logger.Named("PG")}
DB, _ = pgxpool.NewWithConfig(context.Background(), poolConfig)
sqldb := sql.OpenDB(pgconn)
DB = bun.NewDB(sqldb, pgdialect.New())
DB.AddQueryHook(logger.NewQueryHook(logger.QueryHookOptions{
LogSlow: 3 * time.Second,
Logger: logger.Named("PG"),
QueryLevel: zapcore.DebugLevel,
ErrorLevel: zapcore.ErrorLevel,
SlowLevel: zapcore.WarnLevel,
ErrorTemplate: "{{.Operation}}[{{.Duration}}]: {{.Query}}: {{.Error}}",
MessageTemplate: "{{.Operation}}[{{.Duration}}]: {{.Query}}",
}))
DB.SetMaxIdleConns(config.DatabaseSettings.MaxIdleConns)
DB.SetMaxOpenConns(config.DatabaseSettings.MaxOpenConns)
DB.SetConnMaxIdleTime(10 * time.Minute)
DB.SetConnMaxLifetime(60 * time.Minute)
DB.Ping()
return nil
}
type QueryLogger struct {
logger *zap.Logger
}
func (ql QueryLogger) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
ql.logger.Info(fmt.Sprintf("将要执行查询: %s", data.SQL))
ql.logger.Info("查询参数", lo.Map(data.Args, func(elem any, index int) zap.Field {
return zap.Any(fmt.Sprintf("[Arg %d]: ", index), elem)
})...)
// for index, arg := range data.Args {
// ql.logger.Info(fmt.Sprintf("[Arg %d]: %v", index, arg))
// }
return ctx
}
func (ql QueryLogger) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
var logFunc func(string, ...zap.Field)
var templateString string
if data.Err != nil {
logFunc = ql.logger.Error
templateString = "命令 [%s] 执行失败。"
} else {
logFunc = ql.logger.Info
templateString = "命令 [%s] 执行成功。"
}
switch {
case data.CommandTag.Update():
fallthrough
case data.CommandTag.Delete():
fallthrough
case data.CommandTag.Insert():
logFunc(
fmt.Sprintf(templateString, data.CommandTag.String()),
zap.Error(data.Err),
zap.Any("affected", data.CommandTag.RowsAffected()))
case data.CommandTag.Select():
logFunc(
fmt.Sprintf(templateString, data.CommandTag.String()),
zap.Error(data.Err))
default:
logFunc(
fmt.Sprintf(templateString, data.CommandTag.String()),
zap.Error(data.Err))
}
}

View File

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

74
go.mod
View File

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

164
go.sum
View File

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

163
logger/bunhook.go Normal file
View File

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

View File

@ -1,10 +1,8 @@
package logger
import (
"electricity_bill_calc/types"
"os"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
@ -42,7 +40,7 @@ func init() {
logger = zap.New(core).Named("App")
sugaredLogger = logger.Sugar()
logger.Info("日志系统初始化完成。")
logger.Info("Logger initialized.")
}
func GetLogger() *zap.Logger {
@ -132,53 +130,3 @@ func With(fields ...zap.Field) *zap.Logger {
func WithSugar(fields ...zap.Field) *zap.SugaredLogger {
return logger.With(fields...).Sugar()
}
func DecimalField(key string, val decimal.Decimal) zap.Field {
return zap.String(key, val.String())
}
func DecimalFieldp(key string, val *decimal.Decimal) zap.Field {
if val == nil {
return zap.Stringp(key, nil)
}
return DecimalField(key, *val)
}
func NullDecimalField(key string, val decimal.NullDecimal) zap.Field {
if val.Valid {
return DecimalField(key, val.Decimal)
}
return zap.Stringp(key, nil)
}
func NullDecimalFieldp(key string, val *decimal.NullDecimal) zap.Field {
if val == nil {
return zap.Stringp(key, nil)
}
if val.Valid {
return DecimalField(key, val.Decimal)
}
return zap.Stringp(key, nil)
}
func DateField(key string, val types.Date) zap.Field {
return val.Log(key)
}
func DateFieldp(key string, val *types.Date) zap.Field {
if val == nil {
return zap.Stringp(key, nil)
}
return DateField(key, *val)
}
func DateTimeField(key string, val types.DateTime) zap.Field {
return val.Log(key)
}
func DateTimeFieldp(key string, val *types.DateTime) zap.Field {
if val == nil {
return zap.Stringp(key, nil)
}
return DateTimeField(key, *val)
}

View File

@ -62,13 +62,9 @@ func NewLogMiddleware(config LogMiddlewareConfig) fiber.Handler {
fields := []zap.Field{
zap.Namespace("context"),
zap.String("pid", strconv.Itoa(os.Getpid())),
zap.String("method", c.Method()),
zap.String("remote", c.IP()),
zap.Strings("forwarded", c.IPs()),
zap.String("url", c.OriginalURL()),
zap.String("time", stop.Sub(start).String()),
// zap.Object("response", Resp(c.Response())),
// zap.Object("request", Req(c)),
zap.Object("response", Resp(c.Response())),
zap.Object("request", Req(c)),
}
if u := c.Locals("userId"); u != nil {

View File

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

152
main.go
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,37 @@
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);

21
migration/main.go Normal file
View File

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

View File

@ -1,213 +0,0 @@
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,
}
}

View File

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

View File

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

133
model/end_user_detail.go Normal file
View File

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

View File

@ -1,90 +0,0 @@
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
)

View File

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

48
model/maintenance_fee.go Normal file
View File

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

View File

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

39
model/meter_04kv.go Normal file
View File

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

View File

@ -1,45 +1,89 @@
package model
import (
"electricity_bill_calc/types"
"context"
"time"
"github.com/jinzhu/copier"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
const (
CATEGORY_TWO_PART int8 = iota
CATEGORY_SINGLE_PV
CATEGORY_SINGLE_NON_PV
)
const (
CUSTOMER_METER_NON_PV int8 = iota
CUSTOMER_METER_PV
)
type Park struct {
Id string `json:"id"`
UserId string `json:"userId"`
Name string `json:"name"`
Abbr string `json:"-"`
bun.BaseModel `bun:"table:park,alias:p"`
CreatedAndModified `bun:"extend"`
Deleted `bun:"extend"`
Id string `bun:",pk,notnull" json:"id"`
UserId string `bun:",notnull" json:"userId"`
Name string `bun:",notnull" json:"name"`
Abbr *string `json:"abbr"`
Area decimal.NullDecimal `bun:"type:numeric" json:"area"`
TenementQuantity decimal.NullDecimal `bun:"type:numeric" json:"tenement"`
Capacity decimal.NullDecimal `bun:"type:numeric" json:"capacity"`
Category int8 `bun:"type:smallint,notnull" json:"category"`
SubmeterType int8 `bun:"meter_04kv_type,type:smallint,notnull" json:"meter04kvType"`
Region *string `json:"region"`
Address *string `json:"address"`
Contact *string `json:"contact"`
Phone *string `json:"phone"`
Enabled bool `bun:",notnull" json:"enabled"`
EnterpriseIndex *User `bun:"rel:belongs-to,join:user_id=id" json:"-"`
Enterprise *UserDetail `bun:"rel:belongs-to,join:user_id=id" json:"-"`
MaintenanceFees []*MaintenanceFee `bun:"rel:has-many,join:id=park_id" json:"-"`
Meters []*Meter04KV `bun:"rel:has-many,join:id=park_id" json:"-"`
Reports []*Report `bun:"rel:has-many,join:id=park_id" json:"-"`
}
type ParkSimplified struct {
bun.BaseModel `bun:"table:park,alias:p"`
Id string `bun:",pk,notnull" json:"id"`
UserId string `bun:",notnull" json:"userId"`
Name string `bun:",notnull" json:"name"`
Abbr *string `json:"abbr"`
Area decimal.NullDecimal `json:"area"`
TenementQuantity decimal.NullDecimal `json:"tenement"`
Capacity decimal.NullDecimal `json:"capacity"`
Category int16 `json:"category"`
MeterType int16 `json:"meter04kvType" db:"meter_04kv_type"`
PricePolicy int16 `json:"pricePolicy"`
BasicPooled int16 `json:"basicDiluted"`
AdjustPooled int16 `json:"adjustDiluted"`
LossPooled int16 `json:"lossDiluted"`
PublicPooled int16 `json:"publicDiluted"`
TaxRate decimal.NullDecimal `json:"taxRate"`
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 `json:"enabled"`
CreatedAt time.Time `json:"createdAt"`
LastModifiedAt time.Time `json:"lastModifiedAt"`
DeletedAt *time.Time `json:"deletedAt"`
}
type Parks struct {
Park
NormAuthorizedLossRate float64 `json:"norm_authorized_loss_rate"`
}
type ParkPeriodStatistics struct {
Id string `json:"id"`
Name string `json:"name"`
Period *types.DateRange
Id string `bun:"park__id,notnull" json:"id"`
Name string `bun:"park__name,notnull" json:"name"`
Period *Date `bun:"type:date" json:"period"`
}
func FromPark(park Park) ParkSimplified {
dest := ParkSimplified{}
copier.Copy(&dest, park)
return dest
}
var _ bun.BeforeAppendModelHook = (*Park)(nil)
func (p *Park) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
p.CreatedAt = oprTime
p.LastModifiedAt = &oprTime
case *bun.UpdateQuery:
p.LastModifiedAt = &oprTime
}
return nil
}

View File

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

116
model/publicity.go Normal file
View File

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

View File

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

View File

@ -1,8 +1,11 @@
package model
import "github.com/uptrace/bun"
type Region struct {
Code string `json:"code"`
Name string `json:"name"`
Level int32 `json:"level"`
Parent string `json:"parent"`
bun.BaseModel `bun:"table:region,alias:r"`
Code string `bun:",pk,notnull" json:"code"`
Name string `bun:",notnull" json:"name"`
Level int `bun:",notnull" json:"level"`
Parent string `bun:",notnull" json:"parent"`
}

View File

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

119
model/report_summary.go Normal file
View File

@ -0,0 +1,119 @@
package model
import (
"errors"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
type ReportSummary struct {
bun.BaseModel `bun:"table:report_summary,alias:rs"`
ReportId string `bun:",pk,notnull" json:"-"`
Overall decimal.Decimal `bun:"type:numeric,notnull" json:"overall"`
OverallFee decimal.Decimal `bun:"type:numeric,notnull" json:"overallFee"`
ConsumptionFee decimal.NullDecimal `bun:"type:numeric" json:"consumptionFee"`
OverallPrice decimal.NullDecimal `bun:"type:numeric" json:"overallPrice"`
Critical decimal.Decimal `bun:"type:numeric,notnull" json:"critical"`
CriticalFee decimal.Decimal `bun:"type:numeric,notnull" json:"criticalFee"`
CriticalPrice decimal.NullDecimal `bun:"type:numeric" json:"criticalPrice"`
Peak decimal.Decimal `bun:"type:numeric,notnull" json:"peak"`
PeakFee decimal.Decimal `bun:"type:numeric,notnull" json:"peakFee"`
PeakPrice decimal.NullDecimal `bun:"type:numeric" json:"peakPrice"`
Flat decimal.Decimal `bun:"type:numeric,notnull" json:"flat"`
FlatFee decimal.Decimal `bun:"type:numeric,notnull" json:"flatFee"`
FlatPrice decimal.NullDecimal `bun:"type:numeric" json:"flatPrice"`
Valley decimal.Decimal `bun:"type:numeric,notnull" json:"valley"`
ValleyFee decimal.Decimal `bun:"type:numeric,notnull" json:"valleyFee"`
ValleyPrice decimal.NullDecimal `bun:"type:numeric" json:"valleyPrice"`
Loss decimal.NullDecimal `bun:"type:numeric" json:"loss"`
LossFee decimal.NullDecimal `bun:"type:numeric" json:"lossFee"`
LossProportion decimal.NullDecimal `bun:"type:numeric" json:"lossProportion"`
AuthorizeLoss decimal.NullDecimal `bun:"type:numeric" json:"authorizeLoss"`
AuthorizeLossFee decimal.NullDecimal `bun:"type:numeric" json:"authorizeLossFee"`
AuthorizeLossProportion decimal.NullDecimal `bun:"type:numeric" json:"authorizeLossProportion"`
BasicFee decimal.Decimal `bun:"type:numeric,notnull" json:"basicFee"`
BasicDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"basicDilutedPrice"`
AdjustFee decimal.Decimal `bun:"type:numeric,notnull" json:"adjustFee"`
AdjustDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"adjustDilutedPrice"`
MaintenanceDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"maintencanceDilutedPrice"`
LossDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"lossDilutedPrice"`
PublicConsumptionDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"publicConsumptionDilutedPrice"`
MaintenanceOverall decimal.NullDecimal `bun:"type:numeric" json:"maintenanceOverall"`
FinalDilutedOverall decimal.NullDecimal `bun:"type:numeric" json:"finalDilutedOverall"`
Customers Consumptions `bun:"type:jsonb" json:"customers"`
Publics Consumptions `bun:"type:jsonb" json:"publics"`
}
type Consumptions struct {
Consumption decimal.NullDecimal `json:"consumption"`
ConsumptionFee decimal.NullDecimal `json:"fee"`
Critical decimal.NullDecimal `json:"critical"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
Peak decimal.NullDecimal `json:"peak"`
PeakFee decimal.NullDecimal `json:"peakFee"`
Flat decimal.NullDecimal `json:"flat"`
FlatFee decimal.NullDecimal `json:"flatFee"`
Valley decimal.NullDecimal `json:"valley"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
Proportion decimal.NullDecimal `json:"proportion"`
}
func NewConsumptions() Consumptions {
return Consumptions{
Consumption: decimal.NewNullDecimal(decimal.Zero),
ConsumptionFee: decimal.NewNullDecimal(decimal.Zero),
Critical: decimal.NewNullDecimal(decimal.Zero),
Peak: decimal.NewNullDecimal(decimal.Zero),
PeakFee: decimal.NewNullDecimal(decimal.Zero),
Flat: decimal.NewNullDecimal(decimal.Zero),
CriticalFee: decimal.NewNullDecimal(decimal.Zero),
FlatFee: decimal.NewNullDecimal(decimal.Zero),
Valley: decimal.NewNullDecimal(decimal.Zero),
ValleyFee: decimal.NewNullDecimal(decimal.Zero),
Proportion: decimal.NewNullDecimal(decimal.Zero),
}
}
func (s ReportSummary) Validate() (bool, error) {
amountSum := decimal.Sum(s.Critical, s.Peak, s.Valley)
if amountSum.GreaterThan(s.Overall) {
return false, errors.New("峰谷计量总量大于总计电量")
}
feeSum := decimal.Sum(s.CriticalFee, s.PeakFee, s.ValleyFee)
if feeSum.GreaterThan(s.OverallFee) {
return false, errors.New("峰谷计量费用大于总计费用")
}
return true, nil
}
func (s *ReportSummary) CalculatePrices() {
s.ConsumptionFee = decimal.NewNullDecimal(s.OverallFee.Sub(s.BasicFee).Sub(s.AdjustFee))
if s.Overall.GreaterThan(decimal.Zero) {
s.OverallPrice = decimal.NewNullDecimal(s.ConsumptionFee.Decimal.Div(s.Overall).RoundBank(8))
} else {
s.OverallPrice = decimal.NewNullDecimal(decimal.Zero)
}
if s.Critical.GreaterThan(decimal.Zero) {
s.CriticalPrice = decimal.NewNullDecimal(s.CriticalFee.Div(s.Critical).RoundBank(8))
} else {
s.CriticalPrice = decimal.NewNullDecimal(decimal.Zero)
}
if s.Peak.GreaterThan(decimal.Zero) {
s.PeakPrice = decimal.NewNullDecimal(s.PeakFee.Div(s.Peak).RoundBank(8))
} else {
s.PeakPrice = decimal.NewNullDecimal(decimal.Zero)
}
if s.Valley.GreaterThan(decimal.Zero) {
s.ValleyPrice = decimal.NewNullDecimal(s.ValleyFee.Div(s.Valley).RoundBank(8))
} else {
s.ValleyPrice = decimal.NewNullDecimal(decimal.Zero)
}
s.Flat = s.Overall.Sub(s.Critical).Sub(s.Peak).Sub(s.Valley)
s.FlatFee = s.ConsumptionFee.Decimal.Sub(s.CriticalFee).Sub(s.PeakFee).Sub(s.ValleyFee)
if s.Flat.GreaterThan(decimal.Zero) {
s.FlatPrice = decimal.NewNullDecimal(s.FlatFee.Div(s.Flat).RoundBank(8))
} else {
s.FlatPrice = decimal.NewNullDecimal(decimal.Zero)
}
}

View File

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

32
model/shared.go Normal file
View File

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

View File

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

View File

@ -1,33 +0,0 @@
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 Normal file
View File

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

View File

@ -1,114 +1,48 @@
package model
import (
"electricity_bill_calc/types"
"context"
"time"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
const (
USER_TYPE_ENT int16 = iota
USER_TYPE_ENT int8 = iota
USER_TYPE_SUP
USER_TYPE_OPS
)
type ManagementAccountCreationForm struct {
Id *string `json:"id"`
Username string `json:"username"`
Name string `json:"name"`
Contact *string `json:"contact"`
Phone *string `json:"phone"`
Type int16 `json:"type"`
Enabled bool `json:"enabled"`
Expires types.Date `json:"expires"`
}
func (m ManagementAccountCreationForm) IntoUser() *User {
return &User{
Id: *m.Id,
Username: m.Username,
Password: "",
ResetNeeded: false,
UserType: m.Type,
Enabled: m.Enabled,
CreatedAt: nil,
}
}
func (m ManagementAccountCreationForm) IntoUserDetail() *UserDetail {
return &UserDetail{
Id: *m.Id,
Name: &m.Name,
Abbr: nil,
Region: nil,
Address: nil,
Contact: m.Contact,
Phone: m.Phone,
UnitServiceFee: decimal.Zero,
ServiceExpiration: m.Expires,
CreatedAt: types.Now(),
CreatedBy: nil,
LastModifiedAt: types.Now(),
LastModifiedBy: nil,
DeletedAt: nil,
DeletedBy: nil,
}
}
type UserModificationForm struct {
Name string `json:"name"`
Region *string `json:"region"`
Address *string `json:"address"`
Contact *string `json:"contact"`
Phone *string `json:"phone"`
UnitServiceFee *decimal.Decimal `json:"unitServiceFee"`
}
type User struct {
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"`
bun.BaseModel `bun:"table:user,alias:u"`
Created `bun:"extend"`
Id string `bun:",pk,notnull" json:"id"`
Username string `bun:",notnull" json:"username"`
Password string `bun:",notnull" json:"-"`
ResetNeeded bool `bun:",notnull" json:"resetNeeded"`
Type int8 `bun:"type:smallint,notnull" json:"type"`
Enabled bool `bun:",notnull" json:"enabled"`
Detail *UserDetail `bun:"rel:has-one,join:id=id" json:"-"`
Charges []*UserCharge `bun:"rel:has-many,join:id=user_id" json:"-"`
}
type 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 UserWithCredentials struct {
bun.BaseModel `bun:"table:user,alias:u"`
Created `bun:"extend"`
Id string `bun:",pk,notnull" json:"id"`
Username string `bun:",notnull" json:"username"`
Password string `bun:",notnull" json:"credential"`
ResetNeeded bool `bun:",notnull" json:"resetNeeded"`
Type int8 `bun:"type:smallint,notnull" json:"type"`
Enabled bool `bun:",notnull" json:"enabled"`
}
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"`
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
}

43
model/user_charges.go Normal file
View File

@ -0,0 +1,43 @@
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
}

69
model/user_detail.go Normal file
View File

@ -0,0 +1,69 @@
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
}

35
model/will_diluted_fee.go Normal file
View File

@ -0,0 +1,35 @@
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
}

View File

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

View File

@ -1,241 +0,0 @@
package repository
import (
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/types"
"time"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/georgysavva/scany/v2/pgxscan"
"go.uber.org/zap"
)
type _CalculateRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var CalculateRepository = _CalculateRepository{
log: logger.Named("Repository", "Calculate"),
ds: goqu.Dialect("postgres"),
}
// 获取当前正在等待计算的核算任务ID列表
func (cr _CalculateRepository) ListPendingTasks() ([]string, error) {
cr.log.Info("获取当前正在等待计算的核算任务ID列表")
ctx, cancel := global.TimeoutContext()
defer cancel()
var ids []string
querySql, queryArgs, _ := cr.ds.
From("report_task").
Select("id").
Where(goqu.C("status").Eq(model.REPORT_CALCULATE_TASK_STATUS_PENDING)).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &ids, querySql, queryArgs...); err != nil {
cr.log.Error("未能获取到当前正在等待计算的核算任务ID列表", zap.Error(err))
return nil, err
}
return ids, nil
}
// 更新指定报表的核算状态
func (cr _CalculateRepository) UpdateReportTaskStatus(rid string, status int16, message *string) (bool, error) {
cr.log.Info("更新指定报表的核算状态", zap.String("Report", rid), zap.Int16("Status", status))
ctx, cancel := global.TimeoutContext()
defer cancel()
currentTime := types.Now()
updateSql, updateArgs, _ := cr.ds.
Update("report_task").
Set(goqu.Record{
"status": status,
"last_modified_at": currentTime,
"message": message,
}).
Where(goqu.C("id").Eq(rid)).
Prepared(true).ToSQL()
res, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
cr.log.Error("未能更新指定报表的核算状态", zap.Error(err))
return false, err
}
if res.RowsAffected() == 0 {
cr.log.Warn("未能保存指定报表的核算状态", zap.String("Report", rid))
return false, nil
}
return res.RowsAffected() > 0, nil
}
//获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的
func (cr _CalculateRepository) GetAllPoolingMeterRelations(pid string, revokedAfter time.Time) ([]model.MeterRelation, error) {
cr.log.Info("获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的", zap.String("pid", pid), zap.Time("revokedAfter", revokedAfter))
ctx, cancel := global.TimeoutContext()
defer cancel()
relationsSql, relationsArgs, _ := cr.ds.
From(goqu.T("meter_relations")).
Where(goqu.I("park_id").Eq(pid)).
Where(goqu.Or(
goqu.I("revoked_at").IsNull(),
goqu.I("revoked_at").Gte(revokedAfter),
)).ToSQL()
var meterRelation []model.MeterRelation
err := pgxscan.Select(ctx, global.DB, meterRelation, relationsSql, relationsArgs...)
if err != nil {
cr.log.Error("获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的出错", zap.Error(err))
return nil, err
}
return meterRelation, nil
}
//获取当前园区中所有的商户与表计的关联关系,包括已经解除的
func (cr _CalculateRepository) GetAllTenementMeterRelations(pid string, associatedBefore time.Time, disassociatedAfter time.Time) ([]model.TenementMeter, error) {
cr.log.Info("获取当前园区中所有的商户与表计的关联关系,包括已经解除的", zap.String("pid", pid), zap.Time("associatedBefore", associatedBefore), zap.Time("disassociatedAfter", disassociatedAfter))
ctx, cancel := global.TimeoutContext()
defer cancel()
relationsQuerySql, relationsQueryArgs, _ := cr.ds.
From(goqu.T("tenement_meter")).
Where(goqu.I("park_id").Eq(pid)).
Where(goqu.And(
goqu.I("associated_at").IsNull(),
goqu.I("associated_at").Lte(associatedBefore),
)).
Where(goqu.And(
goqu.I("associated_at").IsNull(),
goqu.I("associated_at").Gte(disassociatedAfter),
)).ToSQL()
var tenementMeter []model.TenementMeter
err := pgxscan.Select(ctx, global.DB, tenementMeter, relationsQuerySql, relationsQueryArgs...)
if err != nil {
cr.log.Error("获取当前园区中所有的商户与表计的关联关系,包括已经解除的", zap.Error(err))
return nil, err
}
return tenementMeter, nil
}
//获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据
func (cr _CalculateRepository) GetMeterReadings(rid string, meterType int16) ([]model.MeterReading, error) {
cr.log.Info("获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据", zap.String("rid", rid), zap.Int16("meterType", meterType))
ctx, cancel := global.TimeoutContext()
defer cancel()
readingsQuerySql, readingsQueryArgs, _ := cr.ds.
From(goqu.T("meter_reading").As(goqu.I("mr"))).
Join(
goqu.T("report").As("r"),
goqu.On(goqu.I("r.park_id").Eq(goqu.I("mr.park_id"))),
).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("mr.meter_type").Eq(meterType),
// TODO2023.08.02 此方法出错优先查看是否这里出问题
goqu.I("mr.read_at::date <@ r.period"),
).
Order(goqu.I("mr.read_at").Asc()).Select(goqu.I("mr.*")).ToSQL()
var readings []model.MeterReading
err := pgxscan.Select(ctx, global.DB, readings, readingsQuerySql, readingsQueryArgs...)
if err != nil {
cr.log.Error("获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据出错", zap.Error(err))
return nil, err
}
return readings, nil
}
// 获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数
func (cr _CalculateRepository) GetLastPeriodReadings(rid string, meterType int16) ([]model.MeterReading, error) {
cr.log.Info("获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数", zap.String("rid", rid), zap.Int16("meterType", meterType))
ctx, cancel := global.TimeoutContext()
defer cancel()
readingsSql, readingsArgs, _ := cr.ds.From(goqu.T("meter_reading").As("mr")).
Select(
goqu.MAX("mr.read_at").As("read_at"),
goqu.I("mr.park_id"),
goqu.I("mr.meter_id"),
goqu.I("mr.meter_type"),
goqu.I("mr.ratio"),
goqu.I("mr.overall"),
goqu.I("mr.critical"),
goqu.I("mr.peak"),
goqu.I("mr.flat"),
goqu.I("mr.valley"),
).
Join(
goqu.T("report").As("r"),
goqu.On(goqu.I("r.park_id").Eq(goqu.I("mr.park_id"))),
).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("mr.meter_type").Eq(meterType),
goqu.I(" mr.read_at::date <= lower(r.period)"),
).
GroupBy(
goqu.I("mr.park_id"),
goqu.I("mr.meter_id"),
goqu.I("mr.meter_type"),
goqu.I("mr.ratio"),
goqu.I("mr.overall"),
goqu.I("mr.critical"),
goqu.I("mr.peak"),
goqu.I("mr.flat"),
goqu.I("mr.valley"),
goqu.I("r.period"),
).ToSQL()
var readings []model.MeterReading
err := pgxscan.Select(ctx, global.DB, readings, readingsSql, readingsArgs...)
if err != nil {
cr.log.Error("获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数出错", zap.Error(err))
return nil, err
}
return readings, nil
}
// 取得指定报表所涉及的所有商户信息
func (cr _CalculateRepository) GetAllTenements(rid string) ([]model.Tenement, error) {
cr.log.Info("取得指定报表所涉及的所有商户信息", zap.String("rid", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
tenementQuerySql, tenementQueryArgs, _ := cr.ds.
From(goqu.T("tenement").As("t")).
LeftJoin(
goqu.T("park_building").As("b"),
goqu.On(goqu.I("b.id").Eq(goqu.I("t.building"))),
).
Join(
goqu.T("report").As("r"),
goqu.On(goqu.I("r.park_id").Eq(goqu.I("t.park_id"))),
).
Select(
goqu.I("t.*"),
goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("t.moved_in_at <= upper(r.period)"),
).ToSQL()
var tenements []model.Tenement
err := pgxscan.Select(ctx, global.DB, tenements, tenementQuerySql, tenementQueryArgs...)
if err != nil {
cr.log.Error("取得指定报表所涉及的所有商户信息出错", zap.Error(err))
return nil, err
}
return tenements, nil
}

View File

@ -1,152 +0,0 @@
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
}

View File

@ -1,449 +0,0 @@
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
}

View File

@ -1,348 +0,0 @@
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
}

View File

@ -1,991 +0,0 @@
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
}

View File

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

View File

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

View File

@ -1,846 +0,0 @@
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
}

View File

@ -1,458 +0,0 @@
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
}

View File

@ -1,166 +0,0 @@
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
}

View File

@ -1,419 +0,0 @@
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