|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- package svc
-
- import (
- "applet/app/e"
- "applet/app/enum"
- "applet/app/md"
- "applet/app/utils"
- "applet/app/utils/cache"
- db "code.fnuoos.com/zhimeng/model.git/src"
- implement2 "code.fnuoos.com/zhimeng/model.git/src/implement"
- "code.fnuoos.com/zhimeng/model.git/src/super/implement"
- "code.fnuoos.com/zhimeng/model.git/src/super/model"
- "errors"
- "fmt"
- "github.com/gin-gonic/gin"
- "github.com/jinzhu/copier"
- "time"
- )
-
- func DataCenterIncomeDataListForOpen(c *gin.Context, req md.DataCenterGenerateDataForOpenReq) md.DataCenterIncomeDataRes {
- engine := db.Db
- NewGenerateWxAdDataDb := implement.NewGenerateWxAdDataDb(engine)
- mediumId := req.MediumId
- MediumList, total, _ := NewGenerateWxAdDataDb.FindGenerateWxAdDataListMedium(c.GetString("mid"), "", mediumId, "", req.StartTime, req.EndTime, utils.StrToInt(req.Page), utils.StrToInt(req.Limit))
- data := make([]md.DataCenterIncomeDataData, 0)
- if len(MediumList) > 0 {
- for _, v := range MediumList {
- var tmp = md.DataCenterIncomeDataData{
- AgreementSharing: utils.Float64ToStr(float64(v.AgreementSharing) / 100),
- AgentRevenue: utils.Float64ToStr(float64(v.AgentRevenue) / 100),
- Id: utils.IntToStr(v.Id),
- ExposureCount: utils.IntToStr(v.ExposureCount),
- ClickCount: utils.IntToStr(v.ClickCount),
- ClickRate: v.ClickRate,
- Ecpm: utils.Float64ToStr(utils.StrToFloat64(v.Ecpm) / 100),
- Date: v.Date,
- MediaRevenue: utils.Float64ToStr(float64(v.MediaRevenue) / 100),
- SettleAmount: utils.Float64ToStr(float64(v.MediaRevenue+v.AgentRevenue+v.AgreementSharing) / 100),
- }
- tmpApplet := GetAppletInfo(c, v.AppId)
- if tmpApplet["platform"] != "" {
- tmp.Platform = tmpApplet["platform"]
- }
- if tmpApplet["name"] != "" {
- tmp.Name = tmpApplet["name"]
- }
- tmpSlot := GetSlotInfo(c, v.SlotId)
- if tmpSlot["state"] != "" {
- tmp.State = tmpSlot["state"]
- }
- if tmpSlot["name"] != "" {
- tmp.AdvName = tmpSlot["name"]
- }
- data = append(data, tmp)
- }
- }
- res := md.DataCenterIncomeDataRes{
- List: data,
- Total: total,
- State: md.AdState,
- Platform: md.AdPlatform,
- }
- return res
- }
-
- func DataCenterIncomeDataList(c *gin.Context, req md.DataCenterGenerateDataReq) md.DataCenterIncomeDataRes {
- engine := db.Db
- NewGenerateWxAdDataDb := implement.NewGenerateWxAdDataDb(engine)
- user := GetUser(c)
- appId := GetAppletId(c, req.Name, req.Platform, req.AppId)
- mediumId := GetMediumIdStr(c, user.AdmId, "", "")
- slotId := GetSlotId(c, req.State)
- MediumList, total, _ := NewGenerateWxAdDataDb.FindGenerateWxAdDataListMedium(c.GetString("mid"), appId, mediumId, slotId, req.StartTime, req.EndTime, utils.StrToInt(req.Page), utils.StrToInt(req.Limit))
- data := make([]md.DataCenterIncomeDataData, 0)
- if len(MediumList) > 0 {
- for _, v := range MediumList {
- var tmp = md.DataCenterIncomeDataData{
- AgreementSharing: utils.Float64ToStr(float64(v.AgreementSharing) / 100),
- AgentRevenue: utils.Float64ToStr(float64(v.AgentRevenue) / 100),
- Id: utils.IntToStr(v.Id),
- ExposureCount: utils.IntToStr(v.ExposureCount),
- ClickCount: utils.IntToStr(v.ClickCount),
- ClickRate: v.ClickRate,
- Ecpm: utils.Float64ToStr(utils.StrToFloat64(v.Ecpm) / 100),
- Date: v.Date,
- MediaRevenue: utils.Float64ToStr(float64(v.MediaRevenue) / 100),
- SettleAmount: utils.Float64ToStr(float64(v.MediaRevenue+v.AgentRevenue+v.AgreementSharing) / 100),
- }
- tmpApplet := GetAppletInfo(c, v.AppId)
- if tmpApplet["platform"] != "" {
- tmp.Platform = tmpApplet["platform"]
- }
- if tmpApplet["name"] != "" {
- tmp.Name = tmpApplet["name"]
- }
- tmpSlot := GetSlotInfo(c, v.SlotId)
- if tmpSlot["state"] != "" {
- tmp.State = tmpSlot["state"]
- }
- if tmpSlot["name"] != "" {
- tmp.AdvName = tmpSlot["name"]
- }
- data = append(data, tmp)
- }
- }
- res := md.DataCenterIncomeDataRes{
- List: data,
- Total: total,
- State: md.AdState,
- Platform: md.AdPlatform,
- }
- return res
- }
-
- func DataCenterIncomeDataListOutput(c *gin.Context, req md.DataCenterGenerateDataReq) {
- engine := db.Db
- NewGenerateWxAdDataDb := implement.NewGenerateWxAdDataDb(engine)
- user := GetUser(c)
- appId := GetAppletId(c, req.Name, req.Platform, req.AppId)
- mediumId := GetMediumIdStr(c, user.AdmId, "", "")
- slotId := GetSlotId(c, req.State)
- req.Limit = "3000"
- MediumList, _ := NewGenerateWxAdDataDb.FindGenerateWxAdDataListMediumAll(c.GetString("mid"), appId, mediumId, slotId, req.StartTime, req.EndTime, utils.StrToInt(req.Page), utils.StrToInt(req.Limit))
- data := map[string]string{
- "A1": "名称",
- "B1": "日期",
- "C1": "广告位",
- "D1": "曝光量",
- "E1": "点击率(%)",
- "F1": "点击量",
- "G1": "结算平台",
- "H1": "媒体-ECPM",
- "I1": "媒体收益(元)",
- "J1": "代理收益(元)",
- "K1": "平台收益(元)",
- }
- name := "运营报表第" + req.Page + "页"
-
- if len(MediumList) > 0 {
- for k, v := range MediumList {
- var tmp = md.DataCenterIncomeDataData{
- AgreementSharing: utils.Float64ToStr(float64(v.AgreementSharing) / 100),
- AgentRevenue: utils.Float64ToStr(float64(v.AgentRevenue) / 100),
- Id: utils.IntToStr(v.Id),
- ExposureCount: utils.IntToStr(v.ExposureCount),
- ClickCount: utils.IntToStr(v.ClickCount),
- ClickRate: v.ClickRate,
- Ecpm: utils.Float64ToStr(utils.StrToFloat64(v.Ecpm) / 100),
- Date: v.Date,
- MediaRevenue: utils.Float64ToStr(float64(v.MediaRevenue) / 100),
- SettleAmount: utils.Float64ToStr(float64(v.MediaRevenue+v.AgentRevenue+v.AgreementSharing) / 100),
- }
- tmpApplet := GetAppletInfo(c, v.AppId)
- if tmpApplet["platform"] != "" {
- tmp.Platform = md.AdPlatformMap[tmpApplet["platform"]]
- }
- if tmpApplet["name"] != "" {
- tmp.Name = tmpApplet["name"]
- }
- tmpSlot := GetSlotInfo(c, v.SlotId)
- if tmpSlot["state"] != "" {
- tmp.State = tmpSlot["state"]
- }
- if tmpSlot["name"] != "" {
- tmp.AdvName = tmpSlot["name"]
- }
- i := utils.IntToStr(k + 2)
- data["A"+i] = tmp.Name
- data["B"+i] = tmp.Date
- data["C"+i] = tmp.AdvName
- data["D"+i] = tmp.ExposureCount
- data["E"+i] = tmp.ClickRate
- data["F"+i] = tmp.ClickCount
- data["G"+i] = tmp.Platform
- data["H"+i] = tmp.Ecpm
- data["I"+i] = tmp.MediaRevenue
- data["J"+i] = tmp.AgentRevenue
- data["K"+i] = tmp.AgreementSharing
- }
- }
-
- file := utils.Output(c, name, data)
- filename := name + ".xlsx"
- r := map[string]string{
- "file": file,
- "filename": filename,
- }
- e.OutSuc(c, r, nil)
- return
- }
- func DataCenterIncomeDataDetail(c *gin.Context, req md.DataCenterGenerateDataCommReq) {
- NewGenerateWxAdDataDb := implement.NewGenerateWxAdDataDb(db.Db)
- data, _ := NewGenerateWxAdDataDb.GetGenerateWxAdData(utils.StrToInt(req.Id))
- if data == nil {
- e.OutErr(c, 400, e.NewErr(400, "记录不存在"))
- return
- }
- agentReward := make([]md.DataCenterGenerateDataDetailAgentRewardSecond, 0)
- NewGenerateWxAdDataWithAgentFlowDb := implement.NewGenerateWxAdDataWithAgentFlowDb(db.Db)
- agent, _ := NewGenerateWxAdDataWithAgentFlowDb.FindGenerateWxAdDataWithAgentFlowByStrategyId(data.Id)
- if agent != nil {
- for _, v := range *agent {
- tmp := md.DataCenterGenerateDataDetailAgentRewardSecond{
- Name: "",
- Account: "",
- AgentRevenue: utils.Float64ToStr(float64(v.AgentRevenue) / 100),
- AgentRevenueRate: utils.IntToStr(data.AgentRevenueRate),
- }
- tmpApplet := GetAgentInfo(c, v.AgentId)
- if tmpApplet["name"] != "" {
- tmp.Name = tmpApplet["name"]
- }
- if tmpApplet["account"] != "" {
- tmp.Account = tmpApplet["account"]
- }
- agentReward = append(agentReward, tmp)
- }
- }
- res := md.DataCenterIncomeDataDetail{
- MediaRevenue: utils.Float64ToStr(float64(data.MediaRevenue) / 100),
- AgreementSharing: utils.Float64ToStr(float64(data.AgreementSharing) / 100),
- AgentRevenue: utils.Float64ToStr(float64(data.AgentRevenue) / 100),
- DataSource: "手动同步",
- CreateAt: data.CreateAt,
- UpdateAt: data.UpdateAt,
- MediaRevenueRate: utils.IntToStr(data.MediaRevenueRate),
- AgentRevenueRate: utils.IntToStr(data.AgentRevenueRate),
- AgreementSharingRate: utils.IntToStr(data.AgreementSharingRate),
- AgentReward: agentReward,
- }
- tmpApplet := GetAppletInfo(c, data.AppId)
- if tmpApplet["platform"] != "" {
- res.Platform = tmpApplet["platform"]
- }
- NewAppletApplicationDb := implement2.NewAppletApplicationDb(MasterDb(c))
- app, _ := NewAppletApplicationDb.GetAppletApplicationListByAppid(data.AppId)
- tmp := GetMediumInfo(c, app.MediumId)
- if tmp["name"] != "" {
- res.MediumName = tmp["name"]
- if tmp["account"] != "" {
- res.MediumName += "(" + tmp["account"] + ")"
- }
- }
- e.OutSuc(c, res, nil)
- return
- }
-
- func DataCenterGenerateDataList(c *gin.Context, req md.DataCenterGenerateDataReq) md.DataCenterGenerateDataRes {
- engine := db.Db
- NewGenerateWxAdDataDb := implement.NewGenerateWxAdDataDb(engine)
- user := GetUser(c)
- appId := GetAppletId(c, req.Name, req.Platform, req.AppId)
- mediumId := GetMediumIdStr(c, user.AdmId, "", "")
- slotId := GetSlotId(c, req.State)
- MediumList, total, _ := NewGenerateWxAdDataDb.FindGenerateWxAdDataList(c.GetString("mid"), appId, mediumId, slotId, req.StartTime, req.EndTime, utils.StrToInt(req.Page), utils.StrToInt(req.Limit))
- data := make([]md.DataCenterGenerateDataData, 0)
- if len(MediumList) > 0 {
- NewOriginalWxAdDataDb := implement.NewOriginalWxAdDataDb(engine)
- for _, v := range MediumList {
- wxData, _ := NewOriginalWxAdDataDb.GetOriginalWxAdData(v.OriginalDataId)
- var tmp = md.DataCenterGenerateDataData{
- Id: utils.IntToStr(v.Id),
- ExposureCount: utils.IntToStr(v.ExposureCount),
- ClickCount: utils.IntToStr(v.ClickCount),
- ClickRate: v.ClickRate,
- Ecpm: utils.Float64ToStr(utils.StrToFloat64(v.Ecpm) / 100),
- IsGenerateReport: utils.IntToStr(v.IsGenerateReport),
- Date: v.Date,
- PlatformRetention: utils.Float64ToStr(float64(v.PlatformRetention) / 100),
- CommissionRetention: utils.Float64ToStr(float64(v.CommissionRetention) / 100),
- PriceAdjustmentRetention: utils.Float64ToStr(float64(v.PriceAdjustmentRetention) / 100),
- MediaRevenue: utils.Float64ToStr(float64(v.MediaRevenue) / 100),
- AgentRevenue: utils.Float64ToStr(float64(v.AgentRevenue) / 100),
- ExtraRevenue: utils.Float64ToStr(float64(v.ExtraRevenue) / 100),
- AgreementSharing: utils.Float64ToStr(float64(v.AgreementSharing) / 100),
- AgreementSharingTotal: utils.Float64ToStr(float64(v.AgreementSharingTotal) / 100),
- }
- if wxData != nil {
- tmp.OldClickRate = wxData.ClickRate
- tmp.OldClickCount = utils.IntToStr(wxData.ClickCount)
- tmp.OldEcpm = utils.Float64ToStr(utils.StrToFloat64(wxData.Ecpm) / 100)
- tmp.OldExposureCount = utils.IntToStr(wxData.ExposureCount)
- }
- tmpApplet := GetAppletInfo(c, v.AppId)
- if tmpApplet["platform"] != "" {
- tmp.Platform = tmpApplet["platform"]
- }
- if tmpApplet["name"] != "" {
- tmp.Name = tmpApplet["name"]
- }
- tmpSlot := GetSlotInfo(c, v.SlotId)
- if tmpSlot["state"] != "" {
- tmp.State = tmpSlot["state"]
- }
- if tmpSlot["name"] != "" {
- tmp.AdvName = tmpSlot["name"]
- }
- data = append(data, tmp)
- }
- }
- res := md.DataCenterGenerateDataRes{
- List: data,
- Total: total,
- State: md.AdState,
- Platform: md.AdPlatform,
- }
- return res
- }
-
- func DataCenterGenerateDataDel(c *gin.Context, req md.DataCenterGenerateDataCommReq) error {
- NewGenerateWxAdDataDb := implement.NewGenerateWxAdDataDb(db.Db)
- data, _ := NewGenerateWxAdDataDb.GetGenerateWxAdData(utils.StrToInt(req.Id))
- if data == nil {
- return errors.New("记录不存在")
- }
- if data.IsGenerateReport == 1 {
- return errors.New("记录已应用,不能删除")
- }
-
- // 1、删除 generate_wx_ad_data 数据
- _, err := db.Db.Where("id=?", req.Id).Delete(&model.GenerateWxAdData{})
- if err != nil {
- return err
- }
-
- // 2、删除 generate_wx_ad_data_with_agent_flow 数据
- _, err = db.Db.Where("generate_data_id=?", req.Id).Delete(&model.GenerateWxAdDataWithAgentFlow{})
- if err != nil {
- return err
- }
-
- // 3、修改 original_wx_ad_data 状态值
- db.Db.Where("id=?", data.OriginalDataId).Cols("is_apply").Update(&model.OriginalWxAdData{IsApply: 0})
- return nil
- }
- func DataCenterGenerateDataDetail(c *gin.Context, req md.DataCenterGenerateDataCommReq) {
- NewGenerateWxAdDataDb := implement.NewGenerateWxAdDataDb(db.Db)
- data, _ := NewGenerateWxAdDataDb.GetGenerateWxAdData(utils.StrToInt(req.Id))
- if data == nil {
- e.OutErr(c, 400, e.NewErr(400, "记录不存在"))
- return
- }
- agentReward := make([]md.DataCenterGenerateDataDetailAgentReward, 0)
- NewGenerateWxAdDataWithAgentFlowDb := implement.NewGenerateWxAdDataWithAgentFlowDb(db.Db)
- agent, _ := NewGenerateWxAdDataWithAgentFlowDb.FindGenerateWxAdDataWithAgentFlowByStrategyId(data.Id)
- if agent != nil {
- for _, v := range *agent {
- tmp := md.DataCenterGenerateDataDetailAgentReward{
- Name: "",
- Account: "",
- AgentRevenue: utils.Float64ToStr(float64(v.AgentRevenue) / 100),
- AgentRevenueRate: utils.IntToStr(data.AgentRevenueRate),
- ExtraRevenue: utils.Float64ToStr(float64(v.ExtraRevenue) / 100),
- ExtraRevenueRate: utils.IntToStr(data.ExtraRevenueRate),
- }
- tmpApplet := GetAgentInfo(c, v.AgentId)
- if tmpApplet["name"] != "" {
- tmp.Name = tmpApplet["name"]
- }
- if tmpApplet["account"] != "" {
- tmp.Account = tmpApplet["account"]
- }
- agentReward = append(agentReward, tmp)
- }
- }
- res := md.DataCenterGenerateDataDetailData{
- PlatformRetentionRate: utils.IntToStr(data.PlatformRetentionRate),
- CommissionRetentionRate: utils.IntToStr(data.CommissionRetentionRate),
- MediaRevenueRate: utils.IntToStr(data.MediaRevenueRate),
- AgentRevenueRate: utils.IntToStr(data.AgentRevenueRate),
- ExtraRevenueRate: utils.IntToStr(data.ExtraRevenueRate),
- AgreementSharingRate: utils.IntToStr(data.AgreementSharingRate),
- AgentReward: agentReward,
- }
- e.OutSuc(c, res, nil)
- return
- }
- func DataCenterGenerateDataDoing(c *gin.Context, req md.DataCenterGenerateDataCommReq) error {
- NewGenerateWxAdDataDb := implement.NewGenerateWxAdDataDb(db.Db)
- data, _ := NewGenerateWxAdDataDb.GetGenerateWxAdData(utils.StrToInt(req.Id))
- if data == nil {
- return e.NewErr(400, "记录不存在")
- }
- if data.IsGenerateReport == 1 {
- return e.NewErr(400, "该记录已完成操作")
- }
- // 加锁 防止并发提取
- mutexKey := fmt.Sprintf("%s:DataCenterGenerateDataDoing:%s", c.GetString("mid"), req.Id)
- withdrawAvailable, err := cache.Do("SET", mutexKey, 1, "EX", 5, "NX")
- if err != nil {
- return err
- }
- if withdrawAvailable != "OK" {
- return e.NewErr(400000, "请求过于频繁,请稍后再试")
- }
- args := md.SettlementWxAdData{
- GenerateDataId: utils.StrToInt(req.Id),
- }
- err = SettlementWxAdData(args)
- if err != nil {
- return err
- }
- return nil
- }
- func DataCenterSelectData(c *gin.Context) {
- NewAppletApplicationDb := implement2.NewAppletApplicationDb(MasterDb(c))
- appList, _ := NewAppletApplicationDb.FindAllAppletApplicationList()
- appMap := make(map[string][]map[string]interface{})
- user := GetUser(c)
- NewAdminBindMediumDb := implement2.NewAdminBindMediumDb(MasterDb(c))
- list := NewAdminBindMediumDb.FindAll(user.AdmId)
- ids := make([]string, 0)
- for _, v := range list {
- ids = append(ids, utils.IntToStr(v.MediumId))
- }
- for _, v := range appList {
- if utils.InArr(utils.IntToStr(v.MediumId), ids) == false {
- continue
- }
- _, ok := appMap[v.Platform]
- if ok == false {
- appMap[v.Platform] = make([]map[string]interface{}, 0)
- }
- tmp := map[string]interface{}{
- "name": v.Name,
- "app_id": v.AppId,
- "ad_type": enum.AdTypeList,
- }
- appMap[v.Platform] = append(appMap[v.Platform], tmp)
- }
- platform := []map[string]interface{}{
- {
- "name": "微信小程序",
- "platform": "wx_applet",
- "app_list": appMap["wx_applet"],
- },
- }
- e.OutSuc(c, platform, nil)
- return
- }
- func DataCenterTable(c *gin.Context, req md.DataCenterTableReq) md.DataCenterTableRes {
- var req1 md.DataCenterRecordReq
- copier.Copy(&req1, &req)
- nativeString, _ := comm(c, 0, req1)
- list := make([]md.DataCenterTableData, 0)
- tmpMap := make(map[string]md.DataCenterTableData)
- for _, v := range nativeString {
- tmp := md.DataCenterTableData{
- Date: v["date"],
- ExposureCount: v["exposure_count"],
- MediaRevenue: utils.Float64ToStr(utils.StrToFloat64(v["media_revenue"]) / 100),
- Ecpm: v["ecpm"],
- }
- tmpMap[v["date"]] = tmp
- }
- day := (utils.TimeStdParseUnix(req.EndDate+" 00:00:00") - utils.TimeStdParseUnix(req.StartDate+" 00:00:00")) / 86400
- for i := day; i >= 0; i-- {
- date := utils.TimeStdParseUnix(req.EndDate+" 00:00:00") - i*86400
- tmp, ok := tmpMap[time.Unix(date, 0).Format("2006-01-02")]
- if ok == false {
- tmp = md.DataCenterTableData{
- Date: time.Unix(date, 0).Format("2006-01-02"),
- ExposureCount: "0",
- MediaRevenue: "0",
- Ecpm: "0",
- }
- }
- list = append(list, tmp)
- }
- res := md.DataCenterTableRes{
- List: list,
- }
- return res
- }
- func comm(c *gin.Context, isTotal int, req md.DataCenterRecordReq) ([]map[string]string, int64) {
- appId := GetAppletId(c, req.Name, req.Platform, req.AppId)
- sql := `
- SELECT
- %s
- FROM generate_wx_ad_data
- where %s %s
- `
- where := "is_generate_report=1 and uuid=" + c.GetString("mid")
- if req.AppId != "" || req.Platform != "" {
- where += " and app_id in('" + appId + "')"
- }
- if req.AdType != "" {
- where += " and ad_slot='" + req.AdType + "'"
- }
- if req.StartDate != "" {
- where += " and date>='" + req.StartDate + "'"
- }
- if req.EndDate != "" {
- where += " and date<='" + req.EndDate + "'"
- }
- field := `*`
- start := (utils.StrToInt(req.Page) - 1) * utils.StrToInt(req.Limit)
- groupBy := " order by date desc,id desc"
- if req.Page != "" {
- groupBy += " limit " + utils.IntToStr(start) + "," + req.Limit
- } else {
- groupBy = " order by date asc,id asc"
- }
-
- sql1 := fmt.Sprintf(sql, field, where, groupBy)
- nativeString, _ := db.QueryNativeString(db.Db, sql1)
-
- var total int64 = 0
- if isTotal == 1 {
- sqlTotal := fmt.Sprintf(sql, "COUNT(*) as count ", where, "")
- nativeStringTotal, _ := db.QueryNativeString(db.Db, sqlTotal)
- for _, v := range nativeStringTotal {
- total = utils.StrToInt64(v["count"])
- }
- }
- return nativeString, total
- }
|