forked from free-lancers/electricity_bill_calc_service
合并分支
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"electricity_bill_calc/model"
|
||||
"electricity_bill_calc/tools"
|
||||
"electricity_bill_calc/types"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -19,11 +19,10 @@ import (
|
||||
type ExcelTemplateGenerator interface {
|
||||
Close()
|
||||
WriteTo(w io.Writer) (int64, error)
|
||||
WriteMeterData(meters []model.EndUserDetail) error
|
||||
}
|
||||
|
||||
type ColumnRecognizer struct {
|
||||
Pattern []string
|
||||
Pattern [][]string
|
||||
Tag string
|
||||
MatchIndex int
|
||||
MustFill bool
|
||||
@@ -45,7 +44,7 @@ type ExcelAnalysisError struct {
|
||||
Err AnalysisError `json:"error"`
|
||||
}
|
||||
|
||||
func NewColumnRecognizer(tag string, patterns ...string) ColumnRecognizer {
|
||||
func NewColumnRecognizer(tag string, patterns ...[]string) ColumnRecognizer {
|
||||
return ColumnRecognizer{
|
||||
Pattern: patterns,
|
||||
Tag: tag,
|
||||
@@ -67,9 +66,17 @@ func (e ExcelAnalysisError) Error() string {
|
||||
|
||||
func (r *ColumnRecognizer) Recognize(cellValue string) bool {
|
||||
matches := make([]bool, 0)
|
||||
for _, p := range r.Pattern {
|
||||
matches = append(matches, strings.Contains(cellValue, p))
|
||||
for _, pG := range r.Pattern {
|
||||
groupMatch := make([]bool, 0)
|
||||
for _, p := range pG {
|
||||
groupMatch = append(groupMatch, strings.Contains(cellValue, p))
|
||||
}
|
||||
// 这句表示在每一个匹配组中,只要有一个匹配项,就算匹配成功
|
||||
matches = append(matches, lo.Reduce(groupMatch, func(acc, elem bool, index int) bool {
|
||||
return acc || elem
|
||||
}, false))
|
||||
}
|
||||
// 这句表示在尊有的匹配组中,必须全部的匹配组都完成匹配,才算匹配成功
|
||||
return lo.Reduce(matches, func(acc, elem bool, index int) bool {
|
||||
return acc && elem
|
||||
}, true)
|
||||
@@ -189,6 +196,54 @@ func (a *ExcelAnalyzer[T]) Analysis(bean T) ([]T, []ExcelAnalysisError) {
|
||||
} else {
|
||||
actualField.SetBool(false)
|
||||
}
|
||||
case "types.Date":
|
||||
if len(matchValue) == 0 {
|
||||
actualField.Set(reflect.ValueOf(types.NewEmptyDate()))
|
||||
} else {
|
||||
v, err := types.ParseDate(matchValue)
|
||||
if err != nil {
|
||||
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期格式。%w", err)}})
|
||||
actualField.Set(reflect.ValueOf(types.NewEmptyDate()))
|
||||
} else {
|
||||
actualField.Set(reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
case "*types.Date":
|
||||
if len(matchValue) == 0 {
|
||||
actualField.Set(reflect.ValueOf(nil))
|
||||
} else {
|
||||
v, err := types.ParseDate(matchValue)
|
||||
if err != nil {
|
||||
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期格式。%w", err)}})
|
||||
actualField.Set(reflect.ValueOf(nil))
|
||||
} else {
|
||||
actualField.Set(reflect.ValueOf(&v))
|
||||
}
|
||||
}
|
||||
case "types.DateTime":
|
||||
if len(matchValue) == 0 {
|
||||
actualField.Set(reflect.ValueOf(types.NewEmptyDateTime()))
|
||||
} else {
|
||||
v, err := types.ParseDateTime(matchValue)
|
||||
if err != nil {
|
||||
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期时间格式。%w", err)}})
|
||||
actualField.Set(reflect.ValueOf(types.NewEmptyDateTime()))
|
||||
} else {
|
||||
actualField.Set(reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
case "*types.DateTime":
|
||||
if len(matchValue) == 0 {
|
||||
actualField.Set(reflect.ValueOf(nil))
|
||||
} else {
|
||||
v, err := types.ParseDateTime(matchValue)
|
||||
if err != nil {
|
||||
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期时间格式。%w", err)}})
|
||||
actualField.Set(reflect.ValueOf(nil))
|
||||
} else {
|
||||
actualField.Set(reflect.ValueOf(&v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,21 +1,139 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"electricity_bill_calc/logger"
|
||||
"electricity_bill_calc/model"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var meter04kVExcelRecognizers = []*ColumnRecognizer{
|
||||
{Pattern: []string{"表号"}, Tag: "code", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: []string{"户名"}, Tag: "name", MatchIndex: -1},
|
||||
{Pattern: []string{"户址"}, Tag: "address", MatchIndex: -1},
|
||||
{Pattern: []string{"联系人"}, Tag: "contact", MatchIndex: -1},
|
||||
{Pattern: []string{"电话"}, Tag: "phone", MatchIndex: -1},
|
||||
{Pattern: []string{"倍率"}, Tag: "ratio", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: []string{"序号"}, Tag: "seq", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: []string{"公用设备"}, Tag: "public", MatchIndex: -1, MustFill: true},
|
||||
var meterArchiveRecognizers = []*ColumnRecognizer{
|
||||
{Pattern: [][]string{{"表号"}}, Tag: "code", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: [][]string{{"表址", "地址", "户址"}}, Tag: "address", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"类型"}}, Tag: "meterType", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: [][]string{{"建筑"}}, Tag: "building", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"楼层"}}, Tag: "onFloor", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"面积"}}, Tag: "area", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"倍率"}}, Tag: "ratio", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: [][]string{{"序号"}}, Tag: "seq", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: [][]string{{"抄表"}, {"时间", "日期"}}, Tag: "readAt", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: [][]string{{"有功", "表底", "底数"}, {"总"}}, Tag: "overall", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: [][]string{{"有功", "表底", "底数"}, {"尖"}}, Tag: "critical", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"有功", "表底", "底数"}, {"峰"}}, Tag: "peak", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"有功", "表底", "底数"}, {"平"}}, Tag: "flat", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"有功", "表底", "底数"}, {"谷"}}, Tag: "valley", MatchIndex: -1},
|
||||
}
|
||||
|
||||
func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.Meter04KV], error) {
|
||||
return NewExcelAnalyzer[model.Meter04KV](file, meter04kVExcelRecognizers)
|
||||
func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.MeterImportRow], error) {
|
||||
return NewExcelAnalyzer[model.MeterImportRow](file, meterArchiveRecognizers)
|
||||
}
|
||||
|
||||
type MeterArchiveExcelTemplateGenerator struct {
|
||||
file *excelize.File
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
func NewMeterArchiveExcelTemplateGenerator() *MeterArchiveExcelTemplateGenerator {
|
||||
return &MeterArchiveExcelTemplateGenerator{
|
||||
file: excelize.NewFile(),
|
||||
log: logger.Named("Excel", "MeterArchive"),
|
||||
}
|
||||
}
|
||||
|
||||
func (MeterArchiveExcelTemplateGenerator) titles() *[]interface{} {
|
||||
return &[]interface{}{
|
||||
"序号",
|
||||
"表址",
|
||||
"表号",
|
||||
"表计类型",
|
||||
"倍率",
|
||||
"所在建筑",
|
||||
"所在楼层",
|
||||
"辖盖面积",
|
||||
"抄表时间",
|
||||
"有功(总)",
|
||||
"有功(尖)",
|
||||
"有功(峰)",
|
||||
"有功(平)",
|
||||
"有功(谷)",
|
||||
}
|
||||
}
|
||||
|
||||
func (g *MeterArchiveExcelTemplateGenerator) Close() {
|
||||
g.file.Close()
|
||||
}
|
||||
|
||||
func (g MeterArchiveExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) {
|
||||
return g.file.WriteTo(w)
|
||||
}
|
||||
|
||||
func (g *MeterArchiveExcelTemplateGenerator) WriteTemplateData(buildings []*model.ParkBuilding) error {
|
||||
var err error
|
||||
defaultSheet := g.file.GetSheetName(0)
|
||||
g.log.Debug("选定默认输出表格", zap.String("sheet", defaultSheet))
|
||||
err = g.file.SetColWidth(defaultSheet, "B", "I", 20)
|
||||
if err != nil {
|
||||
g.log.Error("未能设定长型列宽。", zap.Error(err))
|
||||
return fmt.Errorf("未能设定长型列宽,%w", err)
|
||||
}
|
||||
err = g.file.SetColWidth(defaultSheet, "J", "N", 15)
|
||||
if err != nil {
|
||||
g.log.Error("未能设定短型列宽。", zap.Error(err))
|
||||
return fmt.Errorf("未能设定短型列宽,%w", err)
|
||||
}
|
||||
err = g.file.SetSheetRow(defaultSheet, "A1", g.titles())
|
||||
if err != nil {
|
||||
g.log.Error("未能输出模板标题。", zap.Error(err))
|
||||
return fmt.Errorf("未能输出模板标题,%w", err)
|
||||
}
|
||||
err = g.file.SetRowHeight(defaultSheet, 1, 20)
|
||||
if err != nil {
|
||||
g.log.Error("未能设定标题行高度。", zap.Error(err))
|
||||
return fmt.Errorf("未能设定标题行高度,%w", err)
|
||||
}
|
||||
|
||||
dateTimeExp := "yyyy-mm-dd hh:mm"
|
||||
dateTimeColStyle, err := g.file.NewStyle(&excelize.Style{
|
||||
CustomNumFmt: &dateTimeExp,
|
||||
})
|
||||
if err != nil {
|
||||
g.log.Error("未能创建日期时间格式。", zap.Error(err))
|
||||
return fmt.Errorf("未能创建日期时间格式,%w", err)
|
||||
}
|
||||
g.file.SetCellStyle(defaultSheet, "I2", "I1048576", dateTimeColStyle)
|
||||
|
||||
numExp := "0.0000"
|
||||
numColStyle, err := g.file.NewStyle(&excelize.Style{
|
||||
CustomNumFmt: &numExp,
|
||||
})
|
||||
if err != nil {
|
||||
g.log.Error("未能创建抄表数字格式。", zap.Error(err))
|
||||
return fmt.Errorf("未能创建抄表数字格式,%w", err)
|
||||
}
|
||||
g.file.SetCellStyle(defaultSheet, "J2", "N1048576", numColStyle)
|
||||
|
||||
meterInstallationTypeValidation := excelize.NewDataValidation(false)
|
||||
meterInstallationTypeValidation.SetDropList([]string{"商户表", "公共表", "楼道表"})
|
||||
meterInstallationTypeValidation.Sqref = "D2:D1048576"
|
||||
err = g.file.AddDataValidation(defaultSheet, meterInstallationTypeValidation)
|
||||
if err != nil {
|
||||
g.log.Error("未能设定表计类型选择器。", zap.Error(err))
|
||||
return fmt.Errorf("未能设定表计类型选择器,%w", err)
|
||||
}
|
||||
buildingValidation := excelize.NewDataValidation(true)
|
||||
buildingNames := lo.Map(buildings, func(b *model.ParkBuilding, _ int) string {
|
||||
return b.Name
|
||||
})
|
||||
buildingValidation.SetDropList(buildingNames)
|
||||
buildingValidation.Sqref = "F2:F1048576"
|
||||
err = g.file.AddDataValidation(defaultSheet, buildingValidation)
|
||||
if err != nil {
|
||||
g.log.Error("未能设定所在建筑选择器。", zap.Error(err))
|
||||
return fmt.Errorf("未能设定所在建筑选择器,%w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
131
excel/meter_reading.go
Normal file
131
excel/meter_reading.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"electricity_bill_calc/logger"
|
||||
"electricity_bill_calc/model"
|
||||
"electricity_bill_calc/tools"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var meterReadingsRecognizers = []*ColumnRecognizer{
|
||||
{Pattern: [][]string{{"表", "表计"}, {"编号"}}, Tag: "code", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: [][]string{{"抄表", "结束"}, {"时间", "日期"}}, Tag: "readAt", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: [][]string{{"用电", "有功", "表底", "底数"}, {"总", "量"}}, Tag: "overall", MatchIndex: -1, MustFill: true},
|
||||
{Pattern: [][]string{{"有功", "表底", "底数"}, {"尖"}}, Tag: "critical", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"有功", "表底", "底数"}, {"峰"}}, Tag: "peak", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"有功", "表底", "底数"}, {"谷"}}, Tag: "valley", MatchIndex: -1},
|
||||
}
|
||||
|
||||
func NewMeterReadingsExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.ReadingImportRow], error) {
|
||||
return NewExcelAnalyzer[model.ReadingImportRow](file, meterReadingsRecognizers)
|
||||
}
|
||||
|
||||
type MeterReadingsExcelTemplateGenerator struct {
|
||||
file *excelize.File
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
func NewMeterReadingsExcelTemplateGenerator() *MeterReadingsExcelTemplateGenerator {
|
||||
return &MeterReadingsExcelTemplateGenerator{
|
||||
file: excelize.NewFile(),
|
||||
log: logger.Named("Excel", "MeterReadings"),
|
||||
}
|
||||
}
|
||||
|
||||
func (MeterReadingsExcelTemplateGenerator) titles() *[]interface{} {
|
||||
return &[]interface{}{
|
||||
"抄表序号",
|
||||
"抄表时间",
|
||||
"表计编号",
|
||||
"表计名称",
|
||||
"商户名称",
|
||||
"倍率",
|
||||
"有功(总)",
|
||||
"有功(尖)",
|
||||
"有功(峰)",
|
||||
"有功(谷)",
|
||||
}
|
||||
}
|
||||
|
||||
func (g MeterReadingsExcelTemplateGenerator) Close() {
|
||||
g.file.Close()
|
||||
}
|
||||
|
||||
func (g MeterReadingsExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) {
|
||||
return g.file.WriteTo(w)
|
||||
}
|
||||
|
||||
func (g MeterReadingsExcelTemplateGenerator) WriteTemplateData(meters []*model.SimpleMeterDocument) error {
|
||||
var err error
|
||||
defaultSheet := g.file.GetSheetName(0)
|
||||
g.log.Debug("选定默认输出表格", zap.String("sheet", defaultSheet))
|
||||
err = g.file.SetColWidth(defaultSheet, "A", "E", 30)
|
||||
if err != nil {
|
||||
g.log.Error("未能设定长型单元格的宽度。", zap.Error(err))
|
||||
return fmt.Errorf("未能设定长型单元格的宽度,%w", err)
|
||||
}
|
||||
err = g.file.SetColWidth(defaultSheet, "F", "F", 10)
|
||||
if err != nil {
|
||||
g.log.Error("未能设定倍率单元格的宽度。", zap.Error(err))
|
||||
return fmt.Errorf("未能设定倍率单元格的宽度,%w", err)
|
||||
}
|
||||
err = g.file.SetColWidth(defaultSheet, "G", "J", 20)
|
||||
if err != nil {
|
||||
g.log.Error("未能设定短型单元格的宽度。", zap.Error(err))
|
||||
return fmt.Errorf("未能设定短型单元格的宽度,%w", err)
|
||||
}
|
||||
err = g.file.SetSheetRow(defaultSheet, "A1", g.titles())
|
||||
if err != nil {
|
||||
g.log.Error("未能输出模板标题。", zap.Error(err))
|
||||
return fmt.Errorf("未能输出模板标题,%w", err)
|
||||
}
|
||||
err = g.file.SetRowHeight(defaultSheet, 1, 30)
|
||||
if err != nil {
|
||||
g.log.Error("未能设定标题行的高度。", zap.Error(err))
|
||||
return fmt.Errorf("未能设定标题行的高度,%w", err)
|
||||
}
|
||||
|
||||
dateTimeExp := "yyyy-mm-dd hh:mm"
|
||||
dateTimeColStyle, err := g.file.NewStyle(&excelize.Style{
|
||||
CustomNumFmt: &dateTimeExp,
|
||||
})
|
||||
if err != nil {
|
||||
g.log.Error("未能创建日期时间格式。", zap.Error(err))
|
||||
return fmt.Errorf("未能创建日期时间格式,%w", err)
|
||||
}
|
||||
endCellCoord, _ := excelize.CoordinatesToCellName(2, len(meters)+1)
|
||||
g.file.SetCellStyle(defaultSheet, "B2", endCellCoord, dateTimeColStyle)
|
||||
|
||||
numExp := "0.0000"
|
||||
numColStyle, err := g.file.NewStyle(&excelize.Style{
|
||||
CustomNumFmt: &numExp,
|
||||
})
|
||||
if err != nil {
|
||||
g.log.Error("未能创建抄表数字格式。", zap.Error(err))
|
||||
return fmt.Errorf("未能创建抄表数字格式,%w", err)
|
||||
}
|
||||
endCellCoord, _ = excelize.CoordinatesToCellName(9, len(meters)+1)
|
||||
g.file.SetCellStyle(defaultSheet, "F2", endCellCoord, numColStyle)
|
||||
|
||||
for i, meter := range meters {
|
||||
cellCoord, _ := excelize.CoordinatesToCellName(1, i+2)
|
||||
ratio, _ := meter.Ratio.Float64()
|
||||
if err := g.file.SetSheetRow(defaultSheet, cellCoord, &[]interface{}{
|
||||
meter.Seq,
|
||||
"",
|
||||
meter.Code,
|
||||
tools.DefaultTo(meter.Address, ""),
|
||||
tools.DefaultTo(meter.TenementName, ""),
|
||||
ratio,
|
||||
}); err != nil {
|
||||
g.log.Error("向模板写入数据出现错误。", zap.Error(err))
|
||||
return fmt.Errorf("向模板写入数据出现错误,%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
27
excel/tenement.go
Normal file
27
excel/tenement.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"electricity_bill_calc/model"
|
||||
"io"
|
||||
)
|
||||
|
||||
var tenementRecognizers = []*ColumnRecognizer{
|
||||
{Pattern: [][]string{{"商户全称"}}, Tag: "fullName", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"联系地址"}}, Tag: "address", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"入驻时间"}}, Tag: "movedInAt", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"商铺名称"}}, Tag: "shortName", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"联系人"}}, Tag: "contactName", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"电话"}}, Tag: "contactPhone", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"USCI"}}, Tag: "usci", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"开票地址"}}, Tag: "invoiceAddress", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"账号"}}, Tag: "account", MatchIndex: -1},
|
||||
{Pattern: [][]string{{"开户行"}}, Tag: "bank", MatchIndex: -1},
|
||||
}
|
||||
|
||||
func NewTenementExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.TenementImportRow], error) {
|
||||
return NewExcelAnalyzer[model.TenementImportRow](file, tenementRecognizers)
|
||||
}
|
||||
|
||||
//func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.MeterImportRow], error) {
|
||||
// return NewExcelAnalyzer[model.MeterImportRow](file, meterArchiveRecognizers)
|
||||
//}
|
Reference in New Issue
Block a user