diff --git a/controller/withdraw.go b/controller/withdraw.go new file mode 100644 index 0000000..9bdcbbd --- /dev/null +++ b/controller/withdraw.go @@ -0,0 +1,86 @@ +package controller + +import ( + "electricity_bill_calc/exceptions" + "electricity_bill_calc/response" + "electricity_bill_calc/security" + "electricity_bill_calc/service" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +func InitializeWithdrawController(router *gin.Engine) { + router.DELETE("/publicity/:pid", security.EnterpriseAuthorize, applyReportWithdraw) + router.GET("/withdraws", security.OPSAuthorize, fetchWithdrawsWaitingAutdit) + router.PUT("/withdraw/:rid", security.OPSAuthorize, auditWithdraw) +} + +func applyReportWithdraw(c *gin.Context) { + result := response.NewResult(c) + requestReportId := c.Param("pid") + if !ensureReportBelongs(c, result, requestReportId) { + return + } + deleted, err := service.WithdrawService.ApplyWithdraw(requestReportId) + if err != nil { + if nfErr, ok := err.(exceptions.NotFoundError); ok { + result.NotFound(nfErr.Error()) + return + } else if ioErr, ok := err.(exceptions.ImproperOperateError); ok { + result.NotAccept(ioErr.Error()) + return + } else { + result.Error(http.StatusInternalServerError, err.Error()) + return + } + } + if !deleted { + result.Error(http.StatusInternalServerError, "未能完成公示报表的申请撤回操作。") + return + } + result.Success("指定的公示报表已经申请撤回。") +} + +func fetchWithdrawsWaitingAutdit(c *gin.Context) { + result := response.NewResult(c) + keyword := c.DefaultQuery("keyword", "") + requestPage, err := strconv.Atoi(c.DefaultQuery("page", "1")) + if err != nil { + result.NotAccept("查询参数[page]格式不正确。") + return + } + reports, totalitems, err := service.WithdrawService.FetchPagedWithdrawApplies(requestPage, keyword) + if err != nil { + result.NotFound(err.Error()) + return + } + result.Json( + http.StatusOK, + "已经取得符合条件的等待审核的撤回申请。", + response.NewPagedResponse(requestPage, totalitems).ToMap(), + gin.H{"records": reports}, + ) +} + +type WithdrawAuditFormData struct { + Audit bool `json:"audit" form:"audit"` +} + +func auditWithdraw(c *gin.Context) { + result := response.NewResult(c) + requestReportId := c.Param("rid") + formData := new(WithdrawAuditFormData) + c.BindJSON(formData) + err := service.WithdrawService.AuditWithdraw(requestReportId, formData.Audit) + if err != nil { + if nfErr, ok := err.(exceptions.NotFoundError); ok { + result.NotFound(nfErr.Error()) + } else { + result.NotAccept(err.Error()) + } + return + } + result.Success("指定公示报表的撤回申请已经完成审核") +} diff --git a/exceptions/improper_operate.go b/exceptions/improper_operate.go new file mode 100644 index 0000000..4ac7626 --- /dev/null +++ b/exceptions/improper_operate.go @@ -0,0 +1,19 @@ +package exceptions + +import "fmt" + +type ImproperOperateError struct { + Message string + Arguments []string +} + +func NewImproperOperateError(msg string, arguments ...string) ImproperOperateError { + return ImproperOperateError{ + Message: msg, + Arguments: arguments, + } +} + +func (e ImproperOperateError) Error() string { + return fmt.Sprintf("Improper Operate, %s", e.Message) +} diff --git a/model/park.go b/model/park.go index d58ab44..2f53e9d 100644 --- a/model/park.go +++ b/model/park.go @@ -26,3 +26,23 @@ type Park struct { func (Park) TableName() string { return "park" } + +type ParkSimplified struct { + Id string `xorm:"varchar(120) pk not null" json:"id"` + UserId string `xorm:"varchar(120) not null" json:"userId"` + Name string `xorm:"varchar(70) not null" json:"name"` + Abbr *string `xorm:"varchar(50)" json:"abbr"` + Area decimal.NullDecimal `xorm:"numeric(14,2)" json:"area"` + TenementQuantity decimal.NullDecimal `xorm:"numeric(8,0)" json:"tenement"` + Capacity decimal.NullDecimal `xorm:"numeric(16,2)" json:"capacity"` + Category int8 `xorm:"smallint not null" json:"category"` + SubmeterType int8 `xorm:"'meter_04kv_type' smallint not null" json:"meter04kvType"` + Region *string `xorm:"varchar(10)" json:"region"` + Address *string `xorm:"varchar(120)" json:"address"` + Contact *string `xorm:"varchar(100)" json:"contact"` + Phone *string `xorm:"varchar(50)" json:"phone"` +} + +func (ParkSimplified) TableName() string { + return "park" +} diff --git a/model/report.go b/model/report.go index 93f4c59..aa87b0b 100644 --- a/model/report.go +++ b/model/report.go @@ -60,3 +60,29 @@ func (p *ParkNewestReport) AfterLoad() { p.Report = nil } } + +type ReportIndexSimplified struct { + Id string `xorm:"varchar(120) pk not null" json:"id"` + ParkId string `xorm:"varchar(120) not null" json:"parkId"` + Period time.Time `xorm:"date not null" json:"period" time_format:"simple_date" time_location:"shanghai"` + StepState Steps `xorm:"text not null json" json:"stepState"` + Published bool `xorm:"bool not null default false" json:"published"` + PublishedAt *time.Time `xorm:"timestampz" json:"publishedAt" time_format:"simple_datetime" time_location:"shanghai"` + Withdraw int8 `xorm:"smallint not null default 0" json:"withdraw"` + LastWithdrawAppliedAt *time.Time `xorm:"timestampz" json:"lastWithdrawAppliedAt" time_format:"simple_datetime" time_location:"shanghai"` + LastWithdrawAuditAt *time.Time `xorm:"timestampz" json:"lastWithdrawAuditAt" time_format:"simple_datetime" time_location:"shanghai"` +} + +func (ReportIndexSimplified) TableName() string { + return "report" +} + +type JoinedReportForWithdraw struct { + Report ReportIndexSimplified `xorm:"extends" json:"report"` + Park ParkSimplified `xorm:"extends" json:"park"` + User UserDetailSimplified `xorm:"extends" json:"user"` +} + +func (JoinedReportForWithdraw) TableName() string { + return "report" +} diff --git a/model/user_detail.go b/model/user_detail.go index 8db74e4..74e555a 100644 --- a/model/user_detail.go +++ b/model/user_detail.go @@ -44,3 +44,17 @@ type FullJoinedUserDetail struct { func (FullJoinedUserDetail) TableName() string { return "user_detail" } + +type UserDetailSimplified struct { + Id string `xorm:"varchar(120) pk not null" json:"-"` + Name *string `xorm:"varchar(100)" json:"name"` + Abbr *string `xorm:"varchar(50)" json:"abbr"` + Region *string `xorm:"varchar(10)" json:"region"` + Address *string `xorm:"varchar(120)" json:"address"` + Contact *string `xorm:"varchar(100)" json:"contact"` + Phone *string `xorm:"varchar(50)" json:"phone"` +} + +func (UserDetailSimplified) TableName() string { + return "user_detail" +} diff --git a/router/router.go b/router/router.go index 1113ec6..f847ac6 100644 --- a/router/router.go +++ b/router/router.go @@ -22,6 +22,7 @@ func Router() *gin.Engine { controller.InitializeMaintenanceFeeController(router) controller.InitializeMeter04kVController(router) controller.InitializeReportController(router) + controller.InitializeWithdrawController(router) return router } diff --git a/service/withdraw.go b/service/withdraw.go new file mode 100644 index 0000000..a8e6e2f --- /dev/null +++ b/service/withdraw.go @@ -0,0 +1,111 @@ +package service + +import ( + "electricity_bill_calc/config" + "electricity_bill_calc/exceptions" + "electricity_bill_calc/global" + "electricity_bill_calc/model" + "time" + + "github.com/samber/lo" + "xorm.io/builder" +) + +type _WithdrawService struct{} + +var WithdrawService _WithdrawService + +func (_WithdrawService) ApplyWithdraw(reportId string) (bool, error) { + var report = new(model.Report) + has, err := global.DBConn.ID(reportId).Get(report) + if err != nil { + return false, err + } + if !has { + return false, exceptions.NewNotFoundError("指定报表未能找到") + } + if !report.Published { + return false, exceptions.NewImproperOperateError("指定报表尚未发布。") + } + reports := make([]model.Report, 0) + err = global.DBConn. + Where(builder.Eq{"park_id": report.ParkId}). + Find(&reports) + if err != nil { + return false, exceptions.NewNotFoundError("未能找到匹配的系列报表。") + } + 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, + ) + if !report.Period.Equal(*maxPublished) { + return false, exceptions.NewImproperOperateError("申请撤回的报表必须是最新已发布的报表。") + } + + report.Withdraw = model.REPORT_WITHDRAW_APPLIED + report.LastWithdrawAppliedAt = lo.ToPtr(time.Now()) + _, err = global.DBConn.ID(report.Id).Cols("withdraw", "last_withdraw_applied_at").Update(report) + if err != nil { + return false, err + } + return true, nil +} + +func (_WithdrawService) FetchPagedWithdrawApplies(page int, keyword string) ([]model.JoinedReportForWithdraw, int64, error) { + cond := builder.NewCond() + cond = cond.And(builder.Eq{"r.withdraw": model.REPORT_WITHDRAW_APPLIED}) + if len(keyword) > 0 { + cond = cond.And( + builder.Like{"p.name", keyword}. + Or( + builder.Like{"p.abbr", keyword}, + builder.Like{"u.name", keyword}, + builder.Like{"u.abbr", keyword}, + ), + ) + } + var reports = make([]model.JoinedReportForWithdraw, 0) + total, err := global.DBConn. + Table(new(model.JoinedReportForWithdraw)).Alias("r"). + Join("INNER", []string{"park", "p"}, "r.park_id=p.id"). + Join("INNER", []string{"user_detail", "u"}, "p.user_id=u.id"). + Where(cond). + Count() + if err != nil { + return nil, -1, err + } + startItem := (page - 1) * config.ServiceSettings.ItemsPageSize + err = global.DBConn. + Alias("r"). + Join("INNER", []string{"park", "p"}, "r.park_id=p.id"). + Join("INNER", []string{"user_detail", "u"}, "p.user_id=u.id"). + Where(cond). + Limit(config.ServiceSettings.ItemsPageSize, startItem). + Find(&reports) + return reports, total, err +} +func (_WithdrawService) AuditWithdraw(reportId string, granted bool) error { + var report = new(model.Report) + has, err := global.DBConn.ID(reportId).NoAutoCondition().Get(report) + if err != nil { + return err + } + if !has { + return exceptions.NewNotFoundError("指定公示报表未找到。") + } + 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.DBConn.ID(report.Id).Cols("withdraw", "last_withdraw_audit_at", "published").Update(report) + return err +}