refactor(changes):暂时删除全部内容,并完成基本数据库连接的创建。

This commit is contained in:
徐涛 2023-05-30 14:55:39 +08:00
parent 12ec8d26bf
commit ac94c578d6
47 changed files with 127 additions and 7484 deletions

View File

@ -1,93 +0,0 @@
package controller
import (
"electricity_bill_calc/model"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"net/http"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
"github.com/shopspring/decimal"
)
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 listAllCharges(c *fiber.Ctx) error {
result := response.NewResult(c)
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return result.NotAccept("查询参数[page]格式不正确。")
}
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},
)
}
type _NewChargeFormData struct {
UserId string `json:"userId" form:"userId"`
Fee decimal.NullDecimal `json:"fee" form:"fee"`
Discount decimal.NullDecimal `json:"discount" form:"discount"`
Amount decimal.NullDecimal `json:"amount" form:"amount"`
ChargeTo model.Date `json:"chargeTo" form:"chargeTo"`
}
func recordNewCharge(c *fiber.Ctx) error {
result := response.NewResult(c)
formData := new(_NewChargeFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
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 {
return result.Error(http.StatusInternalServerError, err.Error())
}
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("指定用户服务延期记录状态已经更新。")
}

View File

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

View File

@ -1,176 +0,0 @@
package controller
import (
"electricity_bill_calc/exceptions"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"net/http"
"github.com/gofiber/fiber/v2"
)
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)
}
}
func gmResetReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ClearReportSummary(requestReportId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的园区总览部分。")
}
return result.Success("指定报表的园区总览已经重置。")
}
func gmResetReportMaintenance(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ClearReportMaintenances(requestReportId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的配电维护费部分。")
}
return result.Success("指定报表的配电维护费已经重置。")
}
func gmResynchronizeReportEndUserRecord(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
done, err := service.GodModeService.ResynchronizeEndUser(requestReportId)
if err != nil {
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 gmDeleteSpecificMaintenance(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
requestMaintenanceId := c.Params("mid")
done, err := service.GodModeService.RemoveSpecificMaintenance(requestParkId, requestMaintenanceId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除指定的维护费用记录。")
}
return result.Success("指定维护费用记录已经删除。")
}
func gmDeleteAllMaintenance(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
done, err := service.GodModeService.RemoveAllMaintenance(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除全部维护费用记录。")
}
return result.Success("全部维护费用记录已经删除。")
}
func gmDeleteAllMeters(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
done, err := service.GodModeService.RemoveAllMeters(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !done {
return result.Error(http.StatusInternalServerError, "未能成功删除全部终端表计档案记录。")
}
return result.Success("全部终端表计档案记录已经删除。")
}
func gmDeletePark(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
done, err := service.GodModeService.RemovePark(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
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,202 +0,0 @@
package controller
import (
"electricity_bill_calc/model"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"net/http"
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
"github.com/samber/lo"
"github.com/shopspring/decimal"
)
func InitializeMaintenanceFeeController(router *fiber.App) {
router.Get("/maintenance/fee", security.MustAuthenticated, listMaintenanceFees)
router.Post("/maintenance/fee", security.EnterpriseAuthorize, createMaintenanceFeeRecord)
router.Put("/maintenance/fee/:mid", security.EnterpriseAuthorize, modifyMaintenanceFeeRecord)
router.Put("/maintenance/fee/:mid/enabled", security.EnterpriseAuthorize, changeMaintenanceFeeState)
router.Delete("/maintenance/fee/:mid", security.EnterpriseAuthorize, deleteMaintenanceFee)
router.Get("/additional/charges", security.MustAuthenticated, statAdditionalCharges)
}
func ensureMaintenanceFeeBelongs(c *fiber.Ctx, result *response.Result, requestMaintenanceFeeId string) (bool, error) {
userSession, err := _retreiveSession(c)
if err != nil {
return false, result.Unauthorized(err.Error())
}
sure, err := service.MaintenanceFeeService.EnsureFeeBelongs(userSession.Uid, requestMaintenanceFeeId)
if err != nil {
return false, result.Error(http.StatusInternalServerError, err.Error())
}
if !sure {
return false, result.Unauthorized("所操作维护费记录不属于当前用户。")
}
return true, nil
}
func listMaintenanceFees(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
requestPark := c.Query("park")
requestPeriod := c.Query("period")
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return result.Error(http.StatusInternalServerError, "不能解析给定的参数[page]。")
}
if len(requestPark) > 0 {
if userSession.Type == model.USER_TYPE_ENT {
if ensure, err := ensureParkBelongs(c, &result, requestPark); !ensure {
return err
}
}
fees, total, err := service.MaintenanceFeeService.ListMaintenanceFees([]string{requestPark}, requestPeriod, requestPage)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(
http.StatusOK,
"已获取指定园区下的维护费记录",
response.NewPagedResponse(requestPage, total).ToMap(),
fiber.Map{"fees": fees},
)
} else {
parkIds, err := service.ParkService.AllParkIds(userSession.Uid)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
fees, total, err := service.MaintenanceFeeService.ListMaintenanceFees(parkIds, requestPeriod, requestPage)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(
http.StatusOK,
"已获取指定用户下的所有维护费记录。",
response.NewPagedResponse(requestPage, total).ToMap(),
fiber.Map{"fees": fees},
)
}
}
type _FeeCreationFormData struct {
ParkId string `json:"parkId" form:"parkId"`
Name string `json:"name" form:"name"`
Period string `json:"period" form:"period"`
Fee decimal.Decimal `json:"fee" form:"fee"`
Memo *string `json:"memo" form:"memo"`
}
func createMaintenanceFeeRecord(c *fiber.Ctx) error {
result := response.NewResult(c)
formData := new(_FeeCreationFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
if ensure, err := ensureParkBelongs(c, &result, formData.ParkId); !ensure {
return err
}
newMaintenanceFee := &model.MaintenanceFee{}
copier.Copy(newMaintenanceFee, formData)
err := service.MaintenanceFeeService.CreateMaintenanceFeeRecord(*newMaintenanceFee)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("新维护费记录已经创建。")
}
type _FeeModificationFormData struct {
Fee decimal.Decimal `json:"fee" form:"fee"`
Memo *string `json:"memo" form:"memo"`
}
func modifyMaintenanceFeeRecord(c *fiber.Ctx) error {
result := response.NewResult(c)
requestFee := c.Params("mid")
formData := new(_FeeModificationFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
if ensure, err := ensureMaintenanceFeeBelongs(c, &result, requestFee); !ensure {
return err
}
newFeeState := new(model.MaintenanceFee)
copier.Copy(newFeeState, formData)
newFeeState.Id = requestFee
err := service.MaintenanceFeeService.ModifyMaintenanceFee(*newFeeState)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定维护费条目已更新。")
}
type _FeeStateFormData struct {
Enabled bool `json:"enabled" form:"enabled"`
}
func changeMaintenanceFeeState(c *fiber.Ctx) error {
result := response.NewResult(c)
requestFee := c.Params("mid")
formData := new(_FeeStateFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
if ensure, err := ensureMaintenanceFeeBelongs(c, &result, requestFee); !ensure {
return err
}
err := service.MaintenanceFeeService.ChangeMaintenanceFeeState(requestFee, formData.Enabled)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定维护费条目状态已更新。")
}
func deleteMaintenanceFee(c *fiber.Ctx) error {
result := response.NewResult(c)
requestFee := c.Params("mid")
if ensure, err := ensureMaintenanceFeeBelongs(c, &result, requestFee); !ensure {
return err
}
err := service.MaintenanceFeeService.DeleteMaintenanceFee(requestFee)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Deleted("指定维护费条目已删除。")
}
func statAdditionalCharges(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
requestUser := lo.
If(session.Type == model.USER_TYPE_ENT, session.Uid).
Else(c.Query("user"))
requestPark := c.Query("park")
if len(requestPark) > 0 && session.Type == model.USER_TYPE_ENT {
if ensure, err := ensureParkBelongs(c, &result, requestPark); !ensure {
return err
}
}
period := c.Query("period", "")
keyword := c.Query("keyword", "")
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return result.Error(http.StatusInternalServerError, "不能解析给定的参数[page]。")
}
fees, total, err := service.MaintenanceFeeService.QueryAdditionalCharges(requestUser, requestPark, period, keyword, requestPage)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success(
"已经成功获取到物业附加费的统计记录。",
response.NewPagedResponse(requestPage, total).ToMap(),
fiber.Map{"charges": fees},
)
}

View File

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

View File

@ -1,185 +0,0 @@
package controller
import (
"electricity_bill_calc/model"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"electricity_bill_calc/tools"
"net/http"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/jinzhu/copier"
"github.com/shopspring/decimal"
)
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/enabled", security.EnterpriseAuthorize, changeParkEnableState)
router.Delete("/park/:pid", security.EnterpriseAuthorize, deleteSpecificPark)
}
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 {
return result.Unauthorized(err.Error())
}
keyword := c.Query("keyword")
parks, err := service.ParkService.ListAllParkBelongsTo(userSession.Uid, keyword)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "已获取到指定用户下的园区。", fiber.Map{"parks": parks})
}
func listAllParksUnderSpecificUser(c *fiber.Ctx) error {
result := response.NewResult(c)
requestUserId := c.Params("uid")
keyword := c.Query("keyword")
parks, err := service.ParkService.ListAllParkBelongsTo(requestUserId, keyword)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "已获取到指定用户下的园区。", fiber.Map{"parks": parks})
}
type _ParkInfoFormData struct {
Name string `json:"name" form:"name"`
Region *string `json:"region" form:"region"`
Address *string `json:"address" form:"address"`
Contact *string `json:"contact" form:"contact"`
Phone *string `json:"phone" from:"phone"`
Area decimal.NullDecimal `json:"area" from:"area"`
Capacity decimal.NullDecimal `json:"capacity" from:"capacity"`
TenementQuantity decimal.NullDecimal `json:"tenement" from:"tenement"`
Category int8 `json:"category" form:"category"`
SubmeterType int8 `json:"submeter" form:"submeter"`
}
func createNewPark(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
formData := new(_ParkInfoFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
newPark := new(model.Park)
copier.Copy(newPark, formData)
newPark.Id = uuid.New().String()
newPark.UserId = userSession.Uid
nameAbbr := tools.PinyinAbbr(newPark.Name)
newPark.Abbr = &nameAbbr
newPark.Enabled = true
err = service.ParkService.SaveNewPark(*newPark)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Created("新园区完成创建。")
}
func modifyPark(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
requestParkId := c.Params("pid")
formData := new(_ParkInfoFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
park, err := service.ParkService.FetchParkDetail(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if userSession.Uid != park.UserId {
return result.Unauthorized("不能修改不属于自己的园区。")
}
copier.Copy(park, formData)
nameAbbr := tools.PinyinAbbr(formData.Name)
park.Abbr = &nameAbbr
err = service.ParkService.UpdateParkInfo(park)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定园区资料已更新。")
}
func fetchParkDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
}
park, err := service.ParkService.FetchParkDetail(requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "已经获取到指定园区的信息。", fiber.Map{"park": park})
}
type _ParkStateFormData struct {
Enabled bool `json:"enabled" form:"enabled"`
}
func changeParkEnableState(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
requestParkId := c.Params("pid")
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
}
formData := new(_ParkStateFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
err = service.ParkService.ChangeParkState(userSession.Uid, requestParkId, formData.Enabled)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定园区的可用性状态已成功更新。")
}
func deleteSpecificPark(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
requestParkId := c.Params("pid")
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
}
err = service.ParkService.DeletePark(userSession.Uid, requestParkId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Deleted("指定园区已成功删除。")
}

View File

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

View File

@ -1,301 +0,0 @@
package controller
import (
"electricity_bill_calc/exceptions"
"electricity_bill_calc/model"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"electricity_bill_calc/tools"
"net/http"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
"github.com/samber/lo"
"github.com/shopspring/decimal"
)
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.Get("/reports", security.MustAuthenticated, searchReports)
router.Get("/report/:rid", security.MustAuthenticated, fetchReportPublicity)
router.Post("/report/:rid/calculate", security.EnterpriseAuthorize, calculateReport)
}
func ensureReportBelongs(c *fiber.Ctx, result *response.Result, requestReportId string) (bool, error) {
_, err := _retreiveSession(c)
if err != nil {
return false, result.Unauthorized(err.Error())
}
requestReport, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
return false, result.NotFound(err.Error())
}
if requestReport == nil {
return false, result.NotFound("指定报表未能找到。")
}
return ensureParkBelongs(c, result, requestReport.ParkId)
}
func fetchNewestReportOfParkWithDraft(c *fiber.Ctx) error {
result := response.NewResult(c)
userSession, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
parks, err := service.ReportService.FetchParksWithNewestReport(userSession.Uid)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "已获取到指定用户下所有园区的最新报表记录。", fiber.Map{"parks": parks})
}
func initializeNewReport(c *fiber.Ctx) error {
result := response.NewResult(c)
requestParkId := c.Params("pid")
userSession, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure {
return err
}
requestPeriod := c.Query("period")
reportPeriod, err := time.Parse("2006-01", requestPeriod)
if err != nil {
return result.NotAccept("提供的初始化期数格式不正确。")
}
valid, err := service.ReportService.IsNewPeriodValid(userSession.Uid, requestParkId, reportPeriod)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
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 fetchReportStepStates(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
requestReport, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
return result.Json(http.StatusOK, "已经获取到指定报表的填写状态。", fiber.Map{"steps": requestReport.StepState})
}
func fetchReportParkSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
summary, err := service.ReportService.RetreiveReportSummary(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
if summary == nil {
return result.NotFound("指定报表未能找到。")
}
return result.Json(http.StatusOK, "已经获取到指定报表中的园区概况。", fiber.Map{"summary": summary})
}
type ReportSummaryFormData struct {
Overall decimal.Decimal `json:"overall" form:"overall"`
OverallFee decimal.Decimal `json:"overallFee" form:"overallFee"`
Critical decimal.Decimal `json:"critical" form:"critical"`
CriticalFee decimal.Decimal `json:"criticalFee" form:"criticalFee"`
Peak decimal.Decimal `json:"peak" form:"peak"`
PeakFee decimal.Decimal `json:"peakFee" form:"peakFee"`
Valley decimal.Decimal `json:"valley" form:"valley"`
ValleyFee decimal.Decimal `json:"valleyFee" form:"valleyFee"`
BasicFee decimal.Decimal `json:"basicFee" form:"basicFee"`
AdjustFee decimal.Decimal `json:"adjustFee" from:"adjustFee"`
}
func fillReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
formData := new(ReportSummaryFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
originSummary, err := service.ReportService.RetreiveReportSummary(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
copier.Copy(originSummary, formData)
originSummary.ReportId = requestReportId
err = service.ReportService.UpdateReportSummary(originSummary)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Updated("指定电费公示报表中的园区概况基本数据已经完成更新。")
}
func testCalculateReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
summary, err := service.ReportService.RetreiveReportSummary(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
summary.CalculatePrices()
calcResults := tools.ConvertStructToMap(summary)
return result.Json(
http.StatusOK,
"已完成园区概况的试计算。",
fiber.Map{
"result": lo.PickByKeys(
calcResults,
[]string{"overallPrice", "criticalPrice", "peakPrice", "flat", "flatFee", "flatPrice", "valleyPrice", "consumptionFee"},
),
},
)
}
func progressReportSummary(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
err := service.ReportService.CalculateSummaryAndFinishStep(requestReportId)
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("已经完成园区概况的计算,并可以进行到下一步骤。")
}
func progressEndUserRegister(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
report, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
err = service.ReportService.ProgressReportRegisterEndUser(*report)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("终端用户抄表编辑步骤已经完成。")
}
func publishReport(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
report, err := service.ReportService.RetreiveReportIndex(requestReportId)
if err != nil {
return result.NotFound(err.Error())
}
err = service.ReportService.PublishReport(*report)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("指定的公示报表已经发布。")
}
func searchReports(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
requestUser := lo.
If(session.Type == model.USER_TYPE_ENT, session.Uid).
Else(c.Query("user"))
requestPark := c.Query("park")
if len(requestPark) > 0 && session.Type == model.USER_TYPE_ENT {
if ensure, err := ensureParkBelongs(c, &result, requestPark); !ensure {
return err
}
}
requestPeriodString := c.Query("period")
var requestPeriod *time.Time = nil
if len(requestPeriodString) > 0 {
parsedPeriod, err := time.Parse("2006-01", requestPeriodString)
if err != nil {
return result.NotAccept("参数[period]的格式不正确。")
}
requestPeriod = lo.ToPtr(parsedPeriod)
}
requestKeyword := c.Query("keyword")
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return result.NotAccept("查询参数[page]格式不正确。")
}
requestAllReports, err := strconv.ParseBool(c.Query("all", "false"))
if err != nil {
return result.NotAccept("查询参数[all]格式不正确。")
}
records, totalItems, err := service.ReportService.SearchReport(requestUser, requestPark, requestKeyword, requestPeriod, requestPage, !requestAllReports)
if err != nil {
return result.NotFound(err.Error())
}
return result.Success(
"已经取得符合条件的公示报表记录。",
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,73 +0,0 @@
package controller
import (
"electricity_bill_calc/model"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"net/http"
"github.com/gofiber/fiber/v2"
)
func InitializeStatisticsController(router *fiber.App) {
router.Get("/audits", security.OPSAuthorize, currentAuditAmount)
router.Get("/stat/reports", security.MustAuthenticated, statReports)
}
func currentAuditAmount(c *fiber.Ctx) error {
result := response.NewResult(c)
amount, err := service.WithdrawService.AuditWaits()
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "已经获取到指定的统计信息。", fiber.Map{
"amounts": map[string]int64{
"withdraw": amount,
},
})
}
func statReports(c *fiber.Ctx) error {
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
return result.Unauthorized(err.Error())
}
var (
enterprises int64 = 0
parks int64 = 0
reports []model.ParkPeriodStatistics
)
if session.Type != 0 {
enterprises, err = service.StatisticsService.EnabledEnterprises()
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
parks, err = service.StatisticsService.EnabledParks()
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
reports, err = service.StatisticsService.ParksNewestState()
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
} else {
parks, err = service.StatisticsService.EnabledParks(session.Uid)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
reports, err = service.StatisticsService.ParksNewestState(session.Uid)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
}
return result.Json(http.StatusOK, "已经完成园区报告的统计。", fiber.Map{
"statistics": fiber.Map{
"enterprises": enterprises,
"parks": parks,
"reports": reports,
},
})
}

View File

@ -1,357 +0,0 @@
package controller
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/model"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"electricity_bill_calc/tools"
"fmt"
"net/http"
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/shopspring/decimal"
)
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 _LoginFormData struct {
Username string `json:"uname"`
Password string `json:"upass"`
Type int8 `json:"type"`
}
func login(c *fiber.Ctx) error {
result := response.NewResult(c)
loginData := new(_LoginFormData)
if err := c.BodyParser(loginData); err != nil {
return result.Error(http.StatusInternalServerError, "表单解析失败。")
}
var (
session *model.Session
err error
)
if loginData.Type == model.USER_TYPE_ENT {
session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password)
} else {
session, err = service.UserService.ProcessManagementUserLogin(loginData.Username, loginData.Password)
}
if err != nil {
if authError, ok := err.(*exceptions.AuthenticationError); ok {
if authError.NeedReset {
return result.LoginNeedReset()
}
return result.Error(int(authError.Code), authError.Message)
} else {
return result.Error(http.StatusInternalServerError, err.Error())
}
}
return result.LoginSuccess(session)
}
func logout(c *fiber.Ctx) error {
result := response.NewResult(c)
session := c.Locals("session")
if session == nil {
return result.Success("用户会话已结束。")
}
_, err := cache.ClearSession(session.(*model.Session).Token)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success("用户已成功登出系统。")
}
func invalidUserPassword(c *fiber.Ctx) error {
result := response.NewResult(c)
targetUserId := c.Params("uid")
verifyCode, err := service.UserService.InvalidUserPassword(targetUserId)
if _, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound("未找到指定用户。")
}
if _, ok := err.(exceptions.UnsuccessfulOperationError); ok {
return result.NotAccept("未能成功更新用户的密码。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusAccepted, "用户密码已经失效", fiber.Map{"verify": verifyCode})
}
type _ResetPasswordFormData struct {
VerifyCode string `json:"verifyCode"`
Username string `json:"uname"`
NewPassword string `json:"newPass"`
}
func resetUserPassword(c *fiber.Ctx) error {
result := response.NewResult(c)
resetForm := new(_ResetPasswordFormData)
if err := c.BodyParser(resetForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
verified, err := service.UserService.VerifyUserPassword(resetForm.Username, resetForm.VerifyCode)
if _, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound("指定的用户不存在。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if !verified {
return result.Error(http.StatusUnauthorized, "验证码不正确。")
}
completed, err := service.UserService.ResetUserPassword(resetForm.Username, resetForm.NewPassword)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
if completed {
return result.Updated("用户凭据已更新。")
}
return result.NotAccept("用户凭据未能成功更新。")
}
func listPagedUser(c *fiber.Ctx) error {
result := response.NewResult(c)
requestPage, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return result.NotAccept("查询参数[page]格式不正确。")
}
requestKeyword := c.Query("keyword")
requestUserType, err := strconv.Atoi(c.Query("type", "-1"))
if err != nil {
return result.NotAccept("查询参数[type]格式不正确。")
}
var requestUserStat *bool
state, err := strconv.ParseBool(c.Query("state"))
if err != nil {
requestUserStat = nil
} else {
requestUserStat = &state
}
users, total, err := service.UserService.ListUserDetail(requestKeyword, requestUserType, requestUserStat, requestPage)
if err != nil {
return result.NotFound(err.Error())
}
return result.Json(
http.StatusOK,
"已取得符合条件的用户集合。",
response.NewPagedResponse(requestPage, total).ToMap(),
fiber.Map{"accounts": users},
)
}
type _UserStateChangeFormData struct {
UserID string `json:"uid" form:"uid"`
Enabled bool `json:"enabled" form:"enabled"`
}
func switchUserEnabling(c *fiber.Ctx) error {
result := response.NewResult(c)
switchForm := new(_UserStateChangeFormData)
if err := c.BodyParser(switchForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
err := service.UserService.SwitchUserState(switchForm.UserID, switchForm.Enabled)
if err != nil {
if nfErr, ok := err.(*exceptions.NotFoundError); ok {
return result.NotFound(nfErr.Message)
} else {
return result.Error(http.StatusInternalServerError, err.Error())
}
}
return result.Updated("用户状态已经更新。")
}
type _OPSAccountCreationFormData struct {
Username string `json:"username" form:"username"`
Name string `json:"name" form:"name"`
Contact *string `json:"contact" form:"contact"`
Phone *string `json:"phone" form:"phone"`
Type int `json:"type" form:"type"`
}
func createOPSAndManagementAccount(c *fiber.Ctx) error {
result := response.NewResult(c)
creationForm := new(_OPSAccountCreationFormData)
if err := c.BodyParser(creationForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
exists, err := service.UserService.IsUsernameExists(creationForm.Username)
if exists {
return result.Conflict("指定的用户名已经被使用了。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
newUser := new(model.User)
newUser.Username = creationForm.Username
newUser.Type = int8(creationForm.Type)
newUser.Enabled = true
newUserDetail := new(model.UserDetail)
newUserDetail.Name = &creationForm.Name
newUserDetail.Contact = creationForm.Contact
newUserDetail.Phone = creationForm.Phone
newUserDetail.UnitServiceFee = decimal.Zero
newUserDetail.ServiceExpiration, _ = model.ParseDate("2099-12-31")
verifyCode, err := service.UserService.CreateUser(newUser, newUserDetail)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
cache.AbolishRelation("user")
return result.Json(http.StatusCreated, "用户已经成功创建。", fiber.Map{"verify": verifyCode})
}
func getUserDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
targetUserId := c.Params("uid")
exists, err := service.UserService.IsUserExists(targetUserId)
if !exists {
return result.NotFound("指定的用户不存在。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
userDetail, err := service.UserService.FetchUserDetail(targetUserId)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "用户详细信息已获取到。", fiber.Map{"user": userDetail})
}
type _EnterpriseCreationFormData struct {
Username string `json:"username" form:"username"`
Name string `json:"name" form:"name"`
Region *string `json:"region" form:"region"`
Address *string `json:"address" form:"address"`
Contact *string `json:"contact" form:"contact"`
Phone *string `json:"phone" form:"phone"`
UnitServiceFee *string `json:"unitServiceFee" form:"unitServiceFee"`
}
func createEnterpriseAccount(c *fiber.Ctx) error {
result := response.NewResult(c)
creationForm := new(_EnterpriseCreationFormData)
if err := c.BodyParser(creationForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
exists, err := service.UserService.IsUsernameExists(creationForm.Username)
if exists {
return result.Conflict("指定的用户名已经被使用了。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
newUser := new(model.User)
newUser.Username = creationForm.Username
newUser.Type = model.USER_TYPE_ENT
newUser.Enabled = true
newUserDetail := new(model.UserDetail)
newUserDetail.Name = &creationForm.Name
newUserDetail.Contact = creationForm.Contact
newUserDetail.Phone = creationForm.Phone
newUserDetail.UnitServiceFee, err = decimal.NewFromString(*creationForm.UnitServiceFee)
if err != nil {
return result.BadRequest("用户月服务费无法解析。")
}
newUserDetail.ServiceExpiration = model.NewEmptyDate()
verifyCode, err := service.UserService.CreateUser(newUser, newUserDetail)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
cache.AbolishRelation("user")
return result.Json(http.StatusCreated, "用户已经成功创建。", fiber.Map{"verify": verifyCode})
}
type _AccountModificationFormData struct {
Name string `json:"name" form:"name"`
Region *string `json:"region" form:"region"`
Address *string `json:"address" form:"address"`
Contact *string `json:"contact" form:"contact"`
Phone *string `json:"phone" form:"phone"`
UnitServiceFee *string `json:"unitServiceFee" form:"unitServiceFee"`
}
func modifyAccountDetail(c *fiber.Ctx) error {
result := response.NewResult(c)
targetUserId := c.Params("uid")
modForm := new(_AccountModificationFormData)
if err := c.BodyParser(modForm); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
exists, err := service.UserService.IsUserExists(targetUserId)
if !exists {
return result.NotFound("指定的用户不存在。")
}
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
newUserInfo := new(model.UserDetail)
newUserInfo.Id = targetUserId
newUserInfo.Name = &modForm.Name
if len(modForm.Name) > 0 {
abbr := tools.PinyinAbbr(modForm.Name)
newUserInfo.Abbr = &abbr
}
newUserInfo.Region = modForm.Region
newUserInfo.Address = modForm.Address
newUserInfo.Contact = modForm.Contact
newUserInfo.Phone = modForm.Phone
newUserInfo.UnitServiceFee, err = decimal.NewFromString(*modForm.UnitServiceFee)
if err != nil {
return result.BadRequest("用户月服务费无法解析。")
}
_, err = global.DB.NewUpdate().Model(newUserInfo).
WherePK().
Column("name", "abbr", "region", "address", "contact", "phone", "unit_service_fee").
Exec(c.Context())
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
cache.AbolishRelation(fmt.Sprintf("user:%s", targetUserId))
return result.Updated("指定用户的信息已经更新。")
}
func quickSearchEnterprise(c *fiber.Ctx) error {
result := response.NewResult(c)
keyword := c.Query("keyword")
searchResult, err := service.UserService.SearchLimitUsers(keyword, 6)
if err != nil {
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Json(http.StatusOK, "已查询到存在符合条件的企业", fiber.Map{"users": searchResult})
}
func fetchExpiration(c *fiber.Ctx) error {
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,81 +0,0 @@
package controller
import (
"electricity_bill_calc/exceptions"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"net/http"
"strconv"
"github.com/gofiber/fiber/v2"
)
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 applyReportWithdraw(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("pid")
if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure {
return err
}
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},
)
}
type WithdrawAuditFormData struct {
Audit bool `json:"audit" form:"audit"`
}
func auditWithdraw(c *fiber.Ctx) error {
result := response.NewResult(c)
requestReportId := c.Params("rid")
formData := new(WithdrawAuditFormData)
if err := c.BodyParser(formData); err != nil {
return result.UnableToParse("无法解析提交的数据。")
}
err := service.WithdrawService.AuditWithdraw(requestReportId, formData.Audit)
if err != nil {
if nfErr, ok := err.(exceptions.NotFoundError); ok {
return result.NotFound(nfErr.Error())
} else {
return result.NotAccept(err.Error())
}
}
return result.Success("指定公示报表的撤回申请已经完成审核")
}

View File

@ -1,7 +1,6 @@
package excel
import (
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"encoding/json"
"errors"
@ -19,7 +18,6 @@ import (
type ExcelTemplateGenerator interface {
Close()
WriteTo(w io.Writer) (int64, error)
WriteMeterData(meters []model.EndUserDetail) error
}
type ColumnRecognizer struct {

View File

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

View File

@ -1,21 +0,0 @@
package excel
import (
"electricity_bill_calc/model"
"io"
)
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.Meter04KV], error) {
return NewExcelAnalyzer[model.Meter04KV](file, meter04kVExcelRecognizers)
}

View File

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

View File

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

View File

@ -1,60 +1,92 @@
package global
import (
"database/sql"
"context"
"fmt"
"time"
"electricity_bill_calc/config"
"electricity_bill_calc/logger"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/driver/pgdriver"
"go.uber.org/zap/zapcore"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/samber/lo"
"go.uber.org/zap"
)
var (
DB *bun.DB
DB *pgxpool.Pool
)
func SetupDatabaseConnection() error {
// connStr := fmt.Sprintf(
// "host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai connect_timeout=0 tcp_user_timeout=180000",
// config.DatabaseSettings.Host,
// config.DatabaseSettings.User,
// config.DatabaseSettings.Pass,
// config.DatabaseSettings.DB,
// config.DatabaseSettings.Port,
// )
pgconn := pgdriver.NewConnector(
pgdriver.WithNetwork("tcp"),
pgdriver.WithAddr(fmt.Sprintf("%s:%d", config.DatabaseSettings.Host,
config.DatabaseSettings.Port)),
pgdriver.WithUser(config.DatabaseSettings.User),
pgdriver.WithInsecure(true),
pgdriver.WithPassword(config.DatabaseSettings.Pass),
pgdriver.WithDatabase(config.DatabaseSettings.DB),
pgdriver.WithDialTimeout(30*time.Second),
pgdriver.WithReadTimeout(3*time.Minute),
pgdriver.WithWriteTimeout(10*time.Minute),
)
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()
connConfig := &pgx.ConnConfig{
Config: pgconn.Config{
Host: config.DatabaseSettings.Host,
Port: uint16(config.DatabaseSettings.Port),
User: config.DatabaseSettings.User,
Password: config.DatabaseSettings.Pass,
Database: config.DatabaseSettings.DB,
TLSConfig: nil,
ConnectTimeout: 0 * time.Second,
RuntimeParams: map[string]string{"application_name": "elec_service_go"},
},
Tracer: QueryLogger{
logger: logger.Named("PG"),
},
}
poolConfig := &pgxpool.Config{
ConnConfig: connConfig,
MaxConnLifetime: 60 * time.Minute,
MaxConnIdleTime: 10 * time.Minute,
HealthCheckPeriod: 10 * time.Second,
MaxConns: int32(config.DatabaseSettings.MaxOpenConns),
MinConns: int32(config.DatabaseSettings.MaxIdleConns),
}
DB, _ = pgxpool.NewWithConfig(context.Background(), poolConfig)
return nil
}
type QueryLogger struct {
logger *zap.Logger
}
func (ql QueryLogger) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
ql.logger.Info(fmt.Sprintf("将要执行查询: %s", data.SQL))
ql.logger.Info("查询参数", lo.Map(data.Args, func(elem any, index int) zap.Field {
return zap.Any(fmt.Sprintf("[Arg %d]: ", index), elem)
})...)
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))
}
}

17
go.mod
View File

@ -22,14 +22,21 @@ require (
require (
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/georgysavva/scany/v2 v2.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.3.1 // indirect
github.com/jackc/puddle/v2 v2.2.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/klauspost/compress v1.15.0 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/rogpeppe/go-internal v1.9.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.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
mellium.im/sasl v0.3.0 // indirect
)
@ -59,11 +66,11 @@ require (
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/crypto v0.6.0 // 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
golang.org/x/net v0.6.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

30
go.sum
View File

@ -66,9 +66,12 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fufuok/utils v0.7.13 h1:FGx8Mnfg0ZB8HdVz1X60JJ2kFu1rtcsFDYUxUTzNKkU=
github.com/fufuok/utils v0.7.13/go.mod h1:ztIaorPqZGdbvmW3YlwQp80K8rKJmEy6xa1KwpJSsmk=
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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -135,10 +138,20 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk=
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@ -156,8 +169,10 @@ 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/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-sqlite3 v1.14.6/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=
@ -190,6 +205,8 @@ github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTK
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/samber/lo v1.27.0 h1:GOyDWxsblvqYobqsmUuMddPa2/mMzkKyojlXol4+LaQ=
@ -216,6 +233,7 @@ 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.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
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/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
@ -272,6 +290,10 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0
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/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=
@ -344,6 +366,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
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.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -363,6 +387,8 @@ 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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.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=
@ -404,6 +430,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/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.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.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/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -415,6 +443,8 @@ 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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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=

View File

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

99
main.go
View File

@ -1,25 +1,17 @@
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/router"
"electricity_bill_calc/service"
"encoding/csv"
"fmt"
"io"
"os"
"strconv"
"time"
"github.com/samber/lo"
"github.com/shopspring/decimal"
"github.com/uptrace/bun/migrate"
"go.uber.org/zap"
)
@ -27,98 +19,27 @@ func init() {
l := logger.Named("Init")
err := config.SetupSetting()
if err != nil {
l.Fatal("Configuration load failed.", zap.Error(err))
l.Fatal("服务配置文件加载失败!", zap.Error(err))
}
l.Info("Configuration loaded!")
l.Info("服务配置已经完成加载。")
err = global.SetupDatabaseConnection()
if err != nil {
l.Fatal("Main Database connect failed.", zap.Error(err))
}
l.Info("Main Database connected!")
migrator := migrate.NewMigrator(global.DB, migration.Migrations)
ctx, cancel := global.TimeoutContext(12)
defer cancel()
err = migrator.Init(ctx)
if err != nil {
l.Fatal("Database migration unable to initialized.", zap.Error(err))
}
group, err := migrator.Migrate(ctx)
if err != nil {
l.Fatal("Database migrate failed.", zap.Error(err))
}
if group.IsZero() {
l.Info("There are no new migrations to run (database is up to date)")
l.Fatal("主数据库连接失败!", zap.Error(err))
}
l.Info("主数据库已经连接。")
err = global.SetupRedisConnection()
if err != nil {
l.Fatal("Main Cache Database connect failed.", zap.Error(err))
l.Fatal("主缓存数据库连接失败!", zap.Error(err))
}
l.Info("Main Cache Database connected!")
err = initializeRegions()
if err != nil {
l.Fatal("Regions initialize failed.", zap.Error(err))
}
l.Info("Regions synchronized.")
l.Info("主缓存数据库已经连接。")
err = intializeSingularity()
if err != nil {
l.Fatal("Singularity account intialize failed.", zap.Error(err))
l.Fatal("奇点账号初始化失败。", zap.Error(err))
}
l.Info("Singularity account intialized.")
}
func initializeRegions() error {
ctx, cancel := global.TimeoutContext()
defer cancel()
logger.Info("Synchronize regions...")
regionCsvFile, err := os.Open("regions.csv")
if err != nil {
return fmt.Errorf("region initialize file is not found: %w", err)
}
defer regionCsvFile.Close()
var existRegions = make([]string, 0)
err = global.DB.NewSelect().Model((*model.Region)(nil)).
Column("code").
Scan(ctx, &existRegions)
if err != nil {
return fmt.Errorf("unable to retreive regions from database: %w", err)
}
regionCsv := csv.NewReader(regionCsvFile)
transaction, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return fmt.Errorf("unable to intiate database transaction: %w", err)
}
for {
record, err := regionCsv.Read()
if err == io.EOF {
break
}
if lo.Contains(existRegions, record[0]) {
continue
}
level, err := strconv.Atoi(record[2])
if err != nil {
continue
}
if _, err := transaction.NewInsert().Model(&model.Region{
Code: record[0],
Name: record[1],
Level: level,
Parent: record[3],
}).Exec(ctx); err != nil {
return fmt.Errorf("region synchronize in failed: %v, %w", record, err)
}
}
if err = transaction.Commit(); err != nil {
return fmt.Errorf("synchronize regions to database failed: %w", err)
}
return nil
l.Info("奇点账号已经完成初始化。")
}
func intializeSingularity() error {
@ -159,7 +80,9 @@ func intializeSingularity() error {
func DBConnectionKeepLive() {
for range time.Tick(30 * time.Second) {
err := global.DB.Ping()
ctx, cancel := global.TimeoutContext()
defer cancel()
err := global.DB.Ping(ctx)
if err != nil {
continue
}

View File

@ -1,133 +0,0 @@
package model
import (
"context"
"errors"
"time"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
)
type EndUserDetail struct {
bun.BaseModel `bun:"table:end_user_detail,alias:eud"`
CreatedAndModified `bun:"extend"`
ReportId string `bun:",pk,notnull" json:"reportId"`
ParkId string `bun:",pk,notnull" json:"parkId"`
MeterId string `bun:"meter_04kv_id,pk,notnull" json:"meterId"`
Seq int64 `bun:"type:bigint,notnull" json:"seq"`
Ratio decimal.Decimal `bun:"type:numeric,notnull" json:"ratio"`
Address *string `json:"address"`
CustomerName *string `json:"customerName"`
ContactName *string `json:"contactName"`
ContactPhone *string `json:"contactPhone"`
IsPublicMeter bool `bun:"public_meter,notnull" json:"isPublicMeter"`
LastPeriodOverall decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodOverall"`
LastPeriodCritical decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodCritical"`
LastPeriodPeak decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodPeak"`
LastPeriodFlat decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodFlat"`
LastPeriodValley decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodValley"`
CurrentPeriodOverall decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodOverall"`
CurrentPeriodCritical decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodCritical"`
CurrentPeriodPeak decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodPeak"`
CurrentPeriodFlat decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodFlat"`
CurrentPeriodValley decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodValley"`
AdjustOverall decimal.Decimal `bun:"type:numeric,notnull" json:"adjustOverall"`
AdjustCritical decimal.Decimal `bun:"type:numeric,notnull" json:"adjustCritical"`
AdjustPeak decimal.Decimal `bun:"type:numeric,notnull" json:"adjustPeak"`
AdjustFlat decimal.Decimal `bun:"type:numeric,notnull" json:"adjustFlat"`
AdjustValley decimal.Decimal `bun:"type:numeric,notnull" json:"adjustValley"`
Overall decimal.NullDecimal `bun:"type:numeric" json:"overall"`
OverallFee decimal.NullDecimal `bun:"type:numeric" json:"overallFee"`
OverallProportion decimal.Decimal `bun:"type:numeric,notnull" json:"-"`
Critical decimal.NullDecimal `bun:"type:numeric" json:"critical"`
CriticalFee decimal.NullDecimal `bun:"type:numeric" json:"criticalFee"`
Peak decimal.NullDecimal `bun:"type:numeric" json:"peak"`
PeakFee decimal.NullDecimal `bun:"type:numeric" json:"peakFee"`
Flat decimal.NullDecimal `bun:"type:numeric" json:"flat"`
FlatFee decimal.NullDecimal `bun:"type:numeric" json:"flatFee"`
Valley decimal.NullDecimal `bun:"type:numeric" json:"valley"`
ValleyFee decimal.NullDecimal `bun:"type:numeric" json:"valleyFee"`
BasicFeeDiluted decimal.NullDecimal `bun:"type:numeric" json:"basicFeeDiluted"`
AdjustFeeDiluted decimal.NullDecimal `bun:"type:numeric" json:"adjustFeeDiluted"`
LossDiluted decimal.NullDecimal `bun:"type:numeric" json:"lossDiluted"`
LossFeeDiluted decimal.NullDecimal `bun:"type:numeric" json:"lossFeeDiluted"`
FinalDiluted decimal.NullDecimal `bun:"type:numeric" json:"finalDiluted"`
FinalCharge decimal.NullDecimal `bun:"type:numeric" json:"finalCharge"`
Initialize bool `bun:"-" json:"-"`
Origin *Meter04KV `bun:"rel:belongs-to,join:park_id=park_id,join:meter_04kv_id=code" json:"-"`
Report *Report `bun:"rel:belongs-to,join:report_id=id" json:"-"`
Park *Park `bun:"rel:belongs-to,join:park_id=id" json:"-"`
}
var _ bun.BeforeAppendModelHook = (*EndUserDetail)(nil)
func (d *EndUserDetail) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
d.CreatedAt = oprTime
d.LastModifiedAt = &oprTime
case *bun.UpdateQuery:
d.LastModifiedAt = &oprTime
}
return nil
}
func (d EndUserDetail) Validate() (bool, error) {
lastPeriodSum := decimal.Sum(d.LastPeriodCritical, d.LastPeriodPeak, d.LastPeriodValley)
if lastPeriodSum.GreaterThan(d.LastPeriodOverall) {
return false, errors.New("上期峰谷计量总量大于上期总计电量")
}
currentPeriodSum := decimal.Sum(d.CurrentPeriodCritical, d.CurrentPeriodPeak, d.CurrentPeriodValley)
if currentPeriodSum.GreaterThan(d.CurrentPeriodOverall) {
return false, errors.New("本期峰谷计量总量大于本期总计电量")
}
return true, nil
}
func (d *EndUserDetail) CalculatePeriod() {
d.LastPeriodFlat = d.LastPeriodOverall.Sub(d.LastPeriodCritical).Sub(d.LastPeriodPeak).Sub(d.LastPeriodValley)
d.CurrentPeriodFlat = d.CurrentPeriodOverall.Sub(d.CurrentPeriodCritical).Sub(d.CurrentPeriodPeak).Sub(d.CurrentPeriodValley)
d.Overall = decimal.NewNullDecimal(d.CurrentPeriodOverall.Sub(d.LastPeriodOverall).Mul(d.Ratio).Add(d.AdjustOverall).RoundBank(2))
d.Critical = decimal.NewNullDecimal(d.CurrentPeriodCritical.Sub(d.LastPeriodCritical).Mul(d.Ratio).Add(d.AdjustCritical).RoundBank(2))
d.Peak = decimal.NewNullDecimal(d.CurrentPeriodPeak.Sub(d.LastPeriodPeak).Mul(d.Ratio).Add(d.AdjustPeak).RoundBank(2))
d.Flat = decimal.NewNullDecimal(d.CurrentPeriodFlat.Sub(d.LastPeriodFlat).Mul(d.Ratio).Add(d.AdjustFlat).RoundBank(2))
d.Valley = decimal.NewNullDecimal(d.CurrentPeriodValley.Sub(d.LastPeriodValley).Mul(d.Ratio).Add(d.AdjustValley).RoundBank(2))
}
type EndUserImport struct {
MeterId string `excel:"meterId"`
LastPeriodOverall decimal.Decimal `excel:"lastPeriodOverall"`
CurrentPeriodOverall decimal.Decimal `excel:"currentPeriodOverall"`
LastPeriodCritical decimal.NullDecimal `excel:"lastPeriodCritical"`
LastPeriodPeak decimal.NullDecimal `excel:"lastPeriodPeak"`
LastPeriodValley decimal.NullDecimal `excel:"lastPeriodValley"`
CurrentPeriodCritical decimal.NullDecimal `excel:"currentPeriodCritical"`
CurrentPeriodPeak decimal.NullDecimal `excel:"currentPeriodPeak"`
CurrentPeriodValley decimal.NullDecimal `excel:"currentPeriodValley"`
AdjustOverall decimal.Decimal `excel:"adjustOverall"`
AdjustCritical decimal.NullDecimal `excel:"adjustCritical"`
AdjustPeak decimal.NullDecimal `excel:"adjustPeak"`
AdjustFlat decimal.NullDecimal `excel:"adjustFlat"`
AdjustValley decimal.NullDecimal `excel:"adjustValley"`
}
type EndUserPeriodStat struct {
CustomerName string `json:"customerName"`
Address string `json:"address"`
ParkId string `json:"parkId"`
MeterId string `bun:"meter_04kv_id" json:"meterId"`
IsPublicMeter bool `bun:"public_meter" json:"isPublicMeter"`
Kind int8 `bun:"-" json:"pvKind"`
Overall decimal.NullDecimal `json:"overall"`
Critical decimal.NullDecimal `json:"critical"`
Peak decimal.NullDecimal `json:"peak"`
Valley decimal.NullDecimal `json:"valley"`
OverallFee decimal.NullDecimal `json:"overallFee"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
PeakFee decimal.NullDecimal `json:"peakFee"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
AdjustFee decimal.NullDecimal `bun:"final_diluted" json:"adjustFee"`
AdjustProportion decimal.NullDecimal `bun:"-" json:"adjustProportion"`
}

View File

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

View File

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

View File

@ -1,89 +0,0 @@
package model
import (
"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 {
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 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"`
}
type ParkPeriodStatistics struct {
Id string `bun:"park__id,notnull" json:"id"`
Name string `bun:"park__name,notnull" json:"name"`
Period *Date `bun:"type:date" json:"period"`
}
func FromPark(park Park) ParkSimplified {
dest := ParkSimplified{}
copier.Copy(&dest, park)
return dest
}
var _ bun.BeforeAppendModelHook = (*Park)(nil)
func (p *Park) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
p.CreatedAt = oprTime
p.LastModifiedAt = &oprTime
case *bun.UpdateQuery:
p.LastModifiedAt = &oprTime
}
return nil
}

View File

@ -1,116 +0,0 @@
package model
import "github.com/shopspring/decimal"
type PaidPart struct {
Overall decimal.Decimal `json:"overall"`
OverallPrice decimal.Decimal `json:"overallPrice"`
ConsumptionFee decimal.Decimal `json:"consumptionFee"`
OverallFee decimal.Decimal `json:"overallFee"`
Critical decimal.NullDecimal `json:"critical"`
CriticalPrice decimal.NullDecimal `json:"criticalPrice"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
Peak decimal.NullDecimal `json:"peak"`
PeakPrice decimal.NullDecimal `json:"peakPrice"`
PeakFee decimal.NullDecimal `json:"peakFee"`
Flat decimal.NullDecimal `json:"flat"`
FlatPrice decimal.NullDecimal `json:"flatPrice"`
FlatFee decimal.NullDecimal `json:"flatFee"`
Valley decimal.NullDecimal `json:"valley"`
ValleyPrice decimal.NullDecimal `json:"valleyPrice"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
BasicFee decimal.Decimal `json:"basicFee"`
AdjustFee decimal.Decimal `json:"adjustFee"`
}
type EndUserOverallPart struct {
Overall decimal.Decimal `json:"overall"`
OverallPrice decimal.Decimal `json:"overallPrice"`
OverallFee decimal.Decimal `json:"consumptionFee"`
Critical decimal.NullDecimal `json:"critical"`
CriticalPrice decimal.NullDecimal `json:"criticalPrice"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
Peak decimal.NullDecimal `json:"peak"`
PeakPrice decimal.NullDecimal `json:"peakPrice"`
PeakFee decimal.NullDecimal `json:"peakFee"`
Flat decimal.NullDecimal `json:"flat"`
FlatPrice decimal.NullDecimal `json:"flatPrice"`
FlatFee decimal.NullDecimal `json:"flatFee"`
Valley decimal.NullDecimal `json:"valley"`
ValleyPrice decimal.NullDecimal `json:"valleyPrice"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
}
type ConsumptionOverallPart struct {
Overall decimal.Decimal `json:"overall"`
OverallPrice decimal.Decimal `json:"overallPrice"`
ConsumptionFee decimal.Decimal `json:"consumptionFee"`
OverallFee decimal.Decimal `json:"overallFee"`
Critical decimal.NullDecimal `json:"critical"`
CriticalPrice decimal.NullDecimal `json:"criticalPrice"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
Peak decimal.NullDecimal `json:"peak"`
PeakPrice decimal.NullDecimal `json:"peakPrice"`
PeakFee decimal.NullDecimal `json:"peakFee"`
Flat decimal.NullDecimal `json:"flat"`
FlatPrice decimal.NullDecimal `json:"flatPrice"`
FlatFee decimal.NullDecimal `json:"flatFee"`
Valley decimal.NullDecimal `json:"valley"`
ValleyPrice decimal.NullDecimal `json:"valleyPrice"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
Proportion decimal.Decimal `json:"proportion"`
}
type LossPart struct {
Quantity decimal.Decimal `json:"quantity"`
Price decimal.Decimal `json:"price"`
ConsumptionFee decimal.Decimal `json:"consumptionFee"`
Proportion decimal.Decimal `json:"proportion"`
AuthorizeQuantity decimal.Decimal `json:"authorizeQuantity"`
AuthorizeConsumptionFee decimal.Decimal `json:"authorizeConsumptionFee"`
}
type OtherShouldCollectionPart struct {
LossFee decimal.NullDecimal `json:"lossFee"`
BasicFees decimal.Decimal `json:"basicFees"`
}
type MaintenancePart struct {
BasicFees decimal.Decimal `json:"basicFees"`
LossFee decimal.Decimal `json:"lossFee"`
AdjustFee decimal.Decimal `json:"adjustFee"`
LossProportion decimal.Decimal `json:"lossProportion"`
AdjustProportion decimal.Decimal `json:"adjustProportion"`
AdjustPrice decimal.Decimal `json:"adjustPrice"`
}
type EndUserSummary struct {
CustomerName *string `json:"customerName"`
Address *string `json:"address"`
MeterId string `json:"meterId"`
IsPublicMeter bool `json:"isPublicMeter"`
Overall decimal.Decimal `json:"overall"`
OverallPrice decimal.Decimal `json:"overallPrice"`
OverallFee decimal.Decimal `json:"overallFee"`
Critical decimal.NullDecimal `json:"critical"`
CriticalFee decimal.NullDecimal `json:"criticalFee"`
Peak decimal.NullDecimal `json:"peak"`
PeakFee decimal.NullDecimal `json:"peakFee"`
Valley decimal.NullDecimal `json:"valley"`
ValleyFee decimal.NullDecimal `json:"valleyFee"`
Loss decimal.Decimal `json:"loss"`
LossFee decimal.Decimal `json:"lossFee"`
}
type Publicity struct {
Report Report `json:"index"`
User UserDetail `json:"enterprise"`
Park Park `json:"park"`
Paid PaidPart `json:"paid"`
EndUser ConsumptionOverallPart `json:"endUserSum"`
Loss LossPart `json:"loss"`
PublicConsumptionOverall ConsumptionOverallPart `json:"public"`
OtherCollections OtherShouldCollectionPart `json:"others"`
Maintenance MaintenancePart `json:"maintenance"`
EndUserDetails []EndUserSummary `json:"endUser"`
}

View File

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

View File

@ -1,99 +0,0 @@
package model
import (
"context"
"time"
"github.com/uptrace/bun"
)
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 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 NewSteps() Steps {
return Steps{
Summary: false,
WillDiluted: false,
Submeter: false,
Calculate: false,
Preview: false,
Publish: false,
}
}
type ParkNewestReport struct {
Park Park `bun:"extends" json:"park"`
Report *Report `bun:"extends" json:"report"`
}
func (p *ParkNewestReport) AfterLoad() {
if p.Report != nil && len(p.Report.Id) == 0 {
p.Report = nil
}
}
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 JoinedReportForWithdraw struct {
Report Report `bun:"extends" json:"report"`
Park ParkSimplified `bun:"extends" json:"park"`
User UserDetailSimplified `bun:"extends" json:"user"`
}
var _ bun.BeforeAppendModelHook = (*Report)(nil)
func (p *Report) BeforeAppendModel(ctx context.Context, query bun.Query) error {
oprTime := time.Now()
switch query.(type) {
case *bun.InsertQuery:
p.CreatedAt = oprTime
p.LastModifiedAt = &oprTime
case *bun.UpdateQuery:
p.LastModifiedAt = &oprTime
}
return nil
}

View File

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

View File

@ -1,32 +0,0 @@
package model
import "time"
type Created struct {
CreatedAt time.Time `bun:"type:timestamptz,notnull" json:"createdAt" time_format:"simple_datetime" time_location:"shanghai"`
}
type CreatedWithUser struct {
Created `bun:"extend"`
CreatedBy *string `json:"createdBy"`
}
type Deleted struct {
DeletedAt *time.Time `bun:"type:timestamptz,soft_delete,nullzero" json:"deletedAt" time_format:"simple_datetime" time_location:"shanghai"`
}
type DeletedWithUser struct {
Deleted `bun:"extend"`
DeletedBy *string `json:"deletedBy"`
}
type CreatedAndModified struct {
Created `bun:"extend"`
LastModifiedAt *time.Time `bun:"type:timestamptz,nullzero" json:"lastModifiedAt" time_format:"simple_datetime" time_location:"shanghai"`
}
type CreatedAndModifiedWithUser struct {
CreatedAndModified `bun:"extend"`
CreatedBy *string `json:"createdBy"`
LastModifiedBy *string `json:"lastModifiedBy"`
}

View File

@ -1,48 +1,7 @@
package model
import (
"context"
"time"
"github.com/uptrace/bun"
)
const (
USER_TYPE_ENT int8 = iota
USER_TYPE_SUP
USER_TYPE_OPS
)
type User struct {
bun.BaseModel `bun:"table:user,alias:u"`
Created `bun:"extend"`
Id string `bun:",pk,notnull" json:"id"`
Username string `bun:",notnull" json:"username"`
Password string `bun:",notnull" json:"-"`
ResetNeeded bool `bun:",notnull" json:"resetNeeded"`
Type int8 `bun:"type:smallint,notnull" json:"type"`
Enabled bool `bun:",notnull" json:"enabled"`
Detail *UserDetail `bun:"rel:has-one,join:id=id" json:"-"`
Charges []*UserCharge `bun:"rel:has-many,join:id=user_id" json:"-"`
}
type UserWithCredentials struct {
bun.BaseModel `bun:"table:user,alias:u"`
Created `bun:"extend"`
Id string `bun:",pk,notnull" json:"id"`
Username string `bun:",notnull" json:"username"`
Password string `bun:",notnull" json:"credential"`
ResetNeeded bool `bun:",notnull" json:"resetNeeded"`
Type int8 `bun:"type:smallint,notnull" json:"type"`
Enabled bool `bun:",notnull" json:"enabled"`
}
var _ bun.BeforeAppendModelHook = (*User)(nil)
func (u *User) BeforeAppendModel(ctx context.Context, query bun.Query) error {
switch query.(type) {
case *bun.InsertQuery:
u.CreatedAt = time.Now()
}
return nil
}

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package router
import (
"electricity_bill_calc/controller"
"electricity_bill_calc/logger"
"electricity_bill_calc/security"
"fmt"
@ -44,18 +43,6 @@ func App() *fiber.App {
}))
app.Use(security.SessionRecovery)
controller.InitializeUserController(app)
controller.InitializeRegionController(app)
controller.InitializeChargesController(app)
controller.InitializeParkController(app)
controller.InitializeMaintenanceFeeController(app)
controller.InitializeMeter04kVController(app)
controller.InitializeReportController(app)
controller.InitializeEndUserController(app)
controller.InitializeWithdrawController(app)
controller.InitializeStatisticsController(app)
controller.InitializeGodModeController(app)
return app
}

View File

@ -1,262 +0,0 @@
package service
import (
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/model"
"fmt"
"github.com/shopspring/decimal"
)
type _CalculateService struct{}
var CalculateService _CalculateService
func (_CalculateService) ComprehensivelyCalculateReport(reportId string) (err error) {
ctx, cancel := global.TimeoutContext(12)
defer cancel()
// 资料准备
var report = new(model.Report)
err = global.DB.NewSelect().Model(report).
Relation("Summary").
Relation("WillDilutedFees").
Relation("EndUsers").
Where("r.id = ?", reportId).
Scan(ctx)
if err != nil || report == nil {
return exceptions.NewNotFoundErrorFromError("未找到指定的公示报表", err)
}
// 综合计算
report.Summary.CalculatePrices()
// 计算维护费总计
maintenanceFeeTotal := decimal.NewFromInt(0)
for _, m := range report.WillDilutedFees {
maintenanceFeeTotal = maintenanceFeeTotal.Add(m.Fee)
}
// 计算终端用户信息与概览中的合计
report.Summary.Customers = model.NewConsumptions()
report.Summary.Publics = model.NewConsumptions()
for _, eu := range report.EndUsers {
eu.OverallFee = decimal.NewNullDecimal(
eu.Overall.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
eu.CriticalFee = decimal.NewNullDecimal(
eu.Critical.Decimal.Mul(report.Summary.CriticalPrice.Decimal).RoundBank(2),
)
eu.PeakFee = decimal.NewNullDecimal(
eu.Peak.Decimal.Mul(report.Summary.PeakPrice.Decimal).RoundBank(2),
)
eu.FlatFee = decimal.NewNullDecimal(
eu.Flat.Decimal.Mul(report.Summary.FlatPrice.Decimal).RoundBank(2),
)
eu.ValleyFee = decimal.NewNullDecimal(
eu.Valley.Decimal.Mul(report.Summary.ValleyPrice.Decimal).RoundBank(2),
)
if eu.IsPublicMeter {
report.Summary.Publics.Consumption.Decimal = report.Summary.Publics.Consumption.Decimal.Add(eu.Overall.Decimal)
report.Summary.Publics.Critical.Decimal = report.Summary.Publics.Critical.Decimal.Add(eu.Critical.Decimal)
report.Summary.Publics.Peak.Decimal = report.Summary.Publics.Peak.Decimal.Add(eu.Peak.Decimal)
report.Summary.Publics.Flat.Decimal = report.Summary.Publics.Flat.Decimal.Add(eu.Flat.Decimal)
report.Summary.Publics.Valley.Decimal = report.Summary.Publics.Valley.Decimal.Add(eu.Valley.Decimal)
} else {
report.Summary.Customers.Consumption.Decimal = report.Summary.Customers.Consumption.Decimal.Add(eu.Overall.Decimal)
report.Summary.Customers.Critical.Decimal = report.Summary.Customers.Critical.Decimal.Add(eu.Critical.Decimal)
report.Summary.Customers.Peak.Decimal = report.Summary.Customers.Peak.Decimal.Add(eu.Peak.Decimal)
report.Summary.Customers.Flat.Decimal = report.Summary.Customers.Flat.Decimal.Add(eu.Flat.Decimal)
report.Summary.Customers.Valley.Decimal = report.Summary.Customers.Valley.Decimal.Add(eu.Valley.Decimal)
}
}
// 计算户表总电费和公共总电费以及相应的摊薄
if report.SubmeterType == model.CUSTOMER_METER_NON_PV {
// 计算终端用户部分
report.Summary.Customers.ConsumptionFee = decimal.NewNullDecimal(
report.Summary.Customers.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.CriticalFee = decimal.NewNullDecimal(
report.Summary.Customers.Critical.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.PeakFee = decimal.NewNullDecimal(
report.Summary.Customers.Peak.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.FlatFee = decimal.NewNullDecimal(
report.Summary.Customers.Flat.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.ValleyFee = decimal.NewNullDecimal(
report.Summary.Customers.Valley.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
// 计算公共区域部分
report.Summary.Publics.ConsumptionFee = decimal.NewNullDecimal(
report.Summary.Publics.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.CriticalFee = decimal.NewNullDecimal(
report.Summary.Publics.Critical.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.PeakFee = decimal.NewNullDecimal(
report.Summary.Publics.Peak.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.FlatFee = decimal.NewNullDecimal(
report.Summary.Publics.Flat.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.ValleyFee = decimal.NewNullDecimal(
report.Summary.Publics.Valley.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
}
if report.SubmeterType == model.CUSTOMER_METER_PV {
// 计算终端用户部分
report.Summary.Customers.ConsumptionFee = decimal.NewNullDecimal(
report.Summary.Customers.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.CriticalFee = decimal.NewNullDecimal(
report.Summary.Customers.Critical.Decimal.Mul(report.Summary.CriticalPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.PeakFee = decimal.NewNullDecimal(
report.Summary.Customers.Peak.Decimal.Mul(report.Summary.PeakPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.FlatFee = decimal.NewNullDecimal(
report.Summary.Customers.Flat.Decimal.Mul(report.Summary.FlatPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.ValleyFee = decimal.NewNullDecimal(
report.Summary.Customers.Valley.Decimal.Mul(report.Summary.ValleyPrice.Decimal).RoundBank(2),
)
// 计算公共区域部分
report.Summary.Publics.ConsumptionFee = decimal.NewNullDecimal(
report.Summary.Publics.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.CriticalFee = decimal.NewNullDecimal(
report.Summary.Publics.Critical.Decimal.Mul(report.Summary.CriticalPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.PeakFee = decimal.NewNullDecimal(
report.Summary.Publics.Peak.Decimal.Mul(report.Summary.PeakPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.FlatFee = decimal.NewNullDecimal(
report.Summary.Publics.Flat.Decimal.Mul(report.Summary.FlatPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.ValleyFee = decimal.NewNullDecimal(
report.Summary.Publics.Valley.Decimal.Mul(report.Summary.ValleyPrice.Decimal).RoundBank(2),
)
}
if report.Summary.Overall.Abs().GreaterThan(decimal.Zero) {
report.Summary.Customers.Proportion = decimal.NewNullDecimal(
report.Summary.Customers.Consumption.Decimal.Div(report.Summary.Overall).RoundBank(15),
)
report.Summary.Publics.Proportion = decimal.NewNullDecimal(
report.Summary.Publics.Consumption.Decimal.Div(report.Summary.Overall).RoundBank(15),
)
}
// 计算线损
report.Summary.Loss = decimal.NewNullDecimal(
report.Summary.Overall.Sub(report.Summary.Publics.Consumption.Decimal).Sub(report.Summary.Customers.Consumption.Decimal),
)
report.Summary.LossFee = decimal.NewNullDecimal(
report.Summary.Loss.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(8),
)
if report.Summary.Overall.Abs().GreaterThan(decimal.Zero) {
report.Summary.LossProportion = decimal.NewNullDecimal(
report.Summary.Loss.Decimal.Div(report.Summary.Overall).RoundBank(15),
)
if report.Summary.LossProportion.Decimal.GreaterThan(decimal.NewFromFloat(0.1)) {
report.Summary.AuthorizeLoss = decimal.NewNullDecimal(
report.Summary.Overall.Mul(decimal.NewFromFloat(0.1)).RoundBank(8),
)
report.Summary.AuthorizeLossFee = decimal.NewNullDecimal(
report.Summary.AuthorizeLoss.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(8),
)
} else {
report.Summary.AuthorizeLoss = report.Summary.Loss
report.Summary.AuthorizeLossFee = report.Summary.LossFee
}
}
if report.Summary.Customers.Consumption.Decimal.Abs().GreaterThan(decimal.Zero) {
report.Summary.LossDilutedPrice = decimal.NewNullDecimal(
report.Summary.AuthorizeLossFee.Decimal.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8),
)
}
// 计算基本电费和调整电费等的摊薄
if report.Summary.Customers.Consumption.Decimal.Abs().GreaterThan(decimal.Zero) {
report.Summary.BasicDilutedPrice = decimal.NewNullDecimal(
report.Summary.BasicFee.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8),
)
report.Summary.AdjustDilutedPrice = decimal.NewNullDecimal(
report.Summary.AdjustFee.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8),
)
report.Summary.MaintenanceDilutedPrice = decimal.NewNullDecimal(
maintenanceFeeTotal.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8),
)
}
// 计算摊薄总计
report.Summary.MaintenanceOverall = decimal.NewNullDecimal(maintenanceFeeTotal)
report.Summary.FinalDilutedOverall = decimal.NewNullDecimal(
report.Summary.BasicFee.
Add(report.Summary.AdjustFee).
Add(report.Summary.AuthorizeLossFee.Decimal),
)
// 计算终端用户的全部摊薄内容
for _, eu := range report.EndUsers {
// 计算户表表计的摊薄内容
if report.Summary.Customers.Consumption.Decimal.Abs().GreaterThan(decimal.Zero) {
eu.OverallProportion = eu.Overall.Decimal.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(15)
} else {
eu.OverallProportion = decimal.Zero
}
eu.BasicFeeDiluted = decimal.NewNullDecimal(
eu.Overall.Decimal.Mul(report.Summary.BasicDilutedPrice.Decimal).RoundBank(2),
)
eu.AdjustFeeDiluted = decimal.NewNullDecimal(
eu.Overall.Decimal.Mul(report.Summary.AdjustDilutedPrice.Decimal).RoundBank(2),
)
eu.LossDiluted = decimal.NewNullDecimal(
report.Summary.AuthorizeLoss.Decimal.Mul(eu.OverallProportion).RoundBank(8),
)
eu.LossFeeDiluted = decimal.NewNullDecimal(
eu.Overall.Decimal.Mul(report.Summary.LossDilutedPrice.Decimal).RoundBank(8),
)
eu.FinalDiluted = decimal.NewNullDecimal(
eu.BasicFeeDiluted.Decimal.
Add(eu.AdjustFeeDiluted.Decimal).
Add(eu.LossFeeDiluted.Decimal).
RoundBank(2),
)
eu.FinalCharge = decimal.NewNullDecimal(
eu.OverallFee.Decimal.Add(eu.FinalDiluted.Decimal).RoundBank(2),
)
}
// 向数据库保存报表概况以及终端用户摊薄结果
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return
}
_, err = tx.NewUpdate().Model(report.Summary).WherePK().Exec(ctx)
if err != nil {
tx.Rollback()
return
}
for _, eu := range report.EndUsers {
_, err = tx.NewUpdate().Model(eu).WherePK().Exec(ctx)
if err != nil {
tx.Rollback()
return
}
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return
}
cache.AbolishRelation(fmt.Sprintf("publicity:%s", reportId))
return
}

View File

@ -1,284 +0,0 @@
package service
import (
"context"
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"strconv"
"time"
"github.com/fufuok/utils"
"github.com/samber/lo"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _ChargeService struct {
l *zap.Logger
}
var ChargeService = _ChargeService{
l: logger.Named("Service", "Charge"),
}
func (c _ChargeService) CreateChargeRecord(charge *model.UserCharge, extendWithIgnoreSettle bool) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
_, err = tx.NewInsert().Model(charge).Exec(ctx)
if err != nil {
tx.Rollback()
return err
}
if extendWithIgnoreSettle {
err := c.updateUserExpiration(&tx, ctx, charge.UserId, charge.ChargeTo)
if err != nil {
return err
}
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation("charge")
return nil
}
func (c _ChargeService) SettleCharge(seq int64, uid string) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
var record = new(model.UserCharge)
err = tx.NewSelect().Model(&record).
Where("seq = ?", seq).
Where("user_id = ?", uid).
Scan(ctx)
if err != nil {
return nil
}
if record == nil {
return exceptions.NewNotFoundError("未找到匹配指定条件的计费记录。")
}
currentTime := time.Now()
_, err = tx.NewUpdate().Model((*model.UserCharge)(nil)).
Where("seq = ?", seq).
Where("user_id = ?", uid).
Set("settled = ?", true).
Set("settled_at = ?", currentTime).
Exec(ctx)
if err != nil {
tx.Rollback()
return err
}
err = c.updateUserExpiration(&tx, ctx, uid, record.ChargeTo)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation(fmt.Sprintf("charge:%s:%d", uid, seq))
return nil
}
func (c _ChargeService) RefundCharge(seq int64, uid string) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
currentTime := time.Now()
res, err := tx.NewUpdate().Model((*model.UserCharge)(nil)).
Where("seq = ?", seq).
Where("user_id = ?", uid).
Set("refunded = ?", true).
Set("refunded_at = ?", currentTime).
Exec(ctx)
if err != nil {
tx.Rollback()
return err
}
if rows, _ := res.RowsAffected(); rows == 0 {
tx.Rollback()
return exceptions.NewNotFoundError("未找到匹配指定条件的计费记录。")
}
lastValidExpriation, err := c.lastValidChargeTo(&tx, &ctx, uid)
if err != nil {
tx.Rollback()
return exceptions.NewNotFoundError("未找到最后合法的计费时间。")
}
err = c.updateUserExpiration(&tx, ctx, uid, lastValidExpriation)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation(fmt.Sprintf("charge:%s:%d", uid, seq))
return nil
}
func (c _ChargeService) CancelCharge(seq int64, uid string) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
currentTime := time.Now()
res, err := tx.NewUpdate().Model((*model.UserCharge)(nil)).
Where("seq = ?", seq).
Where("user_id = ?", uid).
Set("cancelled = ?", true).
Set("cancelled_at = ?", currentTime).
Exec(ctx)
if err != nil {
tx.Rollback()
return err
}
if rows, _ := res.RowsAffected(); rows == 0 {
tx.Rollback()
return exceptions.NewNotFoundError("未找到匹配指定条件的计费记录。")
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
tx, err = global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
lastValidExpriation, err := c.lastValidChargeTo(&tx, &ctx, uid)
if err != nil {
return exceptions.NewNotFoundError("未找到最后合法的计费时间。")
}
err = c.updateUserExpiration(&tx, ctx, uid, lastValidExpriation)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation("user")
cache.AbolishRelation(fmt.Sprintf("user:%s", uid))
cache.AbolishRelation("charge")
cache.AbolishRelation(fmt.Sprintf("charge:%s:%d", uid, seq))
return nil
}
func (ch _ChargeService) updateUserExpiration(tx *bun.Tx, ctx context.Context, uid string, expiration model.Date) error {
_, err := tx.NewUpdate().Model((*model.UserDetail)(nil)).
Set("service_expiration = ?", expiration).
Where("id = ?", uid).
Exec(ctx)
if err != nil {
tx.Rollback()
}
cache.AbolishRelation(fmt.Sprintf("user:%s", uid))
return err
}
func (_ChargeService) ListPagedChargeRecord(keyword, beginDate, endDate string, page int) ([]model.ChargeWithName, int64, error) {
var (
cond = global.DB.NewSelect()
condition = make([]string, 0)
charges = make([]model.UserCharge, 0)
)
cond = cond.Model(&charges).Relation("Detail")
condition = append(condition, strconv.Itoa(page))
if len(keyword) != 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("detail.name like ?", keywordCond).
WhereOr("detail.abbr like ?", keywordCond)
})
condition = append(condition, keyword)
}
if len(beginDate) != 0 {
beginTime, err := time.ParseInLocation("2006-01-02", beginDate, time.Local)
beginTime = utils.BeginOfDay(beginTime)
if err != nil {
return make([]model.ChargeWithName, 0), 0, err
}
cond = cond.Where("c.created_at >= ?", beginTime)
condition = append(condition, strconv.FormatInt(beginTime.Unix(), 10))
}
if len(endDate) != 0 {
endTime, err := time.ParseInLocation("2006-01-02", endDate, time.Local)
endTime = utils.EndOfDay(endTime)
if err != nil {
return make([]model.ChargeWithName, 0), 0, err
}
cond = cond.Where("c.created_at <= ?", endTime)
condition = append(condition, strconv.FormatInt(endTime.Unix(), 10))
}
if cachedTotal, err := cache.RetreiveCount("charge_with_name", condition...); cachedTotal != -1 && err == nil {
if cachedCharges, _ := cache.RetreiveSearch[[]model.ChargeWithName]("charge_with_name", condition...); cachedCharges != nil {
return *cachedCharges, cachedTotal, nil
}
}
startItem := (page - 1) * config.ServiceSettings.ItemsPageSize
var (
total int
err error
)
ctx, cancel := global.TimeoutContext()
defer cancel()
total, err = cond.Limit(config.ServiceSettings.ItemsPageSize).Offset(startItem).ScanAndCount(ctx)
relations := []string{"charge"}
chargesWithName := make([]model.ChargeWithName, 0)
for _, c := range charges {
chargesWithName = append(chargesWithName, model.ChargeWithName{
UserCharge: c,
UserDetail: *c.Detail,
})
relations = append(relations, fmt.Sprintf("charge:%s:%d", c.UserId, c.Seq))
}
cache.CacheCount(relations, "charge_with_name", int64(total), condition...)
cache.CacheSearch(chargesWithName, relations, "charge_with_name", condition...)
return chargesWithName, int64(total), err
}
func (_ChargeService) lastValidChargeTo(tx *bun.Tx, ctx *context.Context, uid string) (model.Date, error) {
var records []model.Date
err := tx.NewSelect().Table("user_charge").
Where("settled = ? and cancelled = ? and refunded = ? and user_id = ?", true, false, false, uid).
Column("charge_to").
Scan(*ctx, &records)
if err != nil {
return model.NewEmptyDate(), nil
}
lastValid := lo.Reduce(records, func(acc, elem model.Date, index int) model.Date {
if elem.Time.After(acc.Time) {
return elem
} else {
return acc
}
}, model.NewEmptyDate())
return lastValid, nil
}

View File

@ -1,479 +0,0 @@
package service
import (
"context"
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/excel"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"io"
"strconv"
"time"
mapset "github.com/deckarep/golang-set/v2"
"github.com/samber/lo"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _EndUserService struct {
l *zap.Logger
}
type MeterAppears struct {
Meter string
Appears int64
}
var EndUserService = _EndUserService{
l: logger.Named("Service", "EndUser"),
}
func (_EndUserService) SearchEndUserRecord(reportId, keyword string, page int) ([]model.EndUserDetail, int64, error) {
var (
conditions = make([]string, 0)
endUsers = make([]model.EndUserDetail, 0)
cond = global.DB.NewSelect().Model(&endUsers)
)
conditions = append(conditions, reportId, strconv.Itoa(page))
cond = cond.Where("report_id = ?", reportId)
if len(keyword) > 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("customer_name like ?", keywordCond).
WhereOr("contact_name like ?", keywordCond).
WhereOr("contact_phone like ?", keywordCond).
WhereOr("meter_04kv_id like ?", keywordCond)
})
conditions = append(conditions, keyword)
}
if cachedTotal, err := cache.RetreiveCount("end_user_detail", conditions...); cachedTotal != -1 && err == nil {
if cachedEndUsers, _ := cache.RetreiveSearch[[]model.EndUserDetail]("end_user_detail", conditions...); cachedEndUsers != nil {
return *cachedEndUsers, cachedTotal, nil
}
}
ctx, cancel := global.TimeoutContext()
defer cancel()
startItem := (page - 1) * config.ServiceSettings.ItemsPageSize
total, err := cond.Limit(config.ServiceSettings.ItemsPageSize).
Offset(startItem).
Order("seq asc", "meter_04kv_id asc").
ScanAndCount(ctx)
relations := []string{"end_user", "report", "park"}
for _, eu := range endUsers {
relations = append(relations, fmt.Sprintf("end_user:%s:%s", eu.ReportId, eu.MeterId))
}
cache.CacheCount(relations, "end_user_detail", int64(total), conditions...)
cache.CacheSearch(endUsers, relations, "end_user_detail", conditions...)
return endUsers, int64(total), err
}
func (_EndUserService) AllEndUserRecord(reportId string) ([]model.EndUserDetail, error) {
if cachedEndUsers, _ := cache.RetreiveSearch[[]model.EndUserDetail]("end_user_detail", "report", reportId); cachedEndUsers != nil {
return *cachedEndUsers, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
users := make([]model.EndUserDetail, 0)
err := global.DB.NewSelect().Model(&users).
Where("report_id = ?", reportId).
Order("seq asc", "meter_04kv_id asc").
Scan(ctx)
relations := lo.Map(users, func(eu model.EndUserDetail, _ int) string {
return fmt.Sprintf("end_user:%s:%s", eu.ReportId, eu.MeterId)
})
relations = append(relations, "report", "park")
cache.CacheSearch(users, relations, "end_user_detail", "report", reportId)
return users, err
}
func (_EndUserService) FetchSpecificEndUserRecord(reportId, parkId, meterId string) (*model.EndUserDetail, error) {
if cachedEndUser, _ := cache.RetreiveEntity[model.EndUserDetail]("end_user_detail", fmt.Sprintf("%s_%s_%s", reportId, parkId, meterId)); cachedEndUser != nil {
return cachedEndUser, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
record := new(model.EndUserDetail)
err := global.DB.NewSelect().Model(record).
Where("report_id = ?", reportId).
Where("park_id = ?", parkId).
Where("meter_04kv_id = ?", meterId).
Scan(ctx)
cache.CacheEntity(record, []string{fmt.Sprintf("end_user:%s:%s", reportId, meterId), "report", "park"}, "end_user_detail", fmt.Sprintf("%s_%s_%s", reportId, parkId, meterId))
return record, err
}
func (_EndUserService) UpdateEndUserRegisterRecord(tx *bun.Tx, ctx *context.Context, record model.EndUserDetail) (err error) {
record.CalculatePeriod()
updateColumns := []string{
"current_period_overall",
"adjust_overall",
"current_period_critical",
"current_period_peak",
"current_period_flat",
"current_period_valley",
"adjust_critical",
"adjust_peak",
"adjust_flat",
"adjust_valley",
"overall",
"critical",
"peak",
"flat",
"valley",
}
if record.Initialize {
updateColumns = append(updateColumns,
"last_period_overall",
"last_period_critical",
"last_period_peak",
"last_period_flat",
"last_period_valley",
)
}
_, err = tx.NewUpdate().Model(&record).
WherePK().
Column(updateColumns...).
Exec(*ctx)
cache.AbolishRelation(fmt.Sprintf("end_user:%s:%s", record.ReportId, record.MeterId))
return
}
func (_EndUserService) newVirtualExcelAnalysisError(err error) *excel.ExcelAnalysisError {
return &excel.ExcelAnalysisError{Col: -1, Row: -1, Err: excel.AnalysisError{Err: err}}
}
func (es _EndUserService) BatchImportNonPVRegister(reportId string, file io.Reader) *exceptions.BatchError {
ctx, cancel := global.TimeoutContext(120)
defer cancel()
errs := exceptions.NewBatchError()
users, err := es.AllEndUserRecord(reportId)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
var reportDetail = new(model.Report)
err = global.DB.NewSelect().Model(reportDetail).
Where("id = ?", reportId).
Scan(ctx)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(fmt.Errorf("未能找到相应的报表。%w", err)))
return errs
}
meterAppers := make([]MeterAppears, 0)
err = global.DB.NewSelect().Model((*model.EndUserDetail)(nil)).
ColumnExpr("meter_04kv_id as meter").
ColumnExpr("count(*) as appears").
Where("park_id = ?", reportDetail.ParkId).
Group("meter_04kv_id").
Scan(ctx, &meterAppers)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
indexedUsers := lo.Reduce(
users,
func(acc map[string]model.EndUserDetail, elem model.EndUserDetail, index int) map[string]model.EndUserDetail {
acc[elem.MeterId] = elem
return acc
},
make(map[string]model.EndUserDetail, 0),
)
analyzer, err := excel.NewEndUserNonPVExcelAnalyzer(file)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
imports, excelErrs := analyzer.Analysis(*new(model.EndUserImport))
if len(excelErrs) > 0 {
for _, e := range excelErrs {
errs.AddError(e)
}
return errs
}
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
for _, im := range imports {
if elem, ok := indexedUsers[im.MeterId]; ok {
if appears, has := lo.Find(meterAppers, func(m MeterAppears) bool {
return m.Meter == elem.MeterId
}); has {
if appears.Appears <= 1 {
elem.LastPeriodOverall = im.LastPeriodOverall
elem.LastPeriodCritical = decimal.Zero
elem.LastPeriodPeak = decimal.Zero
elem.LastPeriodValley = decimal.Zero
elem.LastPeriodFlat = elem.LastPeriodOverall.Sub(elem.LastPeriodCritical).Sub(elem.LastPeriodPeak).Sub(elem.LastPeriodValley)
elem.Initialize = true
}
}
elem.CurrentPeriodOverall = im.CurrentPeriodOverall
elem.AdjustOverall = im.AdjustOverall
elem.CurrentPeriodCritical = decimal.Zero
elem.CurrentPeriodPeak = decimal.Zero
elem.CurrentPeriodValley = decimal.Zero
elem.CurrentPeriodFlat = elem.CurrentPeriodOverall.Sub(elem.CurrentPeriodCritical).Sub(elem.CurrentPeriodPeak).Sub(elem.CurrentPeriodValley)
elem.AdjustCritical = decimal.Zero
elem.AdjustPeak = decimal.Zero
elem.AdjustValley = decimal.Zero
elem.AdjustFlat = elem.AdjustOverall.Sub(elem.AdjustCritical).Sub(elem.AdjustPeak).Sub(elem.AdjustValley)
err := es.UpdateEndUserRegisterRecord(&tx, &ctx, elem)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
}
} else {
errs.AddError(exceptions.NewNotFoundError(fmt.Sprintf("表计 %s 未找到", im.MeterId)))
}
}
if errs.Len() > 0 {
tx.Rollback()
return errs
}
err = tx.Commit()
if err != nil {
tx.Rollback()
errs.AddError(es.newVirtualExcelAnalysisError(err))
}
cache.AbolishRelation("end_user_detail")
return errs
}
func (es _EndUserService) BatchImportPVRegister(reportId string, file io.Reader) *exceptions.BatchError {
ctx, cancel := global.TimeoutContext(120)
defer cancel()
errs := exceptions.NewBatchError()
users, err := es.AllEndUserRecord(reportId)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
var reportDetail = new(model.Report)
err = global.DB.NewSelect().Model(reportDetail).Where("id = ?", reportId).Scan(ctx)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(fmt.Errorf("未能找到相应的报表。%w", err)))
return errs
}
meterAppers := make([]MeterAppears, 0)
err = global.DB.NewSelect().Model((*model.EndUserDetail)(nil)).
ColumnExpr("meter_04kv_id as meter").
ColumnExpr("count(*) as appears").
Where("park_id = ?", reportDetail.Id).
Group("meter_04kv_id").
Scan(ctx, &meterAppers)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
indexedUsers := lo.Reduce(
users,
func(acc map[string]model.EndUserDetail, elem model.EndUserDetail, index int) map[string]model.EndUserDetail {
acc[elem.MeterId] = elem
return acc
},
make(map[string]model.EndUserDetail, 0),
)
analyzer, err := excel.NewEndUserPVExcelAnalyzer(file)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
imports, excelErrs := analyzer.Analysis(*new(model.EndUserImport))
if len(excelErrs) > 0 {
for _, e := range excelErrs {
errs.AddError(e)
}
return errs
}
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
for _, im := range imports {
if elem, ok := indexedUsers[im.MeterId]; ok {
if appears, has := lo.Find(meterAppers, func(m MeterAppears) bool {
return m.Meter == elem.MeterId
}); has {
if appears.Appears <= 1 {
elem.LastPeriodOverall = im.LastPeriodOverall
elem.LastPeriodCritical = im.LastPeriodCritical.Decimal
elem.LastPeriodPeak = im.LastPeriodPeak.Decimal
elem.LastPeriodValley = im.LastPeriodValley.Decimal
elem.LastPeriodFlat = elem.LastPeriodOverall.Sub(elem.LastPeriodCritical).Sub(elem.LastPeriodPeak).Sub(elem.LastPeriodValley)
elem.Initialize = true
}
}
elem.CurrentPeriodOverall = im.CurrentPeriodOverall
elem.AdjustOverall = im.AdjustOverall
elem.CurrentPeriodCritical = im.CurrentPeriodCritical.Decimal
elem.CurrentPeriodPeak = im.CurrentPeriodPeak.Decimal
elem.CurrentPeriodValley = im.CurrentPeriodValley.Decimal
elem.CurrentPeriodFlat = elem.CurrentPeriodOverall.Sub(elem.CurrentPeriodCritical).Sub(elem.CurrentPeriodPeak).Sub(elem.CurrentPeriodValley)
elem.AdjustCritical = im.AdjustCritical.Decimal
elem.AdjustPeak = im.AdjustPeak.Decimal
elem.AdjustValley = im.AdjustValley.Decimal
elem.AdjustFlat = elem.AdjustOverall.Sub(elem.AdjustCritical).Sub(elem.AdjustPeak).Sub(elem.AdjustValley)
err := es.UpdateEndUserRegisterRecord(&tx, &ctx, elem)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
}
} else {
errs.AddError(es.newVirtualExcelAnalysisError(exceptions.NewNotFoundError(fmt.Sprintf("表计 %s 未找到", im.MeterId))))
}
}
if errs.Len() > 0 {
tx.Rollback()
return errs
}
err = tx.Commit()
if err != nil {
tx.Rollback()
errs.AddError(es.newVirtualExcelAnalysisError(err))
}
cache.AbolishRelation("end_user_detail")
return errs
}
func (es _EndUserService) StatEndUserRecordInPeriod(requestUser, requestPark, startDate, endDate string) ([]model.EndUserPeriodStat, error) {
var (
conditions = make([]string, 0)
relations = []string{
fmt.Sprintf("park:%s", requestPark),
"end_user_detail",
}
cond = global.DB.NewSelect().
Model((*model.EndUserDetail)(nil)).
Relation("Report", func(sq *bun.SelectQuery) *bun.SelectQuery {
return sq.ExcludeColumn("*")
}).
Relation("Park", func(sq *bun.SelectQuery) *bun.SelectQuery {
return sq.ExcludeColumn("*")
})
)
if len(requestUser) > 0 {
cond = cond.Where("park.user_id = ?", requestUser)
conditions = append(conditions, requestUser)
} else {
conditions = append(conditions, "_")
}
if len(requestPark) > 0 {
cond = cond.Where("eud.park_id = ?", requestPark)
conditions = append(conditions, requestPark)
} else {
conditions = append(conditions, "_")
}
if len(startDate) > 0 {
parseTime, err := time.Parse("2006-01", startDate)
if err != nil {
return make([]model.EndUserPeriodStat, 0), fmt.Errorf("不能解析给定的参数[startDate]%w", err)
}
start := model.NewDate(parseTime)
cond = cond.Where("report.period >= ?::date", start.ToString())
conditions = append(conditions, startDate)
} else {
conditions = append(conditions, "_")
}
if len(endDate) > 0 {
parseTime, err := time.Parse("2006-01", endDate)
if err != nil {
return make([]model.EndUserPeriodStat, 0), fmt.Errorf("不能解析给定的参数[endDate]%w", err)
}
end := model.NewDate(parseTime)
cond = cond.Where("report.period <= ?::date", end.ToString())
conditions = append(conditions, endDate)
}
if cached, err := cache.RetreiveSearch[[]model.EndUserPeriodStat]("end_user_stat", conditions...); cached != nil && err == nil {
return *cached, nil
}
ctx, cancel := global.TimeoutContext(120)
defer cancel()
var endUserSums []model.EndUserPeriodStat
err := cond.Column("eud.meter_04kv_id", "eud.park_id").
ColumnExpr("sum(?) as overall", bun.Ident("eud.overall")).
ColumnExpr("sum(?) as overall_fee", bun.Ident("eud.overall_fee")).
ColumnExpr("sum(?) as critical", bun.Ident("eud.critical")).
ColumnExpr("sum(?) as critical_fee", bun.Ident("eud.critical_fee")).
ColumnExpr("sum(?) as peak", bun.Ident("eud.peak")).
ColumnExpr("sum(?) as peak_fee", bun.Ident("eud.peak_fee")).
ColumnExpr("sum(?) as valley", bun.Ident("eud.valley")).
ColumnExpr("sum(?) as valley_fee", bun.Ident("eud.valley_fee")).
ColumnExpr("sum(?) as final_diluted", bun.Ident("eud.final_diluted")).
Where("report.published = ?", true).
Group("eud.meter_04kv_id", "eud.park_id").
Scan(ctx, &endUserSums)
if err != nil {
return make([]model.EndUserPeriodStat, 0), fmt.Errorf("未能完成终端用户在指定期限内的统计,%w", err)
}
parkIds := lo.Reduce(
endUserSums,
func(acc mapset.Set[string], elem model.EndUserPeriodStat, _ int) mapset.Set[string] {
acc.Add(elem.ParkId)
return acc
},
mapset.NewSet[string](),
)
meterArchives := make([]model.Meter04KV, 0)
if len(parkIds.ToSlice()) > 0 {
err = global.DB.NewSelect().
Model(&meterArchives).Relation("ParkDetail").
Where("park_id in (?)", bun.In(parkIds.ToSlice())).
Scan(ctx)
if err != nil {
return make([]model.EndUserPeriodStat, 0), fmt.Errorf("未能获取到终端表计的最新基础档案,%w", err)
}
}
filledStats := lo.Map(
endUserSums,
func(elem model.EndUserPeriodStat, _ int) model.EndUserPeriodStat {
archive, has := lo.Find(meterArchives, func(meter model.Meter04KV) bool {
return meter.Code == elem.MeterId
})
if has {
if archive.Address != nil {
elem.Address = *archive.Address
} else {
elem.Address = ""
}
if archive.CustomerName != nil {
elem.CustomerName = *archive.CustomerName
} else {
elem.CustomerName = ""
}
elem.IsPublicMeter = archive.IsPublicMeter
elem.Kind = archive.ParkDetail.SubmeterType
}
if elem.OverallFee.Valid && elem.AdjustFee.Valid && !elem.OverallFee.Decimal.IsZero() {
elem.AdjustProportion = decimal.NewNullDecimal(
elem.AdjustFee.Decimal.Div(elem.OverallFee.Decimal).RoundBank(8),
)
} else {
elem.AdjustProportion = decimal.NullDecimal{}
}
return elem
},
)
cache.CacheSearch(filledStats, relations, "end_user_stat", conditions...)
return filledStats, nil
}

View File

@ -1,875 +0,0 @@
package service
import (
"context"
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"time"
"github.com/samber/lo"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _GodModeService struct {
l *zap.Logger
}
var GodModeService = _GodModeService{
l: logger.Named("Service", "GodMode"),
}
// 从此处开始为删除报表相关的部分
func (_GodModeService) resetReportIndex(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) {
var report = new(model.Report)
err := tx.NewSelect().Model(report).Where("id = ?", reportId).Scan(*ctx)
if err != nil {
tx.Rollback()
return false, exceptions.NewNotFoundError("指定报表索引未找到。")
}
report.StepState.Summary = false
report.StepState.WillDiluted = false
report.StepState.Submeter = false
report.StepState.Calculate = false
report.StepState.Preview = false
report.StepState.Publish = false
report.Published = false
report.PublishedAt = nil
report.Withdraw = model.REPORT_NOT_WITHDRAW
report.LastWithdrawAppliedAt = nil
report.LastWithdrawAuditAt = nil
res, err := tx.NewUpdate().Model(report).
WherePK().
Column(
"step_state",
"published",
"published_at",
"withdraw",
"last_withdraw_applied_at",
"last_withdraw_audit_at",
).
Exec(*ctx)
if affected, _ := res.RowsAffected(); err != nil || affected == 0 {
tx.Rollback()
return false, err
}
return true, err
}
func (_GodModeService) resetReportSummary(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) {
var summary = &model.ReportSummary{
ReportId: reportId,
}
_, err := tx.NewUpdate().Model(summary).WherePK().Exec(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
var report = new(model.Report)
err = tx.NewSelect().Model(report).Where("id = ?", reportId).Scan(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
report.StepState.Summary = false
res, err := tx.NewUpdate().Model(report).
Column("step_state").
WherePK().
Exec(*ctx)
rows, _ := res.RowsAffected()
if err != nil {
tx.Rollback()
}
return rows >= 0, err
}
func (_GodModeService) flushReportMaintenances(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) {
_, err := tx.NewDelete().Model((*model.WillDilutedFee)(nil)).
Where("report_id = ?", reportId).
Exec(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
var report = new(model.Report)
err = tx.NewSelect().Model(report).Where("id = ?", reportId).Scan(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
report.StepState.WillDiluted = false
res, err := tx.NewUpdate().Model(report).
WherePK().
Column("step_state").
Exec(*ctx)
rows, _ := res.RowsAffected()
if err != nil {
tx.Rollback()
}
return rows >= 0, err
}
func (g _GodModeService) resetSingleEndUserRecord(tx *bun.Tx, ctx *context.Context, record model.EndUserDetail, additionalColumns ...string) (bool, error) {
record.CurrentPeriodOverall = decimal.Zero
record.CurrentPeriodCritical = decimal.Zero
record.CurrentPeriodPeak = decimal.Zero
record.CurrentPeriodFlat = decimal.Zero
record.CurrentPeriodValley = decimal.Zero
record.AdjustOverall = decimal.Zero
record.AdjustCritical = decimal.Zero
record.AdjustPeak = decimal.Zero
record.AdjustFlat = decimal.Zero
record.AdjustValley = decimal.Zero
record.Overall = decimal.NewNullDecimal(decimal.Zero)
record.Overall.Valid = false
record.OverallFee = decimal.NewNullDecimal(decimal.Zero)
record.OverallFee.Valid = false
record.OverallProportion = decimal.Zero
record.Critical = decimal.NewNullDecimal(decimal.Zero)
record.Critical.Valid = false
record.CriticalFee = decimal.NewNullDecimal(decimal.Zero)
record.CriticalFee.Valid = false
record.Peak = decimal.NewNullDecimal(decimal.Zero)
record.Peak.Valid = false
record.PeakFee = decimal.NewNullDecimal(decimal.Zero)
record.PeakFee.Valid = false
record.Flat = decimal.NewNullDecimal(decimal.Zero)
record.Flat.Valid = false
record.FlatFee = decimal.NewNullDecimal(decimal.Zero)
record.FlatFee.Valid = false
record.Valley = decimal.NewNullDecimal(decimal.Zero)
record.Valley.Valid = false
record.ValleyFee = decimal.NewNullDecimal(decimal.Zero)
record.ValleyFee.Valid = false
record.BasicFeeDiluted = decimal.NewNullDecimal(decimal.Zero)
record.BasicFeeDiluted.Valid = false
record.AdjustFeeDiluted = decimal.NewNullDecimal(decimal.Zero)
record.AdjustFeeDiluted.Valid = false
record.LossDiluted = decimal.NewNullDecimal(decimal.Zero)
record.LossDiluted.Valid = false
record.LossFeeDiluted = decimal.NewNullDecimal(decimal.Zero)
record.LossFeeDiluted.Valid = false
record.FinalDiluted = decimal.NewNullDecimal(decimal.Zero)
record.FinalDiluted.Valid = false
record.FinalCharge = decimal.NewNullDecimal(decimal.Zero)
record.FinalCharge.Valid = false
columns := []string{
"current_period_overall",
"current_period_critical",
"current_period_peak",
"current_period_flat",
"current_period_valley",
"adjust_overall",
"adjust_critical",
"adjust_peak",
"adjust_flat",
"adjust_valley",
"overall",
"overall_fee",
"overall_proportion",
"critical",
"critical_fee",
"peak",
"peak_fee",
"flat",
"flat_fee",
"valley",
"valley_fee",
"basic_fee_diluted",
"adjust_fee_diluted",
"loss_diluted",
"loss_fee_diluted",
"maintenance_fee_diluted",
"public_consumption_diluted",
"final_diluted",
"final_charge",
}
columns = append(columns, additionalColumns...)
_, err := tx.NewUpdate().Model(&record).
WherePK().
Column(columns...).
Exec(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
return true, nil
}
func (g _GodModeService) resynchronizeEndUserArchives(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) {
var currentRecords = make([]*model.EndUserDetail, 0)
err := tx.NewSelect().Model(&currentRecords).
Where("report_id = ?", reportId).
Scan(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
var report = new(model.Report)
err = tx.NewSelect().Model(report).
Where("id = ?", reportId).
Scan(*ctx)
if err != nil || report == nil {
tx.Rollback()
return false, err
}
var latestArchives = make([]model.Meter04KV, 0)
err = tx.NewSelect().Model(&latestArchives).
Where("park_id = ?", report.ParkId).
Where("enabled = ?", true).
Scan(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
for _, meter := range latestArchives {
record, has := lo.Find(currentRecords, func(rec *model.EndUserDetail) bool {
return rec.ParkId == meter.ParkId && rec.MeterId == meter.Code
})
if has {
record.CustomerName = meter.CustomerName
record.Address = meter.Address
record.Ratio = meter.Ratio
record.ContactName = meter.ContactName
record.ContactPhone = meter.ContactPhone
record.Seq = meter.Seq
record.IsPublicMeter = meter.IsPublicMeter
success, err := g.resetSingleEndUserRecord(
tx, ctx, *record,
"customer_name",
"address",
"ratio",
"contact_name",
"contact_phone",
"seq",
"public_meter",
)
if err != nil {
return success, err
}
} else {
newEndUser := model.EndUserDetail{
ReportId: report.Id,
ParkId: report.ParkId,
MeterId: meter.Code,
Seq: meter.Seq,
Ratio: meter.Ratio,
Address: meter.Address,
CustomerName: meter.CustomerName,
ContactName: meter.ContactName,
ContactPhone: meter.ContactPhone,
IsPublicMeter: meter.IsPublicMeter,
LastPeriodOverall: decimal.Zero,
LastPeriodCritical: decimal.Zero,
LastPeriodPeak: decimal.Zero,
LastPeriodFlat: decimal.Zero,
LastPeriodValley: decimal.Zero,
}
_, err = tx.NewInsert().Model(&newEndUser).Exec(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
}
}
report.StepState.Submeter = false
res, err := tx.NewUpdate().Model(report).
WherePK().
Column("step_state").
Exec(*ctx)
rows, _ := res.RowsAffected()
if err != nil {
tx.Rollback()
}
return rows >= 0, nil
}
func (g _GodModeService) resetEndUserRecords(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) {
var records = make([]model.EndUserDetail, 0)
err := tx.NewSelect().Model(&records).
Where("report_id = ?", reportId).
Scan(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
for _, u := range records {
success, err := g.resetSingleEndUserRecord(tx, ctx, u)
if err != nil {
return success, err
}
}
var report = new(model.Report)
err = tx.NewSelect().Model(report).
Where("id = ?", reportId).
Scan(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
report.StepState.Submeter = false
res, err := tx.NewUpdate().Model(report).
WherePK().
Column("step_state").
Exec(*ctx)
rows, _ := res.RowsAffected()
if err != nil {
tx.Rollback()
}
return rows >= 0, nil
}
type ReportPeriod struct {
Id string
Period time.Time
}
func (_GodModeService) isTheLatestReport(ctx *context.Context, reportId string) (bool, error) {
var report = new(model.Report)
err := global.DB.NewSelect().Model(report).
Where("id = ?", reportId).
Scan(*ctx)
if err != nil || report == nil {
return false, exceptions.NewNotFoundErrorFromError("指定报表索引未找到,", err)
}
var maxPeriod time.Time
err = global.DB.NewSelect().Model((*model.Report)(nil)).
ColumnExpr("max(?)", bun.Ident("period")).
Where("park_id = ?", report.ParkId).
Scan(*ctx, &maxPeriod)
if err != nil {
return false, err
}
return maxPeriod.Equal(report.Period), nil
}
func (_GodModeService) forceDeleteReport(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) {
_, err := tx.NewDelete().Model((*model.EndUserDetail)(nil)).
Where("report_id = ?", reportId).
Exec(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
_, err = tx.NewDelete().Model((*model.WillDilutedFee)(nil)).
Where("report_id = ?", reportId).
Exec(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
_, err = tx.NewDelete().Model((*model.ReportSummary)(nil)).
Where("report_id = ?", reportId).
Exec(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
_, err = tx.NewDelete().Model((*model.Report)(nil)).
Where("id = ?", reportId).
Exec(*ctx)
if err != nil {
tx.Rollback()
return false, err
}
return true, nil
}
func (g _GodModeService) ClearReportSummary(reportId string) (bool, error) {
ctx, cancel := global.TimeoutContext(12)
defer cancel()
isLatest, err := g.isTheLatestReport(&ctx, reportId)
if err != nil {
return false, err
}
if !isLatest {
return false, exceptions.NewImproperOperateError("不能操作非最新期数的报表。")
}
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return false, err
}
result, err := g.resetReportSummary(&tx, &ctx, reportId)
if err != nil {
return false, err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return false, err
}
cache.AbolishRelation(fmt.Sprintf("report:%s", reportId))
return result, nil
}
func (g _GodModeService) ClearReportMaintenances(reportId string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
isLatest, err := g.isTheLatestReport(&ctx, reportId)
if err != nil {
return false, err
}
if !isLatest {
return false, exceptions.NewImproperOperateError("不能操作非最新期数的报表。")
}
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return false, err
}
result, err := g.flushReportMaintenances(&tx, &ctx, reportId)
if err != nil {
return false, err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return false, err
}
cache.AbolishRelation(fmt.Sprintf("report:%s", reportId))
return result, nil
}
func (g _GodModeService) ResynchronizeEndUser(reportId string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
isLatest, err := g.isTheLatestReport(&ctx, reportId)
if err != nil {
return false, err
}
if !isLatest {
return false, exceptions.NewImproperOperateError("不能操作非最新期数的报表。")
}
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return false, err
}
result, err := g.resynchronizeEndUserArchives(&tx, &ctx, reportId)
if err != nil {
return false, err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return false, err
}
cache.AbolishRelation("end_user_detail")
cache.AbolishRelation(fmt.Sprintf("report:%s", reportId))
return result, nil
}
func (g _GodModeService) ResetEndUserRegisterRecords(reportId string) (bool, error) {
ctx, cancel := global.TimeoutContext(48)
defer cancel()
isLatest, err := g.isTheLatestReport(&ctx, reportId)
if err != nil {
return false, err
}
if !isLatest {
return false, exceptions.NewImproperOperateError("不能操作非最新期数的报表。")
}
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return false, err
}
result, err := g.resetEndUserRecords(&tx, &ctx, reportId)
if err != nil {
return false, err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return false, err
}
cache.AbolishRelation("end_user_detail")
cache.AbolishRelation(fmt.Sprintf("report:%s", reportId))
return result, nil
}
func (g _GodModeService) ResetReport(reportId string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
isLatest, err := g.isTheLatestReport(&ctx, reportId)
if err != nil {
return false, err
}
if !isLatest {
return false, exceptions.NewImproperOperateError("不能操作非最新期数的报表。")
}
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return false, err
}
var result = true
r, err := g.resetEndUserRecords(&tx, &ctx, reportId)
if err != nil {
return false, err
}
result = result && r
r, err = g.flushReportMaintenances(&tx, &ctx, reportId)
if err != nil {
return false, err
}
result = result && r
r, err = g.resetReportSummary(&tx, &ctx, reportId)
if err != nil {
return false, err
}
result = result && r
r, err = g.resetReportIndex(&tx, &ctx, reportId)
if err != nil {
return false, err
}
result = result && r
err = tx.Commit()
if err != nil {
tx.Rollback()
return false, err
}
cache.AbolishRelation("end_user_detail")
cache.AbolishRelation(fmt.Sprintf("report:%s", reportId))
return result, nil
}
func (g _GodModeService) DeleteReport(reportId string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
isLatest, err := g.isTheLatestReport(&ctx, reportId)
if err != nil {
return false, err
}
if !isLatest {
return false, exceptions.NewImproperOperateError("不能删除非最新期数的报表。")
}
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return false, err
}
result, err := g.forceDeleteReport(&tx, &ctx, reportId)
if err != nil {
return false, err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return false, err
}
cache.AbolishRelation("end_user_detail")
cache.AbolishRelation(fmt.Sprintf("report:%s", reportId))
return result, nil
}
// 从此处开始为删除园区相关的内容部分
func (_GodModeService) deleteSpecificMaintenance(tx *bun.Tx, ctx *context.Context, parkId, maintenanceId string) (bool, error) {
res, err := tx.NewDelete().Model((*model.MaintenanceFee)(nil)).
Where("park_id = ?", parkId).
Where("id = ?", maintenanceId).
Exec(*ctx)
if err != nil {
tx.Rollback()
return false, nil
}
if rows, err := res.RowsAffected(); err != nil {
tx.Rollback()
return false, err
} else {
return rows >= 0, err
}
}
func (_GodModeService) deleteAllMaintenance(tx *bun.Tx, ctx *context.Context, parkId string) (bool, error) {
res, err := tx.NewDelete().Model((*model.MaintenanceFee)(nil)).
Where("park_id = ?", parkId).
Exec(*ctx)
if err != nil {
tx.Rollback()
return false, nil
}
if rows, err := res.RowsAffected(); err != nil {
tx.Rollback()
return false, err
} else {
return rows >= 0, err
}
}
func (_GodModeService) deleteAllMeters(tx *bun.Tx, ctx *context.Context, parkId string) (bool, error) {
res, err := tx.NewDelete().Model((*model.Meter04KV)(nil)).
Where("park_id = ?", parkId).
Exec(*ctx)
if err != nil {
tx.Rollback()
return false, nil
}
if rows, err := res.RowsAffected(); err != nil {
tx.Rollback()
return false, err
} else {
return rows >= 0, err
}
}
func (_GodModeService) deletePark(tx *bun.Tx, ctx *context.Context, parkId string) (bool, error) {
res, err := tx.NewDelete().Model((*model.Park)(nil)).
Where("id = ?", parkId).
Exec(*ctx)
if err != nil {
tx.Rollback()
return false, nil
}
if rows, err := res.RowsAffected(); err != nil {
tx.Rollback()
return false, err
} else {
return rows >= 0, err
}
}
func (g _GodModeService) RemoveSpecificMaintenance(parkId, maintenanceId string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return false, err
}
result, err := g.deleteSpecificMaintenance(&tx, &ctx, parkId, maintenanceId)
if err != nil {
return false, err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return false, err
}
cache.AbolishRelation(fmt.Sprintf("maintenance_fee:%s", maintenanceId))
return result, nil
}
func (g _GodModeService) RemoveAllMaintenance(parkId string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return false, err
}
result, err := g.deleteAllMaintenance(&tx, &ctx, parkId)
if err != nil {
return false, err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return false, err
}
cache.AbolishRelation("maintenance_fee")
return result, nil
}
func (g _GodModeService) RemoveAllMeters(parkId string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return false, err
}
result, err := g.deleteAllMeters(&tx, &ctx, parkId)
if err != nil {
return false, err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return false, err
}
cache.AbolishRelation("meter_04kv")
return result, nil
}
func (g _GodModeService) erasePark(tx *bun.Tx, ctx *context.Context, parkId string) (bool, error) {
var reportIds = make([]string, 0)
err := tx.NewSelect().Model((*model.Report)(nil)).
Where("park_id = ?", parkId).
Column("id").
Scan(*ctx, &reportIds)
if err != nil {
tx.Rollback()
return false, err
}
var result = true
for _, id := range reportIds {
r, err := g.forceDeleteReport(tx, ctx, id)
if err != nil {
return false, err
}
result = result && r
}
r, err := g.deleteAllMaintenance(tx, ctx, parkId)
if err != nil {
return false, err
}
result = result && r
r, err = g.deleteAllMeters(tx, ctx, parkId)
if err != nil {
return false, err
}
result = result && r
r, err = g.deletePark(tx, ctx, parkId)
if err != nil {
return false, err
}
result = result && r
return result, err
}
func (g _GodModeService) RemovePark(parkId string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return false, err
}
result, err := g.erasePark(&tx, &ctx, parkId)
if err != nil {
return false, err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return false, err
}
cache.AbolishRelation(fmt.Sprintf("park:%s", parkId))
return result, nil
}
// 从此处开始为删除用户相关的部分
func (g _GodModeService) DeleteUser(userId string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return false, err
}
var parkIds = make([]string, 0)
err = tx.NewSelect().Model((*model.Park)(nil)).
Where("user_id = ?", userId).
WhereAllWithDeleted().
Column("id").
Scan(ctx, &parkIds)
if err != nil {
tx.Rollback()
return false, err
}
var result = true
for _, p := range parkIds {
r, err := g.erasePark(&tx, &ctx, p)
if err != nil {
return false, err
}
result = result && r
}
// 删除用户服务计费数据。
res, err := tx.NewDelete().Model((*model.UserCharge)(nil)).
Where("user_id = ?", userId).
Exec(ctx)
if err != nil {
tx.Rollback()
return false, err
}
if rows, err := res.RowsAffected(); err != nil {
tx.Rollback()
return false, err
} else {
result = result && (rows >= 0)
}
// 删除用户详细信息数据
res, err = tx.NewDelete().Model((*model.UserDetail)(nil)).
Where("id = ?", userId).
ForceDelete().
Exec(ctx)
if err != nil {
tx.Rollback()
return false, err
}
if rows, err := res.RowsAffected(); err != nil {
tx.Rollback()
return false, err
} else {
result = result && (rows >= 0)
}
// 删除用户基本索引数据
res, err = tx.NewDelete().Model((*model.User)(nil)).
Where("id = ?", userId).
Exec(ctx)
if err != nil {
tx.Rollback()
return false, err
}
if rows, err := res.RowsAffected(); err != nil {
tx.Rollback()
return false, err
} else {
result = result && (rows >= 0)
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return false, err
}
cache.AbolishRelation(fmt.Sprintf("user:%s", userId))
cache.AbolishRelation("user")
cache.AbolishRelation("park")
cache.AbolishRelation("report")
cache.AbolishRelation("charge")
return result, nil
}

View File

@ -1,295 +0,0 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
mapset "github.com/deckarep/golang-set/v2"
"github.com/google/uuid"
"github.com/samber/lo"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _MaintenanceFeeService struct {
l *zap.Logger
}
var MaintenanceFeeService = _MaintenanceFeeService{
l: logger.Named("Service", "maintenance"),
}
func (_MaintenanceFeeService) ListMaintenanceFees(pid []string, period string, requestPage int) ([]model.MaintenanceFee, int64, error) {
conditions := []string{fmt.Sprintf("%d", requestPage)}
conditions = append(conditions, pid...)
conditions = append(conditions, period)
if cachedTotal, err := cache.RetreiveCount("maintenance_fee", conditions...); cachedTotal != -1 && err == nil {
if fees, _ := cache.RetreiveSearch[[]model.MaintenanceFee]("maintenance_fee", conditions...); fees != nil {
return *fees, cachedTotal, nil
}
}
var (
fees = make([]model.MaintenanceFee, 0)
cond = global.DB.NewSelect().Model(&fees)
)
if len(pid) > 0 {
cond = cond.Where("park_id in (?)", bun.In(pid))
} else {
return make([]model.MaintenanceFee, 0), 0, exceptions.NewIllegalArgumentsError("必须给定所要请求的至少一个园区", "park_id")
}
if len(period) > 0 {
cond = cond.Where("period = ?", period)
}
ctx, cancel := global.TimeoutContext()
defer cancel()
startItem := (requestPage - 1) * config.ServiceSettings.ItemsPageSize
total, err := cond.Order("period desc", "created_at desc").
Limit(config.ServiceSettings.ItemsPageSize).
Offset(startItem).
ScanAndCount(ctx)
if err != nil {
return make([]model.MaintenanceFee, 0), 0, fmt.Errorf("附加费查询出现错误,%w", err)
}
relations := lo.Map(fees, func(f model.MaintenanceFee, _ int) string {
return fmt.Sprintf("maintenance_fee:%s", f.Id)
})
relations = append(relations, "maintenance_fee", "park")
cache.CacheCount(relations, "maintenance_fee", int64(total), conditions...)
cache.CacheSearch(fees, relations, "maintenance_fee", conditions...)
return fees, int64(total), nil
}
func (_MaintenanceFeeService) CreateMaintenanceFeeRecord(fee model.MaintenanceFee) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
fee.Id = uuid.New().String()
fee.Enabled = true
_, err := global.DB.NewInsert().Model(&fee).Exec(ctx)
if err != nil {
return err
}
cache.AbolishRelation("maintenance_fee")
return nil
}
func (_MaintenanceFeeService) ModifyMaintenanceFee(fee model.MaintenanceFee) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewUpdate().Model(&fee).
WherePK().
Column("fee", "memo").
Exec(ctx)
if err != nil {
if rows, _ := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到匹配的附加费记录。")
} else {
return err
}
}
cache.AbolishRelation("maintenance_fee")
cache.AbolishRelation(fmt.Sprintf("maintenance_fee:%s", fee.Id))
return nil
}
func (_MaintenanceFeeService) ChangeMaintenanceFeeState(fid string, state bool) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewUpdate().Model((*model.MaintenanceFee)(nil)).
Where("id = ?", fid).
Set("enabled = ?", state).
Exec(ctx)
if err != nil {
if rows, err := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到匹配的附加费记录。")
} else {
return err
}
}
cache.AbolishRelation("maintenance_fee")
cache.AbolishRelation(fmt.Sprintf("maintenance_fee:%s", fid))
return nil
}
func (_MaintenanceFeeService) DeleteMaintenanceFee(fid string) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewDelete().Model((*model.MaintenanceFee)(nil)).
Where("id = ?", fid).
Exec(ctx)
if err != nil {
if rows, err := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到匹配的附加费记录。")
} else {
return err
}
}
cache.AbolishRelation("maintenance_fee")
cache.AbolishRelation(fmt.Sprintf("maintenance_fee:%s", fid))
return nil
}
func (_MaintenanceFeeService) EnsureFeeBelongs(uid, mid string) (bool, error) {
if has, _ := cache.CheckExists("maintenance_fee", mid, uid); has {
return true, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
parks := make([]model.Park, 0)
err := global.DB.NewSelect().Model(&parks).
Relation("MaintenanceFees").
Where("p.user_id = ?", uid).
Scan(ctx)
if err != nil {
return false, err
}
exists := lo.Reduce(parks, func(acc bool, elem model.Park, _ int) bool {
for _, e := range elem.MaintenanceFees {
if e.Id == mid {
return acc || true
}
}
return acc || false
}, false)
if !exists {
return false, exceptions.NewNotFoundError("指定附加费所属园区未找到。")
}
cache.CacheExists([]string{fmt.Sprintf("maintenance_fee:%s", mid), "maintenance_fee", "park"}, "maintenance_fee", mid, uid)
return exists, nil
}
type _FeeStat struct {
ParkId string
Period string
Total decimal.Decimal
}
func (f _MaintenanceFeeService) QueryAdditionalCharges(uid, pid, period, keyword string, requestPage int) ([]model.AdditionalCharge, int64, error) {
var (
conditions = []string{fmt.Sprintf("%d", requestPage)}
statFees = make([]_FeeStat, 0)
cond = global.DB.NewSelect().
Model((*model.MaintenanceFee)(nil)).
Relation("Park", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.ExcludeColumn("*")
}).
Relation("Park.Enterprise", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.ExcludeColumn("*")
}).
Where("m.enabled = ?", true)
)
if len(uid) > 0 {
cond = cond.Where("park__enterprise.id = ?", uid)
conditions = append(conditions, uid)
} else {
conditions = append(conditions, "_")
}
if len(pid) > 0 {
cond = cond.Where("park.id = ?", pid)
conditions = append(conditions, pid)
} else {
conditions = append(conditions, "_")
}
if len(period) > 0 {
cond = cond.Where("m.period = ?", period)
conditions = append(conditions, period)
} else {
conditions = append(conditions, "_")
}
if len(keyword) > 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("park__enterprise.name like ?", keywordCond).
WhereOr("park__enterprise.abbr like ?", keywordCond).
WhereOr("park.name like ?", keywordCond).
WhereOr("park.abbr like ?", keywordCond).
WhereOr("park.address like ?", keywordCond)
})
conditions = append(conditions, keyword)
} else {
conditions = append(conditions, "_")
}
if cachedTotal, err := cache.RetreiveCount("additional_charge", conditions...); cachedTotal != -1 && err == nil {
if cachedData, _ := cache.RetreiveSearch[[]model.AdditionalCharge]("additional_charge", conditions...); cachedData != nil {
return *cachedData, cachedTotal, nil
}
}
ctx, cancel := global.TimeoutContext(24)
defer cancel()
startItem := (requestPage - 1) * config.ServiceSettings.ItemsPageSize
total, err := cond.ColumnExpr("sum(?) as total", bun.Ident("fee")).
Column("park_id", "period").
Group("park_id", "period").
Order("period desc").
Limit(config.ServiceSettings.ItemsPageSize).
Offset(startItem).
ScanAndCount(ctx, &statFees)
if err != nil {
return make([]model.AdditionalCharge, 0), 0, fmt.Errorf("获取附加费统计信息出现错误,%w", err)
}
parkIds := lo.Reduce(
statFees,
func(acc mapset.Set[string], elem _FeeStat, _ int) mapset.Set[string] {
acc.Add(elem.ParkId)
return acc
},
mapset.NewSet[string](),
)
parks := make([]model.Park, 0)
if len(parkIds.ToSlice()) > 0 {
err = global.DB.NewSelect().Model(&parks).Relation("Enterprise").
Where("p.id in (?)", bun.In(parkIds.ToSlice())).
Scan(ctx)
if err != nil {
return make([]model.AdditionalCharge, 0), 0, fmt.Errorf("获取园区信息出现错误,%w", err)
}
}
assembledStat := lo.Reduce(
statFees,
func(acc []model.AdditionalCharge, elem _FeeStat, _ int) []model.AdditionalCharge {
park, has := lo.Find(parks, func(p model.Park) bool {
return p.Id == elem.ParkId
})
f.l.Debug("Park detection.", zap.Bool("has", has), zap.Any("park", park))
if has {
if !park.Area.Valid || park.Area.Decimal.Equal(decimal.Zero) {
return acc
}
price := elem.Total.Div(park.Area.Decimal).RoundBank(8)
return append(acc, model.AdditionalCharge{
ParkId: elem.ParkId,
Period: elem.Period,
Fee: elem.Total,
Price: price,
QuarterPrice: price.Div(decimal.NewFromInt(4)),
SemiAnnualPrice: price.Div(decimal.NewFromInt(2)),
Enterprise: model.FromUserDetail(*park.Enterprise),
Park: park,
})
} else {
return acc
}
},
make([]model.AdditionalCharge, 0),
)
cache.CacheCount([]string{"maintenance_fee"}, "additional_charge", int64(total), conditions...)
cache.CacheSearch(assembledStat, []string{"maintenance_fee"}, "additional_charge", conditions...)
return assembledStat, int64(total), nil
}

View File

@ -1,231 +0,0 @@
package service
import (
"context"
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/excel"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"strconv"
mapset "github.com/deckarep/golang-set/v2"
"github.com/samber/lo"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _Meter04kVService struct {
l *zap.Logger
}
var Meter04kVService = _Meter04kVService{
l: logger.Named("Service", "Meter04KV"),
}
func (_Meter04kVService) ListMeterDetail(park, keyword string, page int) ([]model.Meter04KV, int64, error) {
var (
condition = make([]string, 0)
meters = make([]model.Meter04KV, 0)
)
cond := global.DB.NewSelect().Model(&meters).
Where("park_id = ?", park)
condition = append(condition, park, strconv.Itoa(page))
if len(keyword) > 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("address like ?", keywordCond).
WhereOr("code like ?", keywordCond).
WhereOr("customer_name like ?", keywordCond).
WhereOr("contact_name like ?", keywordCond).
WhereOr("contact_phone like ?", keywordCond)
})
condition = append(condition, keyword)
}
if cachedTotal, err := cache.RetreiveCount("meter_04kv", condition...); cachedTotal != -1 && err == nil {
if cachedMeters, _ := cache.RetreiveSearch[[]model.Meter04KV]("meter_04kv", condition...); cachedMeters != nil {
return *cachedMeters, cachedTotal, nil
}
}
ctx, cancel := global.TimeoutContext()
defer cancel()
startItem := (page - 1) * config.ServiceSettings.ItemsPageSize
total, err := cond.
Order("seq asc", "code asc").
Limit(config.ServiceSettings.ItemsPageSize).
Offset(startItem).
ScanAndCount(ctx)
relations := lo.Map(meters, func(m model.Meter04KV, _ int) string {
return fmt.Sprintf("meter_04kv:%s:%s", m.ParkId, m.Code)
})
relations = append(relations, "meter_04kv", "park")
cache.CacheCount(relations, "meter_04kv", int64(total), condition...)
cache.CacheSearch(meters, relations, "meter_04kv", condition...)
return meters, int64(total), err
}
func (_Meter04kVService) Get04kVMeterDetail(park, code string) (*model.Meter04KV, error) {
if cachedMeter, _ := cache.RetreiveEntity[model.Meter04KV]("meter_04kv", fmt.Sprintf("%s_%s", park, code)); cachedMeter != nil {
return cachedMeter, nil
}
var meter = new(model.Meter04KV)
ctx, cancel := global.TimeoutContext()
defer cancel()
err := global.DB.NewSelect().Model(meter).
Where("code = ?", code).
Where("park_id = ?", park).
Scan(ctx)
if err != nil {
return nil, err
}
cache.CacheEntity(meter, []string{fmt.Sprintf("meter_04kv:%s:%s", park, code), "park"}, "meter_04kv", fmt.Sprintf("%s_%s", park, code))
return meter, nil
}
func (_Meter04kVService) insertNewMeter(tx *bun.Tx, ctx *context.Context, meter model.Meter04KV) error {
_, err := tx.NewInsert().Model(&meter).Exec(*ctx)
if err != nil {
tx.Rollback()
}
cache.AbolishRelation("meter_04kv")
return err
}
func (_Meter04kVService) updateMeter(tx *bun.Tx, ctx *context.Context, meter model.Meter04KV) error {
_, err := tx.NewUpdate().Model(&meter).
Where("code = ?", meter.Code).
Where("park_id = ?", meter.ParkId).
Column("address", "customer_name", "contact_name", "contact_phone", "ratio", "seq", "public_meter", "enabled").
Exec(*ctx)
if err != nil {
tx.Rollback()
}
cache.AbolishRelation(fmt.Sprintf("meter_04kv:%s:%s", meter.ParkId, meter.Code))
return err
}
func (m _Meter04kVService) CreateSingleMeter(meter model.Meter04KV) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
err = m.insertNewMeter(&tx, &ctx, meter)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation("meter_04kv")
cache.AbolishRelation(fmt.Sprintf("meter_04kv:%s:%s", meter.ParkId, meter.Code))
return nil
}
func (m _Meter04kVService) UpdateSingleMeter(meter *model.Meter04KV) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
err = m.updateMeter(&tx, &ctx, *meter)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation(fmt.Sprintf("meter_04kv:%s:%s", meter.ParkId, meter.Code))
return nil
}
func (_Meter04kVService) DuplicateMeterCodeValidate(meters []model.Meter04KV) []excel.ExcelAnalysisError {
errs := make([]excel.ExcelAnalysisError, 0)
for i := 0; i < len(meters); i++ {
for j := i + 1; j < len(meters); j++ {
if meters[j].Code == meters[i].Code {
errs = append(errs, excel.ExcelAnalysisError{Row: j + 1, Col: 0, Err: excel.AnalysisError{Err: fmt.Errorf("第 %d 行表计表号与第 %d 行表计表号重复!", j+1, i+1)}})
}
}
}
return errs
}
func (m _Meter04kVService) BatchCreateMeter(meters []model.Meter04KV) error {
parkIds := lo.Reduce(meters, func(acc mapset.Set[string], elem model.Meter04KV, index int) mapset.Set[string] {
acc.Add(elem.ParkId)
return acc
}, mapset.NewSet[string]())
if parkIds.Cardinality() > 1 {
return fmt.Errorf("一次只能向同一个园区中添加0.4kV表计。")
}
parkId, _ := parkIds.Pop()
ctx, cancel := global.TimeoutContext(120)
defer cancel()
allMeterCodes := make([]string, 0)
err := global.DB.NewSelect().Model((*model.Meter04KV)(nil)).
Where("park_id = ?", parkId).
Column("code").
Scan(ctx, &allMeterCodes)
if err != nil {
return err
}
meterCodes := mapset.NewSet(allMeterCodes...)
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
var (
updates = make([]model.Meter04KV, 0)
inserts = make([]model.Meter04KV, 0)
)
for _, meter := range meters {
if meterCodes.Contains(meter.Code) {
updates = append(updates, meter)
} else {
inserts = append(inserts, meter)
}
}
if len(updates) > 0 {
_, err = tx.NewUpdate().Model(&updates).
Column("address", "customer_name", "contact_name", "contact_phone", "ratio", "seq", "public_meter", "enabled").
Bulk().
Exec(ctx)
if err != nil {
tx.Rollback()
return err
}
}
if len(inserts) > 0 {
_, err = tx.NewInsert().Model(&inserts).Exec(ctx)
if err != nil {
tx.Rollback()
return err
}
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation("meter_04kv")
return nil
}

View File

@ -1,171 +0,0 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _ParkService struct {
l *zap.Logger
}
var ParkService = _ParkService{
l: logger.Named("Service", "Park"),
}
func (_ParkService) SaveNewPark(park model.Park) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
_, err := global.DB.NewInsert().Model(&park).Exec(ctx)
if err != nil {
return err
}
cache.AbolishRelation("park")
return nil
}
func (_ParkService) UpdateParkInfo(park *model.Park) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewUpdate().Model(park).
Where("id = ?", park.Id).
Where("user_id = ?", park.UserId).
Column("name", "abbr", "region", "area", "address", "contact", "phone", "capacity", "tenement_quantity", "category", "meter_04kv_type").
Exec(ctx)
if err != nil {
if rows, _ := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到符合条件的园区。")
} else {
return err
}
}
cache.AbolishRelation(fmt.Sprintf("park:%s", park.Id))
return nil
}
func (_ParkService) ChangeParkState(uid, pid string, state bool) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewUpdate().Model((*model.Park)(nil)).
Where("id = ?", pid).
Where("user_id = ?", uid).
Set("enabled = ?", state).
Exec(ctx)
if err != nil {
if rows, _ := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到符合条件的园区。")
} else {
return err
}
}
cache.AbolishRelation(fmt.Sprintf("park:%s", pid))
return nil
}
func (_ParkService) DeletePark(uid, pid string) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewDelete().Model((*model.Park)(nil)).
Where("id = ?", pid).
Where("user_id = ?", uid).
Exec(ctx)
if err != nil {
if rows, _ := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到符合条件的园区。")
} else {
return err
}
}
cache.AbolishRelation("park")
cache.AbolishRelation(fmt.Sprintf("park:%s", pid))
return nil
}
func (_ParkService) ListAllParkBelongsTo(uid, keyword string) ([]model.Park, error) {
if parks, _ := cache.RetreiveSearch[[]model.Park]("park", "belong", uid, keyword); parks != nil {
return *parks, nil
}
parks := make([]model.Park, 0)
cond := global.DB.NewSelect().Model(&parks).
Where("user_id = ?", uid)
if len(keyword) > 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("name like ?", keywordCond).
WhereOr("abbr like ?", keywordCond).
WhereOr("address like ?", keywordCond).
WhereOr("contact like ?", keywordCond).
WhereOr("phone like ?", keywordCond)
})
}
ctx, cancel := global.TimeoutContext()
defer cancel()
err := cond.Scan(ctx)
if err != nil {
return make([]model.Park, 0), err
}
relations := []string{"park"}
for _, p := range parks {
relations = append(relations, fmt.Sprintf("park:%s", p.Id))
}
cache.CacheSearch(parks, relations, "park", "belong", uid, keyword)
return parks, nil
}
func (_ParkService) FetchParkDetail(pid string) (*model.Park, error) {
if park, _ := cache.RetreiveEntity[model.Park]("park", pid); park != nil {
return park, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var park = new(model.Park)
err := global.DB.NewSelect().Model(park).
Where("id = ?", pid).
Scan(ctx)
if err != nil {
return nil, exceptions.NewNotFoundErrorFromError("未找到符合条件的园区记录。", err)
}
cache.CacheEntity(*park, []string{fmt.Sprintf("park:%s", pid)}, "park", pid)
return park, nil
}
func (_ParkService) EnsurePark(uid, pid string) (bool, error) {
if has, _ := cache.CheckExists("park", pid, uid); has {
return has, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
has, err := global.DB.NewSelect().Model((*model.Park)(nil)).
Where("id = ?", pid).
Where("user_id = ?", uid).
Exists(ctx)
if has {
cache.CacheExists([]string{fmt.Sprintf("park:%s", pid)}, "park", pid, uid)
}
return has, err
}
func (_ParkService) AllParkIds(uid string) ([]string, error) {
if ids, _ := cache.RetreiveSearch[[]string]("park", "belong", uid); ids != nil {
return *ids, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var ids = make([]string, 0)
err := global.DB.NewSelect().Model((*model.Park)(nil)).
Where("user_id = ?", uid).
Column("id").
Scan(ctx, &ids)
if err != nil {
return make([]string, 0), err
}
cache.CacheSearch(ids, []string{"park"}, "park", "belong", uid)
return ids, nil
}

View File

@ -1,70 +0,0 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"github.com/samber/lo"
"go.uber.org/zap"
)
type _RegionService struct {
l *zap.Logger
}
var RegionService = _RegionService{
l: logger.Named("Service", "Region"),
}
func (_RegionService) FetchSubRegions(parent string) ([]model.Region, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
if regions, _ := cache.RetreiveSearch[[]model.Region]("region", "parent", parent); regions != nil {
return *regions, nil
}
regions := make([]model.Region, 0)
err := global.DB.NewSelect().Model(&regions).Where("parent = ?", parent).Order("code asc").Scan(ctx)
if err != nil {
return make([]model.Region, 0), err
}
relationNames := lo.Map(regions, func(r model.Region, index int) string {
return fmt.Sprintf("region:%s", r.Code)
})
cache.CacheSearch(regions, relationNames, "region", "parent", parent)
return regions, err
}
func (r _RegionService) FetchAllParentRegions(code string) ([]model.Region, error) {
regions := make([]model.Region, 0)
region, err := r.fetchRegion(code)
if err != nil {
return regions, err
}
regions = append(regions, *region)
for region.Level > 1 {
region, err = r.fetchRegion(region.Parent)
if err != nil {
return make([]model.Region, 0), nil
}
regions = append(regions, *region)
}
return regions, nil
}
func (_RegionService) fetchRegion(code string) (*model.Region, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
if cachedRegion, _ := cache.RetreiveSearch[model.Region]("region", code); cachedRegion != nil {
return cachedRegion, nil
}
region := new(model.Region)
err := global.DB.NewSelect().Model(region).Where("code = ?", code).Scan(ctx)
if err != nil {
relationName := fmt.Sprintf("region:%s", code)
cache.CacheSearch(region, []string{relationName}, "region", code)
}
return region, err
}

View File

@ -1,742 +0,0 @@
package service
import (
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"fmt"
"strconv"
"time"
"github.com/fufuok/utils"
"github.com/google/uuid"
"github.com/samber/lo"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _ReportService struct {
l *zap.Logger
}
var ReportService = _ReportService{
l: logger.Named("Service", "Report"),
}
func (_ReportService) FetchParksWithNewestReport(uid string) ([]model.ParkNewestReport, error) {
if cachedParks, _ := cache.RetreiveSearch[[]model.ParkNewestReport]("park_newest_report", uid); cachedParks != nil {
return *cachedParks, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
parks := make([]model.Park, 0)
err := global.DB.NewSelect().Model(&parks).Relation("Reports").
Where("user_id = ?", uid).
Where("enabled = ?", true).
Order("created_at asc").
Scan(ctx)
if err != nil {
return make([]model.ParkNewestReport, 0), err
}
reducedParks := lo.Reduce(
parks,
func(acc map[string]model.ParkNewestReport, elem model.Park, index int) map[string]model.ParkNewestReport {
if _, ok := acc[elem.Id]; !ok {
newestReport := lo.MaxBy(elem.Reports, func(a, b *model.Report) bool {
return a.Period.After(b.Period)
})
acc[elem.Id] = model.ParkNewestReport{
Report: newestReport,
Park: elem,
}
}
return acc
},
make(map[string]model.ParkNewestReport, 0),
)
relations := lo.Map(parks, func(r model.Park, _ int) string {
return fmt.Sprintf("park:%s", r.Id)
})
relations = append(relations, "park", "report")
cache.CacheSearch(reducedParks, relations, "park_newest_report", uid)
return lo.Values(reducedParks), nil
}
func (_ReportService) IsNewPeriodValid(uid, pid string, period time.Time) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
reports := make([]model.Report, 0)
if cachedReport, _ := cache.RetreiveSearch[[]model.Report]("report", "user", uid, "park", pid); cachedReport != nil {
reports = *cachedReport
} else {
err := global.DB.NewSelect().Model(&reports).Relation("Park").
Where("park.user_id = ?", uid).
Where("r.park_id = ?", pid).
Scan(ctx)
if err != nil {
return false, err
}
cache.CacheSearch(reports, []string{"report", "park"}, "park", "user", uid, "park", pid)
}
// 检查给定的期数在目前的记录中是否已经存在
exists := lo.Reduce(
reports,
func(acc bool, elem model.Report, index int) bool {
if elem.Period.Equal(period) {
return acc || true
} else {
return acc || false
}
},
false,
)
if exists {
return false, nil
}
// 检查给定的期数与目前已发布的最大期数的关系
maxPublished := lo.Reduce(
reports,
func(acc *time.Time, elem model.Report, index int) *time.Time {
if elem.Published {
if acc == nil || (acc != nil && elem.Period.After(*acc)) {
return &elem.Period
}
}
return acc
},
nil,
)
// 检查给定的期数与目前未发布的最大期数的关系
maxUnpublished := lo.Reduce(
reports,
func(acc *time.Time, elem model.Report, index int) *time.Time {
if acc == nil || (acc != nil && elem.Period.After(*acc)) {
return &elem.Period
}
return acc
},
nil,
)
if maxUnpublished == nil {
return true, nil
}
if maxPublished != nil && maxUnpublished.Equal(*maxPublished) {
// 此时不存在未发布的报表
return tools.IsNextMonth(*maxPublished, period), nil
} else {
// 存在未发布的报表
return false, nil
}
}
func (_ReportService) InitializeNewReport(parkId string, period time.Time) (string, error) {
ctx, cancel := global.TimeoutContext(120)
defer cancel()
periods := make([]model.Report, 0)
err := global.DB.NewSelect().Model(&periods).
Where("park_id = ?", parkId).
Where("published = ?", true).
Order("period asc").
Scan(ctx)
if err != nil {
return "", err
}
// 获取上一期的报表索引信息
maxPublishedReport := lo.Reduce(
periods,
func(acc *model.Report, elem model.Report, index int) *model.Report {
if acc == nil || (acc != nil && elem.Period.After(acc.Period)) {
return &elem
}
return acc
},
nil,
)
var indexedLastPeriodCustomers map[string]model.EndUserDetail
if maxPublishedReport != nil {
// 获取上一期的所有户表信息,并获取当前已启用的所有用户
lastPeriodCustomers := make([]model.EndUserDetail, 0)
err = global.DB.NewSelect().Model(&lastPeriodCustomers).
Where("report_id = ?", maxPublishedReport.Id).
Scan(ctx)
if err != nil {
return "", err
}
indexedLastPeriodCustomers = lo.Reduce(
lastPeriodCustomers,
func(acc map[string]model.EndUserDetail, elem model.EndUserDetail, index int) map[string]model.EndUserDetail {
acc[elem.MeterId] = elem
return acc
},
make(map[string]model.EndUserDetail, 0),
)
} else {
indexedLastPeriodCustomers = make(map[string]model.EndUserDetail, 0)
}
currentActivatedCustomers := make([]model.Meter04KV, 0)
err = global.DB.NewSelect().Model(&currentActivatedCustomers).
Where("park_id = ?", parkId).
Where("enabled = ?", true).
Scan(ctx)
if err != nil {
return "", err
}
var parkInfo = new(model.Park)
err = global.DB.NewSelect().Model(parkInfo).
Where("id = ?", parkId).
Scan(ctx)
if err != nil || parkInfo == nil {
return "", exceptions.NewNotFoundError(fmt.Sprintf("指定园区未找到, %v", err))
}
// 生成新一期的报表
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return "", err
}
// 插入已经生成的报表索引信息和园区概况信息
newReport := model.Report{
Id: uuid.New().String(),
ParkId: parkId,
Period: period,
Category: parkInfo.Category,
SubmeterType: parkInfo.SubmeterType,
StepState: model.NewSteps(),
Published: false,
Withdraw: model.REPORT_NOT_WITHDRAW,
}
newReportSummary := model.ReportSummary{
ReportId: newReport.Id,
}
_, err = tx.NewInsert().Model(&newReport).Exec(ctx)
if err != nil {
tx.Rollback()
return "", err
}
_, err = tx.NewInsert().Model(&newReportSummary).Exec(ctx)
if err != nil {
tx.Rollback()
return "", err
}
// 生成并插入户表信息
var inserts = make([]model.EndUserDetail, 0)
for _, customer := range currentActivatedCustomers {
newEndUser := model.EndUserDetail{
ReportId: newReport.Id,
ParkId: parkId,
MeterId: customer.Code,
Seq: customer.Seq,
Ratio: customer.Ratio,
Address: customer.Address,
CustomerName: customer.CustomerName,
ContactName: customer.ContactName,
ContactPhone: customer.ContactPhone,
IsPublicMeter: customer.IsPublicMeter,
LastPeriodOverall: decimal.Zero,
LastPeriodCritical: decimal.Zero,
LastPeriodPeak: decimal.Zero,
LastPeriodFlat: decimal.Zero,
LastPeriodValley: decimal.Zero,
}
if lastPeriod, ok := indexedLastPeriodCustomers[customer.Code]; ok {
newEndUser.LastPeriodOverall = lastPeriod.CurrentPeriodOverall
newEndUser.LastPeriodCritical = lastPeriod.CurrentPeriodCritical
newEndUser.LastPeriodPeak = lastPeriod.CurrentPeriodPeak
newEndUser.LastPeriodFlat = lastPeriod.CurrentPeriodFlat
newEndUser.LastPeriodValley = lastPeriod.CurrentPeriodValley
}
inserts = append(inserts, newEndUser)
}
if len(inserts) > 0 {
_, err = tx.NewInsert().Model(&inserts).Exec(ctx)
if err != nil {
tx.Rollback()
return "", err
}
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return "", err
}
cache.AbolishRelation("report")
return newReport.Id, nil
}
func (_ReportService) RetreiveReportIndex(rid string) (*model.Report, error) {
if cachedReport, _ := cache.RetreiveEntity[model.Report]("report", rid); cachedReport != nil {
return cachedReport, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var report = new(model.Report)
err := global.DB.NewSelect().Model(report).
Where("id = ?", rid).
Scan(ctx)
if err != nil {
return nil, err
}
cache.CacheEntity(report, []string{fmt.Sprintf("report:%s", rid), "park"}, "report", rid)
return report, nil
}
func (_ReportService) RetreiveReportSummary(rid string) (*model.ReportSummary, error) {
if cachedSummary, _ := cache.RetreiveEntity[model.ReportSummary]("report_summary", rid); cachedSummary != nil {
return cachedSummary, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var summary = new(model.ReportSummary)
err := global.DB.NewSelect().Model(summary).
Where("report_id = ?", rid).
Scan(ctx)
if err != nil {
return nil, err
}
cache.CacheEntity(summary, []string{fmt.Sprintf("report:%s", rid), "park"}, "report_summary", rid)
return summary, nil
}
func (_ReportService) UpdateReportSummary(summary *model.ReportSummary) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
_, err := global.DB.NewUpdate().Model(summary).
WherePK().
Column("overall", "overall_fee", "critical", "critical_fee", "peak", "peak_fee", "valley", "valley_fee", "basic_fee", "adjust_fee").
Exec(ctx)
if err == nil {
cache.AbolishRelation(fmt.Sprintf("report:%s", summary.ReportId))
}
return err
}
func (_ReportService) CalculateSummaryAndFinishStep(reportId string) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
var report = new(model.Report)
err := global.DB.NewSelect().Model(report).Relation("Summary").
Where("r.id = ?", reportId).
Scan(ctx)
if err != nil || report == nil {
return exceptions.NewNotFoundErrorFromError("未找到指定报表", err)
}
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
report.Summary.CalculatePrices()
_, err = tx.NewUpdate().Model(report.Summary).
WherePK().
Column("overall_price", "critical_price", "peak_price", "flat", "flat_fee", "flat_price", "valley_price", "consumption_fee").
Exec(ctx)
if err != nil {
tx.Rollback()
return err
}
report.StepState.Summary = true
_, err = tx.NewUpdate().Model(report).
WherePK().
Column("step_state").
Exec(ctx)
if err != nil {
tx.Rollback()
return err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation(fmt.Sprintf("report:%s", reportId))
return nil
}
func (_ReportService) FetchWillDulutedMaintenanceFees(reportId string) ([]model.WillDilutedFee, error) {
if cachedFees, _ := cache.RetreiveSearch[[]model.WillDilutedFee]("will_diluted_fee", "report", reportId); cachedFees != nil {
return *cachedFees, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
fees := make([]model.WillDilutedFee, 0)
err := global.DB.NewSelect().Model(&fees).
Where("report_id = ?", reportId).
Order("created_at asc").
Scan(ctx)
if err != nil {
return make([]model.WillDilutedFee, 0), nil
}
relations := lo.Map(fees, func(f model.WillDilutedFee, _ int) string {
return fmt.Sprintf("will_diluted_fee:%s", f.Id)
})
relations = append(relations, fmt.Sprintf("report:will_diluted_fee:%s", reportId), fmt.Sprintf("report:%s", reportId), "park")
cache.CacheSearch(fees, relations, "will_diluted_fee", "report", reportId)
return fees, nil
}
func (_ReportService) CreateTemporaryWillDilutedMaintenanceFee(fee model.WillDilutedFee) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
fee.Id = utils.UUIDString()
_, err := global.DB.NewInsert().Model(&fee).Exec(ctx)
cache.AbolishRelation(fmt.Sprintf("report:will_diluted_fee:%s", fee.ReportId))
return err
}
func (_ReportService) BatchSaveMaintenanceFee(reportId string, fees []model.WillDilutedFee) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
// 首先删除所有预定义的部分条件是指定报表IDSourceID不为空。
_, err = tx.NewDelete().Model((*model.WillDilutedFee)(nil)).
Where("report_id = ?", reportId).
Where("source_id is not null").
Exec(ctx)
if err != nil {
tx.Rollback()
return err
}
// 然后插入新的记录
_, err = tx.NewInsert().Model(&fees).Exec(ctx)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation(fmt.Sprintf("report:will_diluted_fee:%s", reportId))
return nil
}
func (_ReportService) UpdateMaintenanceFee(feeId string, updates map[string]interface{}) (err error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
updates["last_modified_at"] = lo.ToPtr(time.Now())
_, err = global.DB.NewUpdate().Model(&updates).TableExpr("will_diluted_fee").
Where("id = ?", feeId).
Exec(ctx)
cache.AbolishRelation(fmt.Sprintf("will_diluted_fee:%s", feeId))
return
}
func (_ReportService) DeleteWillDilutedFee(fee string) (err error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
_, err = global.DB.NewDelete().Model((*model.WillDilutedFee)(nil)).
Where("id = ?", fee).
Exec(ctx)
cache.AbolishRelation(fmt.Sprintf("will_diluted_fee:%s", fee))
return
}
func (_ReportService) ProgressReportWillDilutedFee(report model.Report) (err error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
report.StepState.WillDiluted = true
_, err = global.DB.NewUpdate().Model(&report).
WherePK().
Column("step_state").
Exec(ctx)
cache.AbolishRelation(fmt.Sprintf("report:%s", report.Id))
return
}
func (_ReportService) ProgressReportRegisterEndUser(report model.Report) (err error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
report.StepState.Submeter = true
_, err = global.DB.NewUpdate().Model(&report).
WherePK().
Column("step_state").
Exec(ctx)
cache.AbolishRelation(fmt.Sprintf("report:%s", report.Id))
return
}
func (_ReportService) ProgressReportCalculate(report model.Report) (err error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
report.StepState.Calculate = true
_, err = global.DB.NewUpdate().Model(&report).
WherePK().
Column("step_state").
Exec(ctx)
cache.AbolishRelation(fmt.Sprintf("report:%s", report.Id))
return
}
func (_ReportService) RetreiveParkEndUserMeterType(reportId string) (int, error) {
if cachedType, _ := cache.RetreiveEntity[int]("park_end_user_meter_type", fmt.Sprintf("report_%s", reportId)); cachedType != nil {
return *cachedType, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var mType int
err := global.DB.NewSelect().Model((*model.Report)(nil)).
Relation("Park", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Column("meter_04kv_type")
}).
ExcludeColumn("*").
Where("r.id = ?", reportId).
Scan(ctx, &mType)
if err != nil {
return -1, err
}
cache.CacheEntity(mType, []string{fmt.Sprintf("report:%s", reportId), "park"}, "park_end_user_meter_type", fmt.Sprintf("report_%s", reportId))
return mType, nil
}
func (_ReportService) PublishReport(report model.Report) (err error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
report.Published = true
report.PublishedAt = lo.ToPtr(time.Now())
report.StepState.Publish = true
_, err = global.DB.NewUpdate().Model(&report).
WherePK().
Column("step_state", "published", "published_at").
Exec(ctx)
cache.AbolishRelation(fmt.Sprintf("report:%s", report.Id))
return
}
func (_ReportService) SearchReport(requestUser, requestPark, requestKeyword string, requestPeriod *time.Time, requestPage int, onlyPublished bool) ([]model.JoinedReportForWithdraw, int64, error) {
var (
conditions = make([]string, 0)
reports = make([]model.Report, 0)
cond = global.DB.NewSelect().
Model(&reports).
Relation("Park").Relation("Park.Enterprise")
)
conditions = append(conditions, strconv.Itoa(requestPage))
if onlyPublished {
cond = cond.Where("r.published = ?", true)
}
conditions = append(conditions, strconv.FormatBool(onlyPublished))
if len(requestUser) > 0 {
cond = cond.Where("park.user_id = ?", requestUser)
conditions = append(conditions, requestUser)
}
if len(requestPark) > 0 {
cond = cond.Where("park.id = ?", requestPark)
conditions = append(conditions, requestPark)
}
if requestPeriod != nil {
cond = cond.Where("r.period = ?", *requestPeriod)
conditions = append(conditions, strconv.FormatInt(requestPeriod.Unix(), 10))
}
if len(requestKeyword) > 0 {
keywordCond := "%" + requestKeyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("park.name like ?", keywordCond).
WhereOr("park__enterprise.name like ?", keywordCond).
WhereOr("park__enterprise.abbr like ?", keywordCond).
WhereOr("park.abbr like ?", keywordCond).
WhereOr("park__enterprise.address like ?", keywordCond).
WhereOr("park.address like ?", keywordCond)
})
conditions = append(conditions, requestKeyword)
}
if cachedTotal, err := cache.RetreiveCount("join_report_for_withdraw", conditions...); cachedTotal != -1 && err == nil {
if cachedRecords, _ := cache.RetreiveSearch[[]model.JoinedReportForWithdraw]("join_report_for_withdraw", conditions...); cachedRecords != nil {
return *cachedRecords, cachedTotal, nil
}
}
ctx, cancel := global.TimeoutContext()
defer cancel()
startItem := (requestPage - 1) * config.ServiceSettings.ItemsPageSize
total, err := cond.Limit(config.ServiceSettings.ItemsPageSize).
Offset(startItem).
ScanAndCount(ctx)
records := make([]model.JoinedReportForWithdraw, 0)
relations := []string{"report", "park"}
for _, r := range reports {
records = append(records, model.JoinedReportForWithdraw{
Report: r,
Park: model.FromPark(*r.Park),
User: model.FromUserDetail(*r.Park.Enterprise),
})
relations = append(relations, fmt.Sprintf("report:%s", r.Id))
}
cache.CacheCount(relations, "join_report_for_withdraw", int64(total), conditions...)
cache.CacheSearch(records, relations, "join_report_for_withdraw", conditions...)
return records, int64(total), err
}
func (_ReportService) AssembleReportPublicity(reportId string) (*model.Publicity, error) {
if cachedPublicity, _ := cache.RetreiveEntity[model.Publicity]("publicity", reportId); cachedPublicity != nil {
return cachedPublicity, nil
}
// 资料准备
ctx, cancel := global.TimeoutContext()
defer cancel()
var report = new(model.Report)
err := global.DB.NewSelect().Model(report).
Relation("Summary").Relation("WillDilutedFees").Relation("EndUsers").
Relation("Park").Relation("Park.Enterprise").
Where("r.id = ?", reportId).
Scan(ctx)
if err != nil {
return nil, exceptions.NewNotFoundErrorFromError("未找到指定的公示报表", err)
}
// 组合数据
paidPart := model.PaidPart{
Overall: report.Summary.Overall,
OverallPrice: report.Summary.OverallPrice.Decimal,
ConsumptionFee: report.Summary.ConsumptionFee.Decimal,
OverallFee: report.Summary.OverallFee,
Critical: decimal.NewNullDecimal(report.Summary.Critical),
CriticalPrice: report.Summary.CriticalPrice,
CriticalFee: decimal.NewNullDecimal(report.Summary.CriticalFee),
Peak: decimal.NewNullDecimal(report.Summary.Peak),
PeakPrice: report.Summary.PeakPrice,
PeakFee: decimal.NewNullDecimal(report.Summary.PeakFee),
Flat: decimal.NewNullDecimal(report.Summary.Flat),
FlatPrice: report.Summary.FlatPrice,
FlatFee: decimal.NewNullDecimal(report.Summary.FlatFee),
Valley: decimal.NewNullDecimal(report.Summary.Valley),
ValleyPrice: report.Summary.ValleyPrice,
ValleyFee: decimal.NewNullDecimal(report.Summary.ValleyFee),
BasicFee: report.Summary.BasicFee,
AdjustFee: report.Summary.AdjustFee,
}
endUserSummary := model.ConsumptionOverallPart{
Overall: report.Summary.Customers.Consumption.Decimal,
OverallPrice: report.Summary.OverallPrice.Decimal,
ConsumptionFee: report.Summary.Customers.ConsumptionFee.Decimal,
OverallFee: report.Summary.Customers.ConsumptionFee.Decimal,
Critical: report.Summary.Customers.Critical,
CriticalPrice: report.Summary.CriticalPrice,
CriticalFee: report.Summary.Customers.CriticalFee,
Peak: report.Summary.Customers.Peak,
PeakPrice: report.Summary.PeakPrice,
PeakFee: report.Summary.Customers.PeakFee,
Flat: report.Summary.Customers.Flat,
FlatPrice: report.Summary.FlatPrice,
FlatFee: report.Summary.Customers.FlatFee,
Valley: report.Summary.Customers.Valley,
ValleyPrice: report.Summary.ValleyPrice,
ValleyFee: report.Summary.Customers.ValleyFee,
Proportion: report.Summary.Customers.Proportion.Decimal,
}
lossPart := model.LossPart{
Quantity: report.Summary.Loss.Decimal,
Price: report.Summary.OverallPrice.Decimal,
ConsumptionFee: report.Summary.LossFee.Decimal,
Proportion: report.Summary.LossProportion.Decimal,
AuthorizeQuantity: report.Summary.AuthorizeLoss.Decimal,
AuthorizeConsumptionFee: report.Summary.AuthorizeLossFee.Decimal,
}
publicSummary := model.ConsumptionOverallPart{
Overall: report.Summary.Publics.Consumption.Decimal,
OverallPrice: report.Summary.OverallPrice.Decimal,
ConsumptionFee: report.Summary.Publics.ConsumptionFee.Decimal,
OverallFee: report.Summary.Publics.ConsumptionFee.Decimal,
Critical: report.Summary.Publics.Critical,
CriticalPrice: report.Summary.CriticalPrice,
CriticalFee: report.Summary.Publics.CriticalFee,
Peak: report.Summary.Publics.Peak,
PeakPrice: report.Summary.PeakPrice,
PeakFee: report.Summary.Publics.PeakFee,
Flat: report.Summary.Publics.Flat,
FlatPrice: report.Summary.FlatPrice,
FlatFee: report.Summary.Publics.FlatFee,
Valley: report.Summary.Publics.Valley,
ValleyPrice: report.Summary.ValleyPrice,
ValleyFee: report.Summary.Publics.ValleyFee,
Proportion: report.Summary.Publics.Proportion.Decimal,
}
otherCollection := model.OtherShouldCollectionPart{
LossFee: report.Summary.AuthorizeLossFee,
BasicFees: report.Summary.BasicFee.Add(report.Summary.AdjustFee),
}
finalAdjustFee := lossPart.AuthorizeConsumptionFee.Add(otherCollection.BasicFees)
var adjustPrice = decimal.Zero
if !endUserSummary.Overall.Equal(decimal.Zero) {
adjustPrice = finalAdjustFee.Div(endUserSummary.Overall).RoundBank(8)
}
var adjustProportion = decimal.Zero
if !paidPart.OverallFee.Equal(decimal.Zero) {
adjustProportion = finalAdjustFee.Div(paidPart.OverallFee.Add(finalAdjustFee)).RoundBank(8)
}
maintenanceFees := model.MaintenancePart{
BasicFees: otherCollection.BasicFees,
LossFee: lossPart.AuthorizeConsumptionFee,
AdjustFee: finalAdjustFee,
LossProportion: lossPart.Proportion,
AdjustPrice: adjustPrice,
AdjustProportion: adjustProportion,
}
if maintenanceFees.LossProportion.GreaterThan(decimal.NewFromFloat(0.1)) {
maintenanceFees.LossProportion = decimal.NewFromFloat(0.1)
}
endUsers := lo.Map(
report.EndUsers,
func(elem *model.EndUserDetail, index int) model.EndUserSummary {
return model.EndUserSummary{
CustomerName: elem.CustomerName,
Address: elem.Address,
MeterId: elem.MeterId,
IsPublicMeter: elem.IsPublicMeter,
Overall: elem.Overall.Decimal,
OverallPrice: report.Summary.OverallPrice.Decimal,
OverallFee: elem.OverallFee.Decimal,
Critical: elem.Critical,
CriticalFee: elem.CriticalFee,
Peak: elem.Peak,
PeakFee: elem.PeakFee,
Valley: elem.Valley,
ValleyFee: elem.ValleyFee,
Loss: elem.LossDiluted.Decimal,
LossFee: elem.LossFeeDiluted.Decimal,
}
},
)
publicity := &model.Publicity{
Report: *report,
Park: *report.Park,
User: *report.Park.Enterprise,
Paid: paidPart,
EndUser: endUserSummary,
Loss: lossPart,
PublicConsumptionOverall: publicSummary,
OtherCollections: otherCollection,
Maintenance: maintenanceFees,
EndUserDetails: endUsers,
}
cache.CacheEntity(publicity, []string{fmt.Sprintf("publicity:%s", reportId), fmt.Sprintf("report:%s", reportId), "report", "park"}, "publicity", reportId)
return publicity, nil
}

View File

@ -1,94 +0,0 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"github.com/samber/lo"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _StatisticsService struct {
l *zap.Logger
}
var StatisticsService = _StatisticsService{
l: logger.Named("Service", "Stat"),
}
func (_StatisticsService) EnabledEnterprises() (int64, error) {
if cachedCount, err := cache.RetreiveCount("enabled_ent"); cachedCount != -1 && err == nil {
return cachedCount, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
c, err := global.DB.NewSelect().Model((*model.User)(nil)).
Where("type = ?", model.USER_TYPE_ENT).
Where("enabled = ?", true).
Count(ctx)
if err == nil {
cache.CacheCount([]string{"user"}, "enabled_ent", int64(c))
}
return int64(c), err
}
func (_StatisticsService) EnabledParks(userIds ...string) (int64, error) {
if cachedParks, err := cache.RetreiveCount("enabled_parks", userIds...); cachedParks != -1 && err == nil {
return cachedParks, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
query := global.DB.NewSelect().Model((*model.Park)(nil)).
Where("enabled = ?", true)
if len(userIds) > 0 {
query = query.Where("user_id in (?)", bun.In(userIds))
}
c, err := query.Count(ctx)
if err == nil {
cache.CacheCount([]string{"user", "park"}, "enabled_parks", int64(c), userIds...)
}
return int64(c), err
}
func (_StatisticsService) ParksNewestState(userIds ...string) ([]model.ParkPeriodStatistics, error) {
if cachedState, _ := cache.RetreiveSearch[[]model.ParkPeriodStatistics]("park_period_stat", userIds...); cachedState != nil {
return *cachedState, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
query := global.DB.NewSelect().Model((*model.Report)(nil)).
Relation("Park", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Column("id", "name")
}).
Where("park.enabled = ?", true).
Where("r.published = ?", true)
if len(userIds) > 0 {
query = query.Where("park.user_id in (?)", bun.In(userIds))
}
parks := make([]model.ParkPeriodStatistics, 0)
groupedParks := make(map[string]model.ParkPeriodStatistics, 0)
err := query.Column("period").Scan(ctx, &parks)
if err != nil {
return make([]model.ParkPeriodStatistics, 0), err
}
for _, p := range parks {
if c, ok := groupedParks[p.Id]; ok {
if c.Period != nil && p.Period != nil && p.Period.After(c.Period.Time) {
groupedParks[p.Id] = p
}
if c.Period == nil && p.Period != nil {
groupedParks[p.Id] = p
}
} else {
groupedParks[p.Id] = p
}
}
cache.CacheSearch(lo.Values(groupedParks), []string{"user", "park"}, "park_period_stat", userIds...)
return lo.Values(groupedParks), nil
}

View File

@ -1,447 +0,0 @@
package service
import (
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"fmt"
"strconv"
"time"
"github.com/fufuok/utils"
"github.com/google/uuid"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _UserService struct {
l *zap.Logger
}
var UserService = _UserService{
l: logger.Named("Service", "User"),
}
func (u _UserService) ProcessEnterpriseUserLogin(username, password string) (*model.Session, error) {
user, err := u.findUserWithCredentialsByUsername(username)
if err != nil {
return nil, err
}
if user == nil {
return nil, exceptions.NewAuthenticationError(404, "用户不存在。")
}
if user.Type != 0 {
return nil, exceptions.NewAuthenticationError(400, "用户类型不正确。")
}
if !user.Enabled {
return nil, exceptions.NewAuthenticationError(403, "用户已被禁用。")
}
hashedPassword := utils.Sha512Hex(password)
if hashedPassword != user.Password {
return nil, exceptions.NewAuthenticationError(402, "用户凭据不正确。")
}
if user.ResetNeeded {
authErr := exceptions.NewAuthenticationError(401, "用户凭据已失效。")
authErr.NeedReset = true
return nil, authErr
}
userDetial, _ := u.retreiveUserDetail(user.Id)
if userDetial.ServiceExpiration.Time.Before(time.Now()) {
return nil, exceptions.NewAuthenticationError(406, "用户服务期限已过。")
}
session := &model.Session{
Token: uuid.New().String(),
Uid: user.Id,
Type: user.Type,
Name: user.Username,
ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife),
}
if userDetial != nil {
session.Name = *userDetial.Name
}
cache.CacheSession(session)
return session, nil
}
func (u _UserService) ProcessManagementUserLogin(username, password string) (*model.Session, error) {
user, err := u.findUserWithCredentialsByUsername(username)
if err != nil {
return nil, err
}
if user == nil {
return nil, exceptions.NewAuthenticationError(404, "用户不存在。")
}
if user.Type != 1 && user.Type != 2 {
return nil, exceptions.NewAuthenticationError(400, "用户类型不正确。")
}
if !user.Enabled {
return nil, exceptions.NewAuthenticationError(403, "用户已被禁用。")
}
hashedPassword := utils.Sha512Hex(password)
if hashedPassword != user.Password {
return nil, exceptions.NewAuthenticationError(402, "用户凭据不正确。")
}
if user.ResetNeeded {
authErr := exceptions.NewAuthenticationError(401, "用户凭据已失效。")
authErr.NeedReset = true
return nil, authErr
}
session := &model.Session{
Token: uuid.New().String(),
Uid: user.Id,
Type: user.Type,
Name: user.Username,
ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife),
}
userDetial, _ := u.retreiveUserDetail(user.Id)
if userDetial != nil {
session.Name = *userDetial.Name
}
cache.CacheSession(session)
return session, nil
}
func (u _UserService) InvalidUserPassword(uid string) (string, error) {
user, err := u.findUserByID(uid)
if user == nil && err != nil {
return "", exceptions.NewNotFoundError("指定的用户不存在。")
}
ctx, cancel := global.TimeoutContext()
defer cancel()
verifyCode := tools.RandStr(10)
user.Password = utils.Sha512Hex(verifyCode)
user.ResetNeeded = true
res, err := global.DB.NewUpdate().Model(user).WherePK().Column("password", "reset_needed").Exec(ctx)
if err != nil {
return "", err
}
if affected, _ := res.RowsAffected(); affected > 0 {
// ! 清除与此用户所有相关的记录。
cache.AbolishRelation(fmt.Sprintf("user:%s", uid))
return verifyCode, nil
} else {
return "", exceptions.NewUnsuccessfulOperationError()
}
}
func (u _UserService) VerifyUserPassword(username, verifyCode string) (bool, error) {
user, err := u.findUserByUsername(username)
if user == nil || err != nil {
return false, exceptions.NewNotFoundError("指定的用户不存在。")
}
hashedVerifyCode := utils.Sha512Hex(verifyCode)
if hashedVerifyCode != user.Password {
return false, nil
} else {
return true, nil
}
}
func (u _UserService) ResetUserPassword(username, password string) (bool, error) {
user, err := u.findUserByUsername(username)
if user == nil || err != nil {
return false, exceptions.NewNotFoundError("指定的用户不存在。")
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user.Password = utils.Sha512Hex(password)
user.ResetNeeded = false
res, err := global.DB.NewUpdate().Model(user).WherePK().Column("password", "reset_needed").Exec(ctx)
if err != nil {
return false, err
}
if affected, _ := res.RowsAffected(); affected > 0 {
cache.AbolishRelation(fmt.Sprintf("user:%s", user.Id))
return true, nil
} else {
return false, nil
}
}
func (_UserService) IsUserExists(uid string) (bool, error) {
if has, _ := cache.CheckExists("user", uid); has {
return has, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
has, err := global.DB.NewSelect().Model((*model.User)(nil)).Where("id = ?", uid).Exists(ctx)
if has {
cache.CacheExists([]string{"user", fmt.Sprintf("user_%s", uid)}, "user", uid)
}
return has, err
}
func (_UserService) IsUsernameExists(username string) (bool, error) {
if has, _ := cache.CheckExists("user", username); has {
return has, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
has, err := global.DB.NewSelect().Model((*model.User)(nil)).Where("username = ?", username).Exists(ctx)
if has {
cache.CacheExists([]string{"user"}, "user", username)
}
return has, err
}
func (u _UserService) CreateUser(user *model.User, detail *model.UserDetail) (string, error) {
if len(user.Id) == 0 {
user.Id = uuid.New().String()
}
exists, err := u.IsUserExists(user.Id)
if exists {
return "", exceptions.NewNotFoundError("user already exists")
}
if err != nil {
return "", nil
}
detail.Id = user.Id
verifyCode := tools.RandStr(10)
user.Password = utils.Sha512Hex(verifyCode)
user.ResetNeeded = true
if detail.Name != nil {
finalAbbr := tools.PinyinAbbr(*detail.Name)
detail.Abbr = &finalAbbr
}
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return "", err
}
_, err = tx.NewInsert().Model(user).Exec(ctx)
if err != nil {
tx.Rollback()
return "", fmt.Errorf("user create failed: %w", err)
}
_, err = tx.NewInsert().Model(detail).Exec(ctx)
if err != nil {
tx.Rollback()
return "", fmt.Errorf("user Detail create failed: %w", err)
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return "", fmt.Errorf("transaction commit unsuccessful: %w", err)
}
// ! 广谱关联关系的废除必须是在有新记录加入或者有记录被删除的情况下。
cache.AbolishRelation("user")
return verifyCode, nil
}
func (u _UserService) SwitchUserState(uid string, enabled bool) error {
exists, err := u.IsUserExists(uid)
if !exists {
return exceptions.NewNotFoundError("user not exists")
}
if err != nil {
return err
}
newStateUser := new(model.User)
newStateUser.Id = uid
newStateUser.Enabled = enabled
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewUpdate().Model(newStateUser).WherePK().Column("enabled").Exec(ctx)
if affected, _ := res.RowsAffected(); err == nil && affected > 0 {
cache.AbolishRelation(fmt.Sprintf("user:%s", uid))
}
return err
}
func (us _UserService) SearchLimitUsers(keyword string, limit int) ([]model.JoinedUserDetail, error) {
if cachedUsers, _ := cache.RetreiveSearch[[]model.JoinedUserDetail]("join_user_detail", keyword, strconv.Itoa(limit)); cachedUsers != nil {
return *cachedUsers, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var users = make([]model.User, 0)
keywordCond := "%" + keyword + "%"
err := global.DB.NewSelect().Model(&users).Relation("Detail").
Where("u.type = ?", model.USER_TYPE_ENT).
WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("u.username like ?", keywordCond).
WhereOr("detail.name like ?", keywordCond).
WhereOr("detail.abbr like ?", keywordCond).
WhereOr("detail.contact like ?", keywordCond).
WhereOr("detail.address like ?", keywordCond)
}).
Order("u.created_at asc").
Limit(limit).
Offset(0).
Scan(ctx)
if err != nil {
return make([]model.JoinedUserDetail, 0), err
}
var detailedUsers = make([]model.JoinedUserDetail, 0)
var relations = make([]string, 0)
// ! 这里的转换是为了兼容之前使用Xorm时构建的关联关系而存在的
for _, u := range users {
detailedUsers = append(detailedUsers, model.JoinedUserDetail{
UserDetail: *u.Detail,
Id: u.Id,
Username: u.Username,
Type: u.Type,
Enabled: u.Enabled,
})
relations = append(relations, fmt.Sprintf("user:%s", u.Id))
}
relations = append(relations, "user")
cache.CacheSearch(detailedUsers, relations, "join_user_detail", keyword, strconv.Itoa(limit))
return detailedUsers, nil
}
func (_UserService) findUserWithCredentialsByUsername(username string) (*model.UserWithCredentials, error) {
if cachedUser, _ := cache.RetreiveSearch[model.UserWithCredentials]("user_with_credentials", username); cachedUser != nil {
return cachedUser, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user := new(model.UserWithCredentials)
err := global.DB.NewSelect().Model(user).Where("username = ?", username).Scan(ctx)
if err == nil {
cache.CacheSearch(*user, []string{fmt.Sprintf("user:%s", user.Id)}, "user_with_credentials", username)
}
return user, err
}
func (u _UserService) findUserByUsername(username string) (*model.User, error) {
if cachedUser, _ := cache.RetreiveSearch[model.User]("user", username); cachedUser != nil {
return cachedUser, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user := new(model.User)
err := global.DB.NewSelect().Model(user).Where("username = ?", username).Scan(ctx)
if err == nil {
cache.CacheSearch(*user, []string{fmt.Sprintf("user:%s", user.Id)}, "user", username)
}
return user, err
}
func (_UserService) retreiveUserDetail(uid string) (*model.UserDetail, error) {
if cachedUser, _ := cache.RetreiveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil {
return cachedUser, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user := &model.UserDetail{
Id: uid,
}
err := global.DB.NewSelect().Model(user).WherePK().Scan(ctx)
if err == nil {
cache.CacheEntity(*user, []string{fmt.Sprintf("user:%s", uid)}, "user_detail", uid)
}
return user, err
}
func (_UserService) findUserByID(uid string) (*model.User, error) {
cachedUser, _ := cache.RetreiveEntity[model.User]("user", uid)
if cachedUser != nil {
return cachedUser, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user := &model.User{
Id: uid,
}
err := global.DB.NewSelect().Model(&user).WherePK().Scan(ctx)
if err == nil {
cache.CacheEntity(*user, []string{fmt.Sprintf("user:%s", uid)}, "user", uid)
}
return user, err
}
func (_UserService) ListUserDetail(keyword string, userType int, userState *bool, page int) ([]model.JoinedUserDetail, int64, error) {
var (
cond = global.DB.NewSelect()
cacheConditions = make([]string, 0)
users = make([]model.User, 0)
)
cond = cond.Model(&users).Relation("Detail")
cacheConditions = append(cacheConditions, strconv.Itoa(page))
cond = cond.Where("detail.id <> ?", "000")
if len(keyword) != 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("u.username like ?", keywordCond).
WhereOr("detail.name like ?", keywordCond)
})
cacheConditions = append(cacheConditions, keyword)
}
if userType != -1 {
cond = cond.Where("u.type = ?", userType)
cacheConditions = append(cacheConditions, strconv.Itoa(userType))
}
if userState != nil {
cond = cond.Where("u.enabled = ?", *userState)
cacheConditions = append(cacheConditions, strconv.FormatBool(*userState))
}
startItem := (page - 1) * config.ServiceSettings.ItemsPageSize
// * 这里利用已经构建完成的条件集合从缓存中获取数据,如果所有数据都可以从缓存中获取,那么就直接返回了。
if cacheCounts, err := cache.RetreiveCount("join_user_detail", cacheConditions...); cacheCounts != -1 && err == nil {
if cachedUsers, _ := cache.RetreiveSearch[[]model.JoinedUserDetail]("join_user_detail", cacheConditions...); cachedUsers != nil {
return *cachedUsers, cacheCounts, nil
}
}
ctx, cancel := global.TimeoutContext()
defer cancel()
total, err := cond.
Limit(config.ServiceSettings.ItemsPageSize).Offset(startItem).
ScanAndCount(ctx)
var (
joinedUsers = make([]model.JoinedUserDetail, 0)
relations = []string{"user"}
)
for _, u := range users {
joinedUsers = append(joinedUsers, model.JoinedUserDetail{
UserDetail: *u.Detail,
Id: u.Id,
Username: u.Username,
Type: u.Type,
Enabled: u.Enabled,
})
relations = append(relations, fmt.Sprintf("user:%s", u.Id))
}
cache.CacheCount(relations, "join_user_detail", int64(total), cacheConditions...)
cache.CacheSearch(joinedUsers, relations, "join_user_detail", cacheConditions...)
return joinedUsers, int64(total), err
}
func (_UserService) FetchUserDetail(uid string) (*model.FullJoinedUserDetail, error) {
if cachedUser, _ := cache.RetreiveEntity[model.FullJoinedUserDetail]("full_join_user_detail", uid); cachedUser != nil {
return cachedUser, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user := &model.User{}
err := global.DB.NewSelect().Model(user).Relation("Detail").
Where("u.id = ?", uid).
Scan(ctx)
if err == nil {
fullJoinedUser := &model.FullJoinedUserDetail{
User: *user,
UserDetail: *user.Detail,
}
cache.CacheEntity(*fullJoinedUser, []string{fmt.Sprintf("user:%s", uid)}, "full_join_user_detail", uid)
return fullJoinedUser, nil
}
return nil, err
}

View File

@ -1,160 +0,0 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"strconv"
"time"
"github.com/samber/lo"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _WithdrawService struct {
l *zap.Logger
}
var WithdrawService = _WithdrawService{
l: logger.Named("Service", "Withdraw"),
}
func (_WithdrawService) ApplyWithdraw(reportId string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
var report = new(model.Report)
err := global.DB.NewSelect().Model(report).
Where("id = ?", reportId).
Scan(ctx)
if err != nil || report == nil {
return false, exceptions.NewNotFoundErrorFromError("指定报表未能找到", err)
}
if !report.Published {
return false, exceptions.NewImproperOperateError("指定报表尚未发布。")
}
var maxPublished time.Time
err = global.DB.NewSelect().Model((*model.Report)(nil)).
ColumnExpr("max(period)").
Where("park_id = ?", report.ParkId).
Where("published = ?", true).
Scan(ctx, &maxPublished)
if err != nil {
return false, exceptions.NewNotFoundError("未能找到匹配的系列报表。")
}
if !report.Period.Equal(maxPublished) {
return false, exceptions.NewImproperOperateError("申请撤回的报表必须是最新已发布的报表。")
}
report.Withdraw = model.REPORT_WITHDRAW_APPLIED
report.LastWithdrawAppliedAt = lo.ToPtr(time.Now())
_, err = global.DB.NewUpdate().Model(report).
WherePK().
Column("withdraw", "last_withdraw_applied_at").
Exec(ctx)
if err != nil {
return false, err
}
cache.AbolishRelation("withdraw_stat")
cache.AbolishRelation(fmt.Sprintf("report:%s", reportId))
cache.AbolishRelation(fmt.Sprintf("publicity:%s", reportId))
return true, nil
}
func (_WithdrawService) FetchPagedWithdrawApplies(page int, keyword string) ([]model.JoinedReportForWithdraw, int64, error) {
var (
conditions = make([]string, 0)
reports = make([]model.Report, 0)
cond = global.DB.NewSelect().Model(&reports).
Relation("Park").Relation("Park.Enterprise")
)
conditions = append(conditions, strconv.Itoa(int(model.REPORT_WITHDRAW_APPLIED)), strconv.Itoa(page))
cond = cond.Where("r.withdraw = ?", model.REPORT_WITHDRAW_APPLIED)
if len(keyword) > 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("p.name like ?", keywordCond).
WhereOr("p.abbr like ?", keywordCond).
WhereOr("d.name like ?", keywordCond).
WhereOr("d.abbr like ?", keywordCond)
})
conditions = append(conditions, keyword)
}
if cachedTotal, err := cache.RetreiveCount("join_report_for_withdraw", conditions...); cachedTotal != -1 && err == nil {
if cachedReports, _ := cache.RetreiveSearch[[]model.JoinedReportForWithdraw]("join_user_detail", conditions...); cachedReports != nil {
return *cachedReports, cachedTotal, err
}
}
ctx, cancel := global.TimeoutContext()
defer cancel()
startItem := (page - 1) * config.ServiceSettings.ItemsPageSize
total, err := cond.Limit(config.ServiceSettings.ItemsPageSize).
Offset(startItem).
ScanAndCount(ctx)
var (
joinedReports = make([]model.JoinedReportForWithdraw, 0)
relations = []string{"report", "park"}
)
for _, r := range reports {
joinedReports = append(joinedReports, model.JoinedReportForWithdraw{
Report: r,
Park: model.FromPark(*r.Park),
User: model.FromUserDetail(*r.Park.Enterprise),
})
relations = append(relations, fmt.Sprintf("report:%s", r.Id), fmt.Sprintf("publicity:%s", r.Id))
}
cache.CacheCount(relations, "join_report_for_withdraw", int64(total), conditions...)
cache.CacheSearch(joinedReports, relations, "join_report_for_withdraw", conditions...)
return joinedReports, int64(total), err
}
func (_WithdrawService) AuditWithdraw(reportId string, granted bool) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
var report = new(model.Report)
err := global.DB.NewSelect().Model(report).
Where("id = ?", reportId).
Scan(ctx)
if err != nil {
return exceptions.NewNotFoundErrorFromError("指定公示报表未找到。", err)
}
report.Withdraw = lo.If(granted, model.REPORT_WITHDRAW_GRANTED).Else(model.REPORT_WITHDRAW_DENIED)
report.LastWithdrawAuditAt = lo.ToPtr(time.Now())
if granted {
report.Published = false
}
_, err = global.DB.NewUpdate().Model(report).
WherePK().
Column("withdraw", "last_withdraw_audit_at", "published").
Exec(ctx)
if err == nil {
cache.AbolishRelation("withdraw_stat")
cache.AbolishRelation(fmt.Sprintf("report:%s", reportId))
}
return err
}
func (_WithdrawService) AuditWaits() (int64, error) {
if cachedWaits, err := cache.RetreiveCount("withdraw_waits"); cachedWaits != -1 && err == nil {
return cachedWaits, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
total, err := global.DB.NewSelect().Model((*model.Report)(nil)).
Where("withdraw = ?", model.REPORT_WITHDRAW_APPLIED).
Count(ctx)
if err == nil {
cache.CacheCount([]string{"withdraw_stat"}, "withdraw_waits", int64(total))
}
return int64(total), err
}