From 20af882d5a8b59f4c4a5645fd2e4fd4a244609f2 Mon Sep 17 00:00:00 2001 From: liujiandao <274878379@qq.com> Date: 星期一, 22 四月 2024 16:40:45 +0800 Subject: [PATCH] 考勤统计 --- controllers/attendance_controller.go | 55 +++ controllers/response/attendance_response.go | 27 + docs/swagger.yaml | 171 ++++++++++++ docs/docs.go | 258 ++++++++++++++++++ docs/swagger.json | 258 ++++++++++++++++++ controllers/request/attendance_request.go | 4 controllers/worker_controller.go | 2 models/attendance_manage.go | 10 router/router.go | 11 9 files changed, 790 insertions(+), 6 deletions(-) diff --git a/controllers/attendance_controller.go b/controllers/attendance_controller.go index 6a88958..e63e361 100644 --- a/controllers/attendance_controller.go +++ b/controllers/attendance_controller.go @@ -6,12 +6,14 @@ "github.com/xuri/excelize/v2" "silkserver/constvar" "silkserver/controllers/request" + "silkserver/controllers/response" "silkserver/extend/code" "silkserver/extend/util" "silkserver/middleware" "silkserver/models" "silkserver/pkg/logx" "silkserver/pkg/timex" + "strings" "time" ) @@ -185,6 +187,59 @@ util.ResponseFormatList(c, code.Success, manages, total) } +// GetAttendanceStatistic +// +// @Tags 鑰冨嫟绠$悊 +// @Summary 鑾峰彇鑰冨嫟缁熻 +// @Produce application/json +// @Param object body request.GetAttendanceStatistic true "鍙傛暟" +// @Param Authorization header string true "token" +// @Success 200 {object} util.Response{data=response.AttendanceList} "鎴愬姛" +// @Router /api-jl/v1/attendance/getAttendanceStatistic [post] +func (slf AttendanceController) GetAttendanceStatistic(c *gin.Context) { + var params request.GetAttendanceStatistic + err := c.BindJSON(¶ms) + if err != nil { + util.ResponseFormat(c, code.RequestParamError, "鍙傛暟瑙f瀽澶辫触锛屾暟鎹被鍨嬮敊璇�") + return + } + if params.Month == "" { + util.ResponseFormat(c, code.RequestParamError, "鍙傛暟涓嶈兘涓虹┖") + return + } + manages, err := models.NewAttendanceManageSearch().SetMonth(params.Month).SetPreload(true).FindNotTotal() + if err != nil { + util.ResponseFormat(c, code.RequestParamError, err) + return + } + m := make(map[string]response.AttendanceStatistic) + for _, manage := range manages { + var as response.AttendanceStatistic + if _, ok := m[manage.WorkerId]; ok { + as = m[manage.WorkerId] + } else { + as.WorkerId = manage.WorkerId + as.WorkerName = manage.WorkerName + as.WorkType = manage.WorkType.WorkName + as.Month = params.Month + } + as.WeekdayOverTime = as.WeekdayOverTime.Add(manage.OverTimeDuration) + as.ActualAttendanceDays = as.ActualAttendanceDays + 1 + var ad response.AttendanceDetail + ad.Date = strings.ReplaceAll(manage.Date, params.Month+"-", "") + ad.Status = manage.Status + as.Details = append(as.Details, ad) + + m[manage.WorkerId] = as + } + var list response.AttendanceList + for _, statistic := range m { + list.List = append(list.List, statistic) + } + + util.ResponseFormat(c, code.Success, list) +} + // DeleteAttendanceInfo // // @Tags 鑰冨嫟绠$悊 diff --git a/controllers/request/attendance_request.go b/controllers/request/attendance_request.go index e28e379..27163c5 100644 --- a/controllers/request/attendance_request.go +++ b/controllers/request/attendance_request.go @@ -8,3 +8,7 @@ type DeleteAttendanceInfo struct { Ids []uint `json:"ids"` //璁板綍id } + +type GetAttendanceStatistic struct { + Month string `json:"month"` //鏈堜唤 +} diff --git a/controllers/response/attendance_response.go b/controllers/response/attendance_response.go new file mode 100644 index 0000000..4b38892 --- /dev/null +++ b/controllers/response/attendance_response.go @@ -0,0 +1,27 @@ +package response + +import ( + "github.com/shopspring/decimal" + "silkserver/constvar" +) + +type AttendanceList struct { + List []AttendanceStatistic `json:"list"` +} + +type AttendanceStatistic struct { + WorkerId string `json:"workerId"` //浜哄憳id + WorkerName string `json:"workerName"` //浜哄憳濮撳悕 + WorkType string `json:"workType"` //宸ョ + Month string `json:"month"` //鏈堜唤 + WeekdayOverTime decimal.Decimal `json:"weekdayOverTime"` //宸ヤ綔鏃ュ姞鐝椂闀� + RestDayOverTime decimal.Decimal `json:"restDayOverTime"` //浼戞伅鏃ュ姞鐝椂闀� + RequiredAttendanceDays int `json:"requiredAttendanceDays"` //搴斿嚭鍕ゅぉ鏁� + ActualAttendanceDays int `json:"actualAttendanceDays"` //瀹為檯鍑哄嫟澶╂暟 + Details []AttendanceDetail `json:"details"` //璇︽儏 +} + +type AttendanceDetail struct { + Date string `json:"date"` //鏃ユ湡 + Status constvar.AttendanceStatus `json:"status"` //鐘舵�� +} diff --git a/controllers/worker_controller.go b/controllers/worker_controller.go index f4f93ae..e16a888 100644 --- a/controllers/worker_controller.go +++ b/controllers/worker_controller.go @@ -77,7 +77,7 @@ util.ResponseFormat(c, code.RequestParamError, "鍚嶇О涓虹┖") return } - err = models.NewWorkerSearch().Create(¶ms) + err = models.NewWorkerSearch().Save(¶ms) if err != nil { util.ResponseFormat(c, code.RequestParamError, "鏇存柊澶辫触") return diff --git a/docs/docs.go b/docs/docs.go index 3cceedb..c3e6e3f 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -124,6 +124,120 @@ } } }, + "/api-jl/v1/attendance/getAttendanceRule": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "鑰冨嫟绠$悊" + ], + "summary": "鑾峰彇鍔犵彮瑙勫垯", + "parameters": [ + { + "type": "string", + "description": "token", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "鎴愬姛", + "schema": { + "$ref": "#/definitions/util.Response" + } + } + } + } + }, + "/api-jl/v1/attendance/getAttendanceStatistic": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "鑰冨嫟绠$悊" + ], + "summary": "鑾峰彇鑰冨嫟缁熻", + "parameters": [ + { + "description": "鍙傛暟", + "name": "object", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetAttendanceStatistic" + } + }, + { + "type": "string", + "description": "token", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "鎴愬姛", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.AttendanceList" + } + } + } + ] + } + } + } + } + }, + "/api-jl/v1/attendance/saveAttendanceRule": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "鑰冨嫟绠$悊" + ], + "summary": "淇濆瓨鍔犵彮瑙勫垯", + "parameters": [ + { + "description": "鍙傛暟", + "name": "object", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.AttendanceRule" + } + }, + { + "type": "string", + "description": "token", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "鎴愬姛", + "schema": { + "$ref": "#/definitions/util.Response" + } + } + } + } + }, "/api-jl/v1/fineness/changeYieldRegister": { "post": { "produces": [ @@ -2079,6 +2193,27 @@ } }, "definitions": { + "constvar.AttendanceStatus": { + "type": "integer", + "enum": [ + 1, + 2, + 3, + 4 + ], + "x-enum-comments": { + "Abnormal": "寮傚父", + "Normal": "姝e父", + "Overtime": "鍔犵彮", + "Vacation": "浼戝亣" + }, + "x-enum-varnames": [ + "Normal", + "Overtime", + "Vacation", + "Abnormal" + ] + }, "constvar.CarFlag": { "type": "integer", "enum": [ @@ -2241,17 +2376,58 @@ "id": { "type": "integer" }, + "overTimeDuration": { + "type": "number" + }, "startWorkTime": { "type": "string" }, + "status": { + "$ref": "#/definitions/constvar.AttendanceStatus" + }, "updatedAt": { "type": "string" + }, + "workType": { + "$ref": "#/definitions/models.WorkTypeManage" + }, + "workTypeId": { + "type": "integer" }, "workerId": { "type": "string" }, "workerName": { "type": "string" + } + } + }, + "models.AttendanceRule": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "deletedAt": { + "$ref": "#/definitions/gorm.DeletedAt" + }, + "id": { + "type": "integer" + }, + "overTimeStart": { + "type": "number" + }, + "restDayRule": { + "type": "integer" + }, + "restDayStart": { + "type": "number" + }, + "updatedAt": { + "type": "string" + }, + "weekdayRule": { + "type": "integer" } } }, @@ -3219,6 +3395,7 @@ "type": "object", "properties": { "ids": { + "description": "璁板綍id", "type": "array", "items": { "type": "integer" @@ -3277,6 +3454,15 @@ "pageSize": { "description": "姣忛〉澶у皬", "type": "integer" + } + } + }, + "request.GetAttendanceStatistic": { + "type": "object", + "properties": { + "month": { + "description": "鏈堜唤", + "type": "string" } } }, @@ -3784,6 +3970,78 @@ } } }, + "response.AttendanceDetail": { + "type": "object", + "properties": { + "date": { + "description": "鏃ユ湡", + "type": "string" + }, + "status": { + "description": "鐘舵��", + "allOf": [ + { + "$ref": "#/definitions/constvar.AttendanceStatus" + } + ] + } + } + }, + "response.AttendanceList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/response.AttendanceStatistic" + } + } + } + }, + "response.AttendanceStatistic": { + "type": "object", + "properties": { + "actualAttendanceDays": { + "description": "瀹為檯鍑哄嫟澶╂暟", + "type": "integer" + }, + "details": { + "description": "璇︽儏", + "type": "array", + "items": { + "$ref": "#/definitions/response.AttendanceDetail" + } + }, + "month": { + "description": "鏈堜唤", + "type": "string" + }, + "requiredAttendanceDays": { + "description": "搴斿嚭鍕ゅぉ鏁�", + "type": "integer" + }, + "restDayOverTime": { + "description": "浼戞伅鏃ュ姞鐝椂闀�", + "type": "number" + }, + "weekdayOverTime": { + "description": "宸ヤ綔鏃ュ姞鐝椂闀�", + "type": "number" + }, + "workType": { + "description": "宸ョ", + "type": "string" + }, + "workerId": { + "description": "浜哄憳id", + "type": "string" + }, + "workerName": { + "description": "浜哄憳濮撳悕", + "type": "string" + } + } + }, "response.CarAndLevel": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 4857f9d..a8cc528 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -112,6 +112,120 @@ } } }, + "/api-jl/v1/attendance/getAttendanceRule": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "鑰冨嫟绠$悊" + ], + "summary": "鑾峰彇鍔犵彮瑙勫垯", + "parameters": [ + { + "type": "string", + "description": "token", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "鎴愬姛", + "schema": { + "$ref": "#/definitions/util.Response" + } + } + } + } + }, + "/api-jl/v1/attendance/getAttendanceStatistic": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "鑰冨嫟绠$悊" + ], + "summary": "鑾峰彇鑰冨嫟缁熻", + "parameters": [ + { + "description": "鍙傛暟", + "name": "object", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetAttendanceStatistic" + } + }, + { + "type": "string", + "description": "token", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "鎴愬姛", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.AttendanceList" + } + } + } + ] + } + } + } + } + }, + "/api-jl/v1/attendance/saveAttendanceRule": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "鑰冨嫟绠$悊" + ], + "summary": "淇濆瓨鍔犵彮瑙勫垯", + "parameters": [ + { + "description": "鍙傛暟", + "name": "object", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.AttendanceRule" + } + }, + { + "type": "string", + "description": "token", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "鎴愬姛", + "schema": { + "$ref": "#/definitions/util.Response" + } + } + } + } + }, "/api-jl/v1/fineness/changeYieldRegister": { "post": { "produces": [ @@ -2067,6 +2181,27 @@ } }, "definitions": { + "constvar.AttendanceStatus": { + "type": "integer", + "enum": [ + 1, + 2, + 3, + 4 + ], + "x-enum-comments": { + "Abnormal": "寮傚父", + "Normal": "姝e父", + "Overtime": "鍔犵彮", + "Vacation": "浼戝亣" + }, + "x-enum-varnames": [ + "Normal", + "Overtime", + "Vacation", + "Abnormal" + ] + }, "constvar.CarFlag": { "type": "integer", "enum": [ @@ -2229,17 +2364,58 @@ "id": { "type": "integer" }, + "overTimeDuration": { + "type": "number" + }, "startWorkTime": { "type": "string" }, + "status": { + "$ref": "#/definitions/constvar.AttendanceStatus" + }, "updatedAt": { "type": "string" + }, + "workType": { + "$ref": "#/definitions/models.WorkTypeManage" + }, + "workTypeId": { + "type": "integer" }, "workerId": { "type": "string" }, "workerName": { "type": "string" + } + } + }, + "models.AttendanceRule": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "deletedAt": { + "$ref": "#/definitions/gorm.DeletedAt" + }, + "id": { + "type": "integer" + }, + "overTimeStart": { + "type": "number" + }, + "restDayRule": { + "type": "integer" + }, + "restDayStart": { + "type": "number" + }, + "updatedAt": { + "type": "string" + }, + "weekdayRule": { + "type": "integer" } } }, @@ -3207,6 +3383,7 @@ "type": "object", "properties": { "ids": { + "description": "璁板綍id", "type": "array", "items": { "type": "integer" @@ -3265,6 +3442,15 @@ "pageSize": { "description": "姣忛〉澶у皬", "type": "integer" + } + } + }, + "request.GetAttendanceStatistic": { + "type": "object", + "properties": { + "month": { + "description": "鏈堜唤", + "type": "string" } } }, @@ -3772,6 +3958,78 @@ } } }, + "response.AttendanceDetail": { + "type": "object", + "properties": { + "date": { + "description": "鏃ユ湡", + "type": "string" + }, + "status": { + "description": "鐘舵��", + "allOf": [ + { + "$ref": "#/definitions/constvar.AttendanceStatus" + } + ] + } + } + }, + "response.AttendanceList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/response.AttendanceStatistic" + } + } + } + }, + "response.AttendanceStatistic": { + "type": "object", + "properties": { + "actualAttendanceDays": { + "description": "瀹為檯鍑哄嫟澶╂暟", + "type": "integer" + }, + "details": { + "description": "璇︽儏", + "type": "array", + "items": { + "$ref": "#/definitions/response.AttendanceDetail" + } + }, + "month": { + "description": "鏈堜唤", + "type": "string" + }, + "requiredAttendanceDays": { + "description": "搴斿嚭鍕ゅぉ鏁�", + "type": "integer" + }, + "restDayOverTime": { + "description": "浼戞伅鏃ュ姞鐝椂闀�", + "type": "number" + }, + "weekdayOverTime": { + "description": "宸ヤ綔鏃ュ姞鐝椂闀�", + "type": "number" + }, + "workType": { + "description": "宸ョ", + "type": "string" + }, + "workerId": { + "description": "浜哄憳id", + "type": "string" + }, + "workerName": { + "description": "浜哄憳濮撳悕", + "type": "string" + } + } + }, "response.CarAndLevel": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 2e0c19d..dd37dc9 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,4 +1,21 @@ definitions: + constvar.AttendanceStatus: + enum: + - 1 + - 2 + - 3 + - 4 + type: integer + x-enum-comments: + Abnormal: 寮傚父 + Normal: 姝e父 + Overtime: 鍔犵彮 + Vacation: 浼戝亣 + x-enum-varnames: + - Normal + - Overtime + - Vacation + - Abnormal constvar.CarFlag: enum: - 1 @@ -126,14 +143,41 @@ type: string id: type: integer + overTimeDuration: + type: number startWorkTime: type: string + status: + $ref: '#/definitions/constvar.AttendanceStatus' updatedAt: type: string + workType: + $ref: '#/definitions/models.WorkTypeManage' + workTypeId: + type: integer workerId: type: string workerName: type: string + type: object + models.AttendanceRule: + properties: + createdAt: + type: string + deletedAt: + $ref: '#/definitions/gorm.DeletedAt' + id: + type: integer + overTimeStart: + type: number + restDayRule: + type: integer + restDayStart: + type: number + updatedAt: + type: string + weekdayRule: + type: integer type: object models.Dict: properties: @@ -794,6 +838,7 @@ request.DeleteAttendanceInfo: properties: ids: + description: 璁板綍id items: type: integer type: array @@ -835,6 +880,12 @@ pageSize: description: 姣忛〉澶у皬 type: integer + type: object + request.GetAttendanceStatistic: + properties: + month: + description: 鏈堜唤 + type: string type: object request.GetMentorList: properties: @@ -1190,6 +1241,55 @@ description: 浜ч噺鐧昏琛╥d type: integer type: object + response.AttendanceDetail: + properties: + date: + description: 鏃ユ湡 + type: string + status: + allOf: + - $ref: '#/definitions/constvar.AttendanceStatus' + description: 鐘舵�� + type: object + response.AttendanceList: + properties: + list: + items: + $ref: '#/definitions/response.AttendanceStatistic' + type: array + type: object + response.AttendanceStatistic: + properties: + actualAttendanceDays: + description: 瀹為檯鍑哄嫟澶╂暟 + type: integer + details: + description: 璇︽儏 + items: + $ref: '#/definitions/response.AttendanceDetail' + type: array + month: + description: 鏈堜唤 + type: string + requiredAttendanceDays: + description: 搴斿嚭鍕ゅぉ鏁� + type: integer + restDayOverTime: + description: 浼戞伅鏃ュ姞鐝椂闀� + type: number + weekdayOverTime: + description: 宸ヤ綔鏃ュ姞鐝椂闀� + type: number + workType: + description: 宸ョ + type: string + workerId: + description: 浜哄憳id + type: string + workerName: + description: 浜哄憳濮撳悕 + type: string + type: object response.CarAndLevel: properties: car: @@ -1342,6 +1442,77 @@ summary: 鑾峰彇鑰冨嫟鍒楄〃 tags: - 鑰冨嫟绠$悊 + /api-jl/v1/attendance/getAttendanceRule: + get: + parameters: + - description: token + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: 鎴愬姛 + schema: + $ref: '#/definitions/util.Response' + summary: 鑾峰彇鍔犵彮瑙勫垯 + tags: + - 鑰冨嫟绠$悊 + /api-jl/v1/attendance/getAttendanceStatistic: + post: + parameters: + - description: 鍙傛暟 + in: body + name: object + required: true + schema: + $ref: '#/definitions/request.GetAttendanceStatistic' + - description: token + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: 鎴愬姛 + schema: + allOf: + - $ref: '#/definitions/util.Response' + - properties: + data: + $ref: '#/definitions/response.AttendanceList' + type: object + summary: 鑾峰彇鑰冨嫟缁熻 + tags: + - 鑰冨嫟绠$悊 + /api-jl/v1/attendance/saveAttendanceRule: + post: + parameters: + - description: 鍙傛暟 + in: body + name: object + required: true + schema: + $ref: '#/definitions/models.AttendanceRule' + - description: token + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: 鎴愬姛 + schema: + $ref: '#/definitions/util.Response' + summary: 淇濆瓨鍔犵彮瑙勫垯 + tags: + - 鑰冨嫟绠$悊 /api-jl/v1/fineness/changeYieldRegister: post: parameters: diff --git a/models/attendance_manage.go b/models/attendance_manage.go index 9a9f49e..ed14ff0 100644 --- a/models/attendance_manage.go +++ b/models/attendance_manage.go @@ -33,6 +33,7 @@ PageSize int Preload bool Ids []uint + Month string Orm *gorm.DB } ) @@ -65,6 +66,11 @@ return slf } +func (slf *AttendanceManageSearch) SetMonth(month string) *AttendanceManageSearch { + slf.Month = month + return slf +} + func (slf *AttendanceManageSearch) build() *gorm.DB { var db = slf.Orm.Table(slf.TableName()) @@ -76,6 +82,10 @@ db = db.Model(&AttendanceManage{}).Preload("WorkType") } + if slf.Month != "" { + db = db.Where("date like ?", slf.Month+"%") + } + return db } diff --git a/router/router.go b/router/router.go index 22b8c04..8d2b3e2 100644 --- a/router/router.go +++ b/router/router.go @@ -105,11 +105,12 @@ attendanceApi := r.Group(urlPrefix + "/attendance") attendanceController := new(controllers.AttendanceController) { - attendanceApi.POST("attendanceInput", attendanceController.AttendanceInput) //鑰冨嫟瀵煎叆 - attendanceApi.POST("getAttendanceList", attendanceController.GetAttendanceList) //鑾峰彇鑰冨嫟鍒楄〃 - attendanceApi.DELETE("deleteAttendanceInfo", attendanceController.DeleteAttendanceInfo) //鍒犻櫎鑰冨嫟淇℃伅 - attendanceApi.GET("getAttendanceRule", attendanceController.GetAttendanceRule) //鑾峰彇鍔犵彮瑙勫垯 - attendanceApi.POST("saveAttendanceRule", attendanceController.SaveAttendanceRule) //淇濆瓨鍔犵彮瑙勫垯 + attendanceApi.POST("attendanceInput", attendanceController.AttendanceInput) //鑰冨嫟瀵煎叆 + attendanceApi.POST("getAttendanceList", attendanceController.GetAttendanceList) //鑾峰彇鑰冨嫟鍒楄〃 + attendanceApi.POST("getAttendanceStatistic", attendanceController.GetAttendanceStatistic) //鑾峰彇鑰冨嫟缁熻 + attendanceApi.DELETE("deleteAttendanceInfo", attendanceController.DeleteAttendanceInfo) //鍒犻櫎鑰冨嫟淇℃伅 + attendanceApi.GET("getAttendanceRule", attendanceController.GetAttendanceRule) //鑾峰彇鍔犵彮瑙勫垯 + attendanceApi.POST("saveAttendanceRule", attendanceController.SaveAttendanceRule) //淇濆瓨鍔犵彮瑙勫垯 } mentorApi := r.Group(urlPrefix + "/mentor") -- Gitblit v1.8.0