فهرست منبع

feature:运维事件任务工时管理及相关功能优化

1、新增运维事件任务工时记录功能,添加相关Dao、Model层
2、优化运维事件任务和交付项目管理服务层逻辑
3、完善opms_admin模块字典和用户管理功能
4、更新各模块Handler层导入和配置

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
程健 5 روز پیش
والد
کامیت
33fa92679c
49فایلهای تغییر یافته به همراه1131 افزوده شده و 76 حذف شده
  1. 15 0
      opms_admin/app/handler/dict.go
  2. 17 0
      opms_admin/app/handler/user.go
  3. 147 0
      opms_admin/app/model/opsdev/ops_event_task.go
  4. 9 0
      opms_admin/app/model/sys_dict_data.go
  5. 6 0
      opms_admin/app/model/sys_user.go
  6. 26 0
      opms_admin/app/service/sys_dict_data.go
  7. 43 0
      opms_admin/app/service/sys_user.go
  8. 8 7
      opms_admin/config/config.toml
  9. 1 1
      opms_parent/README.md
  10. 3 0
      opms_parent/app/dao/opsdev/internal/ops_event_task.go
  11. 435 0
      opms_parent/app/dao/opsdev/internal/ops_event_task_work_hour.go
  12. 36 0
      opms_parent/app/dao/opsdev/ops_event_task_work_hour.go
  13. 1 0
      opms_parent/app/handler/base/distributor_contact.go
  14. 1 0
      opms_parent/app/handler/base/distributor_target.go
  15. 1 0
      opms_parent/app/handler/base/region.go
  16. 1 0
      opms_parent/app/handler/contract/ctr_contract_sale_target.go
  17. 1 0
      opms_parent/app/handler/cust/cust_customer_bid_record.go
  18. 1 0
      opms_parent/app/handler/cust/cust_customer_invoice_header.go
  19. 1 0
      opms_parent/app/handler/cust/product_consult_record.go
  20. 3 2
      opms_parent/app/handler/dingtalk/ding_event.go
  21. 1 0
      opms_parent/app/handler/home/home.go
  22. 14 0
      opms_parent/app/handler/opsdev/delivery_project.go
  23. 2 2
      opms_parent/app/handler/opsdev/delivery_project_event.go
  24. 34 1
      opms_parent/app/handler/opsdev/ops_event_task.go
  25. 1 0
      opms_parent/app/handler/plat/followup_comment.go
  26. 1 0
      opms_parent/app/handler/plat/questionnaire.go
  27. 1 0
      opms_parent/app/handler/proj/business.go
  28. 1 0
      opms_parent/app/handler/proj/business_contact.go
  29. 1 0
      opms_parent/app/handler/proj/business_file.go
  30. 1 0
      opms_parent/app/handler/proj/business_team.go
  31. 1 0
      opms_parent/app/handler/sysreport/sys_report.go
  32. 1 0
      opms_parent/app/handler/sysreport/sys_role_report.go
  33. 1 0
      opms_parent/app/handler/sysreport/work_order_report.go
  34. 1 0
      opms_parent/app/handler/train/sale_apply.go
  35. 1 0
      opms_parent/app/handler/train/sale_apply_summary.go
  36. 1 0
      opms_parent/app/handler/train/train_head_office.go
  37. 1 0
      opms_parent/app/handler/work/work_order.go
  38. 1 0
      opms_parent/app/handler/work/work_order_type.go
  39. 1 0
      opms_parent/app/model/opsdev/internal/ops_event_task.go
  40. 27 0
      opms_parent/app/model/opsdev/internal/ops_event_task_work_hour.go
  41. 1 1
      opms_parent/app/model/opsdev/ops_delivery_project_event.go
  42. 25 1
      opms_parent/app/model/opsdev/ops_event_task.go
  43. 14 0
      opms_parent/app/model/opsdev/ops_event_task_work_hour.go
  44. 78 1
      opms_parent/app/service/opsdev/delivery_project.go
  45. 3 3
      opms_parent/app/service/opsdev/delivery_project_event.go
  46. 147 43
      opms_parent/app/service/opsdev/ops_event_task.go
  47. 5 5
      opms_parent/config/config.toml
  48. 9 9
      opms_parent/main.go
  49. BIN
      opms_parent/opms_parent_v2

+ 15 - 0
opms_admin/app/handler/dict.go

@@ -144,6 +144,21 @@ func (h *DictHandler) GetDictDataByType(ctx context.Context, req *model.GetDictR
 	return nil
 }
 
+// GetDictDataByTypes 批量根据字典类型获取字典项明细
+func (h *DictHandler) GetDictDataByTypes(ctx context.Context, req *model.GetDictsReq, rsp *comm_def.CommonMsg) error {
+	dictService, err := service.NewDictDataService(ctx)
+	if err != nil {
+		return err
+	}
+
+	dicts, err := dictService.GetDictWithDataByTypes(req)
+	if err != nil {
+		return err
+	}
+	rsp.Data = dicts
+	return nil
+}
+
 // CreateDictData 添加字典明细
 func (h *DictHandler) CreateDictData(ctx context.Context, req *model.DictDataAddReq, rsp *comm_def.CommonMsg) error {
 	// 参数校验

+ 17 - 0
opms_admin/app/handler/user.go

@@ -266,6 +266,23 @@ func (h *UserHandler) GetUserByRole(ctx context.Context, req *model.SysUserRoleR
 	return nil
 }
 
+func (h *UserHandler) GetUsersByRoleKeys(ctx context.Context, req *model.SysUserRoleKeysReq, rsp *comm_def.CommonMsg) error {
+	if len(req.RoleKeys) == 0 && len(req.RoleIds) == 0 {
+		return myerrors.TipsError("角色编码和角色ID不能同时为空")
+	}
+	userService, err := service.NewUserService(ctx)
+	if err != nil {
+		return err
+	}
+
+	list, err := userService.GetUsersByRoleKeys(req.RoleKeys, req.RoleIds)
+	if err != nil {
+		return err
+	}
+	rsp.Data = list
+	return nil
+}
+
 //// GetDictList 获取用户列表字典项(返回字典数据形式,一般用来绑定用户的下拉列表)
 //func (e *UserHandler) GetDictList(ctx context.Context, req *user_def.DictSelectReq, rsp *comm_def.CommonMsg) error {
 //	// 获取租户码

+ 147 - 0
opms_admin/app/model/opsdev/ops_event_task.go

@@ -0,0 +1,147 @@
+package opsdev
+
+import (
+    "dashoo.cn/opms_libary/request"
+    "dashoo.cn/opms_parent/app/model/opsdev/internal"
+    "github.com/gogf/gf/os/gtime"
+)
+
+// OpsEventTask is the golang structure for table ops_event_task.
+type OpsEventTask internal.OpsEventTask
+
+// ----------------------
+//  任务状态常量定义
+// ----------------------
+// Task Status
+const (
+    // 待处理
+    TaskStatusTodo       = "10"
+    // 处理中
+    TaskStatusProcessing = "20"
+    // 暂停
+    TaskStatusPaused     = "25"
+    // 已完成
+    TaskStatusCompleted  = "30"
+    // 阻塞
+    TaskStatusBlocked    = "70"
+    // 作废
+    TaskStatusCancelled  = "90"
+)
+
+// Task Type
+const (
+    // 需求评审
+    TaskTypeReqReview     = "10"
+    // 功能开发
+    TaskTypeFeatureDev    = "20"
+    // 功能测试
+    TaskTypeFeatureTest   = "30"
+    // 系统发版
+    TaskTypeSystemRelease = "40"
+)
+
+// Priority
+const (
+    PriorityUrgent = "10" // 紧急
+    PriorityHigh   = "20" // 高
+    PriorityMedium = "30" // 中
+    PriorityLow    = "40" // 低
+)
+
+// ----------------------
+// 传输对象 (DTOs)
+// ----------------------
+
+// OpsEventTaskSearchReq - 列表查询请求,嵌入分页
+type OpsEventTaskSearchReq struct {
+    request.PageReq
+    ProjectId  int        `json:"projectId"`
+    TaskTitle  string     `json:"taskTitle"`
+    TaskType   []string   `json:"taskType"`
+    TaskStatus []string   `json:"taskStatus"`
+    Priority   []string   `json:"priority"`
+    OpsUserName string    `json:"opsUserName"`
+    SortFields []SortField `json:"sortFields"`
+}
+
+// OpsEventTaskAddReq - 创建请求
+type OpsEventTaskAddReq struct {
+    ProjectId int     `json:"projectId"` // 必填
+    TaskTitle string  `json:"taskTitle"` // 必填
+    TaskDesc  string  `json:"taskDesc"`
+    TaskType  string  `json:"taskType"`  // 必填
+    Priority  string  `json:"priority"`  // 必填
+    Remark    string  `json:"remark"`
+}
+
+// OpsEventTaskUpdateReq - 更新请求
+type OpsEventTaskUpdateReq struct {
+    Id        int     `json:"id"`
+    TaskTitle string  `json:"taskTitle"`
+    TaskDesc  string  `json:"taskDesc"`
+    TaskType  string  `json:"taskType"`
+    Priority  string  `json:"priority"`
+    Remark    string  `json:"remark"`
+}
+
+// OpsEventTaskScheduleReq - 排期请求
+type OpsEventTaskScheduleReq struct {
+    Id               int       `json:"id"`
+    OpsUserId        int       `json:"opsUserId"`
+    OpsUserName      string    `json:"opsUserName"`
+    PlanStartTime    string    `json:"planStartTime"`
+    PlanEndTime      string    `json:"planEndTime"`
+    EstimateWorkHour float64   `json:"estimateWorkHour"`
+}
+
+// OpsEventTaskCompleteReq - 完成请求
+type OpsEventTaskCompleteReq struct {
+    Id              int        `json:"id"`
+    ActualWorkHour  float64    `json:"actualWorkHour"`
+    Remark          string     `json:"remark"`
+    Attachments     []Attachment `json:"attachments"`
+}
+
+// OpsEventTaskStatusReq - 状态变更请求(暂停/阻塞/取消)
+type OpsEventTaskStatusReq struct {
+    Id     int    `json:"id"`
+    Remark string `json:"remark"`
+}
+
+// OpsEventTaskRsp - API 响应结构
+type OpsEventTaskRsp struct {
+    OpsEventTask
+    ProjectName string `json:"projectName"`
+}
+
+// OpsEventTaskRecordSearchReq - 记录查询
+type OpsEventTaskRecordSearchReq struct {
+    TaskId int `json:"taskId"`
+}
+
+// OpsEventTaskRecordWithAttachments - 记录带附件
+type OpsEventTaskRecordWithAttachments struct {
+    OpsEventTaskRecord
+    Attachments []*Attachment `json:"attachments"`
+}
+
+// SortField - 排序字段
+type SortField struct {
+    Field string `json:"field"`
+    Order string `json:"order"`
+}
+
+// Attachment - 附件信息
+type Attachment struct {
+    FileName string `json:"fileName"`
+    FileUrl  string `json:"fileUrl"`
+    FileType string `json:"fileType"`
+}
+
+// OpsEventTaskRecord - 任务记录的简化 DTO(与业务无关,仅为 DTO 演示)
+type OpsEventTaskRecord struct {
+    Id        int       `json:"id"`
+    TaskId    int       `json:"taskId"`
+    Action    string    `json:"action"`
+    CreatedTime *gtime.Time `json:"createdTime"`
+}

+ 9 - 0
opms_admin/app/model/sys_dict_data.go

@@ -30,6 +30,14 @@ type GetDictReq struct {
 	Ctx          context.Context
 }
 
+// GetDictsReq 批量获取多个字典信息
+type GetDictsReq struct {
+	DictTypes []string `p:"dictTypes" v:"required#字典类型不能为空"`
+}
+
+// DictsRes 批量字典响应
+type DictsRes map[string]*DictRes
+
 // DictRes 完整的一个字典信息
 type DictRes struct {
 	Info   *DictTypeRes   `json:"info"`
@@ -43,6 +51,7 @@ type DictTypeRes struct {
 
 // DictDataRes 字典数据
 type DictDataRes struct {
+	DictType  string `json:"dictType"`
 	DictValue string `json:"key"`
 	DictLabel string `json:"value"`
 	IsDefault int    `json:"isDefault"`

+ 6 - 0
opms_admin/app/model/sys_user.go

@@ -137,3 +137,9 @@ type DeptIdReq struct {
 	DeptId  int  `json:"dept_id,omitempty"`
 	Include bool `json:"include,omitempty"`
 }
+
+// SysUserRoleKeysReq 根据角色编码查询用户请求
+type SysUserRoleKeysReq struct {
+	RoleKeys []string `json:"roleKeys"` // 角色编码列表,如:["ProjectManager", "ProjectDeliveryManager"]
+	RoleIds  []int    `json:"roleIds"`  // 角色ID列表,如:[1011, 1012]
+}

+ 26 - 0
opms_admin/app/service/sys_dict_data.go

@@ -99,6 +99,32 @@ func (s *DictDataService) GetDictWithDataByType(req *model.GetDictReq) (dict *mo
 	return
 }
 
+// GetDictWithDataByTypes 批量通过字典键类型获取选项
+func (s *DictDataService) GetDictWithDataByTypes(req *model.GetDictsReq) (dicts model.DictsRes, err error) {
+	dicts = make(model.DictsRes)
+	if len(req.DictTypes) == 0 {
+		return dicts, nil
+	}
+
+	var values []*model.DictDataRes
+	err = s.Dao.Where(s.Dao.C.DictType+" IN(?)", req.DictTypes).
+		Where(s.Dao.C.Status, "10").
+		Order(s.Dao.C.DictSort + " asc," + s.Dao.C.DictCode + " asc").
+		Scan(&values)
+	if err != nil {
+		g.Log().Error(err)
+		return nil, myerrors.TipsError("批量获取字典数据失败")
+	}
+
+	for _, dt := range req.DictTypes {
+		dicts[dt] = &model.DictRes{Values: make([]*model.DictDataRes, 0)}
+	}
+	for _, v := range values {
+		dicts[v.DictType].Values = append(dicts[v.DictType].Values, v)
+	}
+	return dicts, nil
+}
+
 // CheckDictTypeUniqueAll 检查字典类型是否唯一
 func (s *DictDataService) CheckDictTypeUniqueAll(dictType string) bool {
 	dict, err := s.Dao.FindOne(s.Dao.C.DictType+"=?", dictType)

+ 43 - 0
opms_admin/app/service/sys_user.go

@@ -220,6 +220,49 @@ func (s *UserService) GetUserByRole(roleId int) (userList []*model.SysUser, err
 	return userList, err
 }
 
+func (s *UserService) GetUsersByRoleKeys(roleKeys []string, roleIds []int) (userList []*model.SysUser, err error) {
+	var allRoleIds []int
+
+	if len(roleKeys) > 0 {
+		roleIdsFromKeys, err := dao.NewSysRoleDao(s.Tenant).
+			Fields(dao.SysRole.C.Id).
+			WhereIn(dao.SysRole.C.RoleKey, roleKeys).
+			Where(dao.SysRole.C.Status, "10").
+			Array()
+		if err != nil {
+			return nil, err
+		}
+		allRoleIds = append(allRoleIds, gconv.Ints(roleIdsFromKeys)...)
+	}
+
+	if len(roleIds) > 0 {
+		allRoleIds = append(allRoleIds, roleIds...)
+	}
+
+	if len(allRoleIds) == 0 {
+		return make([]*model.SysUser, 0), nil
+	}
+
+	userIds, err := dao.NewSysUserRoleDao(s.Tenant).
+		Fields(dao.SysUserRole.C.UserId).
+		WhereIn(dao.SysUserRole.C.RoleId, allRoleIds).
+		Array()
+	if err != nil {
+		return nil, err
+	}
+
+	if len(userIds) == 0 {
+		return make([]*model.SysUser, 0), nil
+	}
+
+	userList = make([]*model.SysUser, 0)
+	err = s.Dao.FieldsEx(s.Dao.C.Password, s.Dao.C.UserSalt).
+		Where(s.Dao.C.Status, "10").
+		WhereIn(s.Dao.C.Id, gconv.Ints(userIds)).
+		Scan(&userList)
+	return userList, err
+}
+
 // GetAdminUserByUsernamePassword 后台登陆验证
 func (s *UserService) GetAdminUserByUsernamePassword(ctx context.Context, req *model.LoginParamsReq) (user *model.LoginUserRes, err error) {
 	user, err = s.GetUserByUsernamePassword(ctx, req)

+ 8 - 7
opms_admin/config/config.toml

@@ -1,11 +1,11 @@
 # 应用系统设置
 [setting]
     logpath = "./log/admin"
-    bind-addr = "127.0.0.1:8001"
+    bind-addr = "192.168.0.188:8001"
     need-advertise-addr = false
-    srv-name = "dashoo.opms.admin-0.0.1"
+    srv-name = "dashoo.opms.admin-0.0.1-cj"
     env = "dev"
-    websocket-addr = "127.0.0.1:8899"
+    websocket-addr = "192.168.0.188:8899"
 
 [logger.websocket]
     path   = "./log/websocket"
@@ -18,7 +18,8 @@
 # 微服务注册中心配置
 [service_registry]
     registry = "consul" # consul 或 peer2peer
-    server-addr = "127.0.0.1:8500"
+#    server-addr = "192.168.0.39:8500"
+    server-addr = "192.168.0.221:18500"
 
 # 数据库连接
 [database]
@@ -27,14 +28,13 @@
         createdAt = "created_time"
         updatedAt = "updated_time"
         deletedAt = "deleted_time"
-        link = "mysql:root:Dashoo#190801@ali@tcp(192.168.0.252:3306)/dashoo_crm"
+        link = "mysql:oms_u:S3TFPnthe3SaJpmV@tcp(sh-cdb-3dhdnseg.sql.tencentcdb.com:59463)/dashoo_oms"
     [[database.8b9ec443]]
         Debug = true
         createdAt = "created_time"
         updatedAt = "updated_time"
         deletedAt = "deleted_time"
-        link = "mysql:opms:Bgt5^yhn@tcp(rm-bp1j647875x906s19go.mysql.rds.aliyuncs.com:3306)/opms?loc=Local&parseTime=true"
-
+        link = "mysql:oms_u:S3TFPnthe3SaJpmV@tcp(sh-cdb-3dhdnseg.sql.tencentcdb.com:59463)/dashoo_oms"
 # token认证设置
 [gtoken]
     # 缓存模式 1 gcache 2 gredis
@@ -55,6 +55,7 @@
     aes-key="oUjmeWea8Ow1jsdK4UHoDthy6EMQKq3RGbM2rEeTgnm"
     token="WaasHsYk8V3wqwN5xRGsCmiiRDB"
 
+
 [email]
     From="likai@dashoo.cn"
     host="hwsmtp.exmail.qq.com"

+ 1 - 1
opms_parent/README.md

@@ -34,5 +34,5 @@
 
 
 
-/gf gen dao -mode opsdev -t ops_delivery_project  -l "mysql:opms:Bgt5^yhn@tcp(rm-bp1j647875x906s19go.mysql.rds.aliyuncs.com:3306)/opms" 
+/gf gen dao -mode opsdev -t ops_event_task  -l "mysql:opms:Bgt5^yhn@tcp(rm-bp1j647875x906s19go.mysql.rds.aliyuncs.com:3306)/opms" 
 

+ 3 - 0
opms_parent/app/dao/opsdev/internal/ops_event_task.go

@@ -31,6 +31,7 @@ type opsEventTaskColumns struct {
 	EventId          string // 关联事件ID(event表id)
 	EventType        string // 关联事件类型(10 交付 20 运维)
 	ProjectId        string // 交付项目ID
+	ProjectName      string // 项目名称
 	TaskNo           string // 任务编码
 	TaskTitle        string // 任务标题
 	TaskDesc         string // 任务描述
@@ -71,6 +72,7 @@ var (
 			EventId:          "event_id",
 			EventType:        "event_type",
 			ProjectId:        "project_id",
+			ProjectName:      "project_name",
 			TaskNo:           "task_no",
 			TaskTitle:        "task_title",
 			TaskDesc:         "task_desc",
@@ -113,6 +115,7 @@ func NewOpsEventTaskDao(tenant string) OpsEventTaskDao {
 			EventId:          "event_id",
 			EventType:        "event_type",
 			ProjectId:        "project_id",
+			ProjectName:      "project_name",
 			TaskNo:           "task_no",
 			TaskTitle:        "task_title",
 			TaskDesc:         "task_desc",

+ 435 - 0
opms_parent/app/dao/opsdev/internal/ops_event_task_work_hour.go

@@ -0,0 +1,435 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. DO NOT EDIT THIS FILE MANUALLY.
+// ==========================================================================
+
+package internal
+
+import (
+	"context"
+	"database/sql"
+	"time"
+
+	"dashoo.cn/opms_parent/app/model/opsdev"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+)
+
+// OpsEventTaskWorkHourDao is the manager for logic model data accessing
+// and custom defined data operations functions management.
+type OpsEventTaskWorkHourDao struct {
+	gmvc.M
+	DB      gdb.DB
+	Table   string
+	Columns opsEventTaskWorkHourColumns
+}
+
+// OpsEventTaskWorkHourColumns defines and stores column names for table ops_event_task_work_hour.
+type opsEventTaskWorkHourColumns struct {
+	Id             string // ID
+	TaskId         string // 关联事件ID(event表id)
+	OpsUserId      string // 执行人ID
+	OpsUserName    string // 执行人姓名
+	ActualWorkDate string // 工作日期
+	ActualWorkHour string // 工作时长(小时)
+	Remark         string // 工作说明
+	CreatedBy      string // 创建人ID
+	CreatedName    string // 创建人
+	CreatedTime    string // 创建时间
+	UpdatedBy      string // 更新人ID
+	UpdatedName    string // 更新人
+	UpdatedTime    string // 更新时间
+	DeletedTime    string // 删除时间
+}
+
+var (
+	// OpsEventTaskWorkHour is globally public accessible object for table ops_event_task_work_hour operations.
+	OpsEventTaskWorkHour = OpsEventTaskWorkHourDao{
+		M:     g.DB("default").Model("ops_event_task_work_hour").Safe(),
+		DB:    g.DB("default"),
+		Table: "ops_event_task_work_hour",
+		Columns: opsEventTaskWorkHourColumns{
+			Id:             "id",
+			TaskId:         "task_id",
+			OpsUserId:      "ops_user_id",
+			OpsUserName:    "ops_user_name",
+			ActualWorkDate: "actual_work_date",
+			ActualWorkHour: "actual_work_hour",
+			Remark:         "remark",
+			CreatedBy:      "created_by",
+			CreatedName:    "created_name",
+			CreatedTime:    "created_time",
+			UpdatedBy:      "updated_by",
+			UpdatedName:    "updated_name",
+			UpdatedTime:    "updated_time",
+			DeletedTime:    "deleted_time",
+		},
+	}
+)
+
+func NewOpsEventTaskWorkHourDao(tenant string) OpsEventTaskWorkHourDao {
+	var dao OpsEventTaskWorkHourDao
+	dao = OpsEventTaskWorkHourDao{
+		M:     g.DB(tenant).Model("ops_event_task_work_hour").Safe(),
+		DB:    g.DB(tenant),
+		Table: "ops_event_task_work_hour",
+		Columns: opsEventTaskWorkHourColumns{
+			Id:             "id",
+			TaskId:         "task_id",
+			OpsUserId:      "ops_user_id",
+			OpsUserName:    "ops_user_name",
+			ActualWorkDate: "actual_work_date",
+			ActualWorkHour: "actual_work_hour",
+			Remark:         "remark",
+			CreatedBy:      "created_by",
+			CreatedName:    "created_name",
+			CreatedTime:    "created_time",
+			UpdatedBy:      "updated_by",
+			UpdatedName:    "updated_name",
+			UpdatedTime:    "updated_time",
+			DeletedTime:    "deleted_time",
+		},
+	}
+	return dao
+}
+
+// Ctx is a chaining function, which creates and returns a new DB that is a shallow copy
+// of current DB object and with given context in it.
+// Note that this returned DB object can be used only once, so do not assign it to
+// a global or package variable for long using.
+func (d *OpsEventTaskWorkHourDao) Ctx(ctx context.Context) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Ctx(ctx)}
+}
+
+// As sets an alias name for current table.
+func (d *OpsEventTaskWorkHourDao) As(as string) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (d *OpsEventTaskWorkHourDao) TX(tx *gdb.TX) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (d *OpsEventTaskWorkHourDao) Master() *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (d *OpsEventTaskWorkHourDao) Slave() *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Slave()}
+}
+
+// Args sets custom arguments for model operation.
+func (d *OpsEventTaskWorkHourDao) Args(args ...interface{}) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Args(args...)}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (d *OpsEventTaskWorkHourDao) LeftJoin(table ...string) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (d *OpsEventTaskWorkHourDao) RightJoin(table ...string) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (d *OpsEventTaskWorkHourDao) InnerJoin(table ...string) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct.
+func (d *OpsEventTaskWorkHourDao) Fields(fieldNamesOrMapStruct ...interface{}) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Fields(fieldNamesOrMapStruct...)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct.
+func (d *OpsEventTaskWorkHourDao) FieldsEx(fieldNamesOrMapStruct ...interface{}) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.FieldsEx(fieldNamesOrMapStruct...)}
+}
+
+// Option sets the extra operation option for the model.
+func (d *OpsEventTaskWorkHourDao) Option(option int) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (d *OpsEventTaskWorkHourDao) OmitEmpty() *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (d *OpsEventTaskWorkHourDao) Filter() *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (d *OpsEventTaskWorkHourDao) Where(where interface{}, args ...interface{}) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Where(where, args...)}
+}
+
+// WherePri does the same logic as M.Where except that if the parameter <where>
+// is a single condition like int/string/float/slice, it treats the condition as the primary
+// key value. That is, if primary key is "id" and given <where> parameter as "123", the
+// WherePri function treats the condition as "id=123", but M.Where treats the condition
+// as string "123".
+func (d *OpsEventTaskWorkHourDao) WherePri(where interface{}, args ...interface{}) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.WherePri(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (d *OpsEventTaskWorkHourDao) And(where interface{}, args ...interface{}) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (d *OpsEventTaskWorkHourDao) Or(where interface{}, args ...interface{}) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (d *OpsEventTaskWorkHourDao) Group(groupBy string) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (d *OpsEventTaskWorkHourDao) Order(orderBy ...string) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (d *OpsEventTaskWorkHourDao) Limit(limit ...int) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (d *OpsEventTaskWorkHourDao) Offset(offset int) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (d *OpsEventTaskWorkHourDao) Page(page, limit int) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (d *OpsEventTaskWorkHourDao) Batch(batch int) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (d *OpsEventTaskWorkHourDao) Cache(duration time.Duration, name ...string) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (d *OpsEventTaskWorkHourDao) Data(data ...interface{}) *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*model.OpsEventTaskWorkHour.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of M.Where function,
+// see M.Where.
+func (d *OpsEventTaskWorkHourDao) All(where ...interface{}) ([]*opsdev.OpsEventTaskWorkHour, error) {
+	all, err := d.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*opsdev.OpsEventTaskWorkHour
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *model.OpsEventTaskWorkHour.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of M.Where function,
+// see M.Where.
+func (d *OpsEventTaskWorkHourDao) One(where ...interface{}) (*opsdev.OpsEventTaskWorkHour, error) {
+	one, err := d.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *opsdev.OpsEventTaskWorkHour
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by M.WherePri and M.One.
+// Also see M.WherePri and M.One.
+func (d *OpsEventTaskWorkHourDao) FindOne(where ...interface{}) (*opsdev.OpsEventTaskWorkHour, error) {
+	one, err := d.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *opsdev.OpsEventTaskWorkHour
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by M.WherePri and M.All.
+// Also see M.WherePri and M.All.
+func (d *OpsEventTaskWorkHourDao) FindAll(where ...interface{}) ([]*opsdev.OpsEventTaskWorkHour, error) {
+	all, err := d.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*opsdev.OpsEventTaskWorkHour
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Struct retrieves one record from table and converts it into given struct.
+// The parameter <pointer> should be type of *struct/**struct. If type **struct is given,
+// it can create the struct internally during converting.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+//
+// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
+// from table and <pointer> is not nil.
+//
+// Eg:
+// user := new(User)
+// err  := dao.User.Where("id", 1).Struct(user)
+//
+// user := (*User)(nil)
+// err  := dao.User.Where("id", 1).Struct(&user)
+func (d *OpsEventTaskWorkHourDao) Struct(pointer interface{}, where ...interface{}) error {
+	return d.M.Struct(pointer, where...)
+}
+
+// Structs retrieves records from table and converts them into given struct slice.
+// The parameter <pointer> should be type of *[]struct/*[]*struct. It can create and fill the struct
+// slice internally during converting.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+//
+// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
+// from table and <pointer> is not empty.
+//
+// Eg:
+// users := ([]User)(nil)
+// err   := dao.User.Structs(&users)
+//
+// users := ([]*User)(nil)
+// err   := dao.User.Structs(&users)
+func (d *OpsEventTaskWorkHourDao) Structs(pointer interface{}, where ...interface{}) error {
+	return d.M.Structs(pointer, where...)
+}
+
+// Scan automatically calls Struct or Structs function according to the type of parameter <pointer>.
+// It calls function Struct if <pointer> is type of *struct/**struct.
+// It calls function Structs if <pointer> is type of *[]struct/*[]*struct.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+//
+// Note that it returns sql.ErrNoRows if there's no record retrieved and given pointer is not empty or nil.
+//
+// Eg:
+// user  := new(User)
+// err   := dao.User.Where("id", 1).Scan(user)
+//
+// user  := (*User)(nil)
+// err   := dao.User.Where("id", 1).Scan(&user)
+//
+// users := ([]User)(nil)
+// err   := dao.User.Scan(&users)
+//
+// users := ([]*User)(nil)
+// err   := dao.User.Scan(&users)
+func (d *OpsEventTaskWorkHourDao) Scan(pointer interface{}, where ...interface{}) error {
+	return d.M.Scan(pointer, where...)
+}
+
+// Chunk iterates the table with given size and callback function.
+func (d *OpsEventTaskWorkHourDao) Chunk(limit int, callback func(entities []*opsdev.OpsEventTaskWorkHour, err error) bool) {
+	d.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*opsdev.OpsEventTaskWorkHour
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (d *OpsEventTaskWorkHourDao) LockUpdate() *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (d *OpsEventTaskWorkHourDao) LockShared() *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (d *OpsEventTaskWorkHourDao) Unscoped() *OpsEventTaskWorkHourDao {
+	return &OpsEventTaskWorkHourDao{M: d.M.Unscoped()}
+}

+ 36 - 0
opms_parent/app/dao/opsdev/ops_event_task_work_hour.go

@@ -0,0 +1,36 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package opsdev
+
+import (
+	"dashoo.cn/opms_parent/app/dao/opsdev/internal"
+)
+
+// opsEventTaskWorkHourDao is the manager for logic model data accessing
+// and custom defined data operations functions management. You can define
+// methods on it to extend its functionality as you wish.
+type opsEventTaskWorkHourDao struct {
+	internal.OpsEventTaskWorkHourDao
+}
+
+var (
+	// OpsEventTaskWorkHour is globally public accessible object for table ops_event_task_work_hour operations.
+	OpsEventTaskWorkHour = opsEventTaskWorkHourDao{
+		internal.OpsEventTaskWorkHour,
+	}
+)
+
+type OpsEventTaskWorkHourDao struct {
+	internal.OpsEventTaskWorkHourDao
+}
+
+func NewOpsEventTaskWorkHourDao(tenant string) *OpsEventTaskWorkHourDao {
+	dao := internal.NewOpsEventTaskWorkHourDao(tenant)
+	return &OpsEventTaskWorkHourDao{
+		dao,
+	}
+}
+
+// Fill with you ideas below.

+ 1 - 0
opms_parent/app/handler/base/distributor_contact.go

@@ -2,6 +2,7 @@ package base
 
 import (
 	"context"
+
 	model "dashoo.cn/opms_parent/app/model/base"
 	service "dashoo.cn/opms_parent/app/service/base"
 

+ 1 - 0
opms_parent/app/handler/base/distributor_target.go

@@ -2,6 +2,7 @@ package base
 
 import (
 	"context"
+
 	model "dashoo.cn/opms_parent/app/model/base"
 	service "dashoo.cn/opms_parent/app/service/base"
 

+ 1 - 0
opms_parent/app/handler/base/region.go

@@ -2,6 +2,7 @@ package base
 
 import (
 	"context"
+
 	"github.com/gogf/gf/util/gvalid"
 
 	"dashoo.cn/common_definition/comm_def"

+ 1 - 0
opms_parent/app/handler/contract/ctr_contract_sale_target.go

@@ -2,6 +2,7 @@ package contract
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	model "dashoo.cn/opms_parent/app/model/contract"
 	service "dashoo.cn/opms_parent/app/service/contract"

+ 1 - 0
opms_parent/app/handler/cust/cust_customer_bid_record.go

@@ -2,6 +2,7 @@ package cust
 
 import (
 	"context"
+
 	model "dashoo.cn/opms_parent/app/model/cust"
 	service "dashoo.cn/opms_parent/app/service/cust"
 	"github.com/gogf/gf/frame/g"

+ 1 - 0
opms_parent/app/handler/cust/cust_customer_invoice_header.go

@@ -2,6 +2,7 @@ package cust
 
 import (
 	"context"
+
 	model "dashoo.cn/opms_parent/app/model/cust"
 	service "dashoo.cn/opms_parent/app/service/cust"
 

+ 1 - 0
opms_parent/app/handler/cust/product_consult_record.go

@@ -2,6 +2,7 @@ package cust
 
 import (
 	"context"
+
 	model "dashoo.cn/opms_parent/app/model/cust"
 	service "dashoo.cn/opms_parent/app/service/cust"
 

+ 3 - 2
opms_parent/app/handler/dingtalk/ding_event.go

@@ -2,6 +2,9 @@ package dingtalk
 
 import (
 	"context"
+	"database/sql"
+	"fmt"
+
 	"dashoo.cn/opms_libary/plugin/dingtalk"
 	dingContext "dashoo.cn/opms_libary/plugin/dingtalk/context"
 	"dashoo.cn/opms_libary/plugin/dingtalk/message"
@@ -14,8 +17,6 @@ import (
 	projService "dashoo.cn/opms_parent/app/service/proj"
 	workService "dashoo.cn/opms_parent/app/service/work"
 	workflowServer "dashoo.cn/opms_parent/app/service/workflow"
-	"database/sql"
-	"fmt"
 	"github.com/gogf/gf/os/glog"
 	"github.com/gogf/gf/util/gconv"
 )

+ 1 - 0
opms_parent/app/handler/home/home.go

@@ -2,6 +2,7 @@ package home
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	model "dashoo.cn/opms_parent/app/model/home"
 	service "dashoo.cn/opms_parent/app/service/home"

+ 14 - 0
opms_parent/app/handler/opsdev/delivery_project.go

@@ -28,6 +28,20 @@ func (h *DeliveryProjectHandler) GetList(ctx context.Context, req *opsdevmodel.O
 	return nil
 }
 
+// GetListAll 获取所有交付项目列表(不带权限校验,用于下拉选择等场景)
+func (h *DeliveryProjectHandler) GetListAll(ctx context.Context, req *opsdevmodel.OpsDeliveryProjectSearchReq, rsp *comm_def.CommonMsg) error {
+	srv, err := opsdevSrv.NewDeliveryProjectService(ctx)
+	if err != nil {
+		return err
+	}
+	total, list, err := srv.GetListAll(req)
+	if err != nil {
+		return err
+	}
+	rsp.Data = g.Map{"list": list, "total": total}
+	return nil
+}
+
 // GetEntityById 根据ID获取详情
 func (h *DeliveryProjectHandler) GetEntityById(ctx context.Context, req *comm_def.IdReq, rsp *comm_def.CommonMsg) error {
 	srv, err := opsdevSrv.NewDeliveryProjectService(ctx)

+ 2 - 2
opms_parent/app/handler/opsdev/delivery_project_event.go

@@ -4,11 +4,11 @@ import (
 	"context"
 
 	"dashoo.cn/common_definition/comm_def"
+	"dashoo.cn/opms_libary/myerrors"
 	eventmodel "dashoo.cn/opms_parent/app/model/opsdev"
 	services "dashoo.cn/opms_parent/app/service/opsdev"
 	"github.com/gogf/gf/frame/g"
 	"github.com/gogf/gf/util/gvalid"
-	"dashoo.cn/opms_libary/myerrors"
 )
 
 // DeliveryProjectEventHandler 交付项目事件接口处理类
@@ -210,4 +210,4 @@ func (h *DeliveryProjectEventHandler) Cancel(ctx context.Context, req *eventmode
 
 	rsp.Data = g.Map{"msg": "作废成功"}
 	return nil
-}
+}

+ 34 - 1
opms_parent/app/handler/opsdev/ops_event_task.go

@@ -4,9 +4,9 @@ import (
 	"context"
 
 	"dashoo.cn/common_definition/comm_def"
+	"dashoo.cn/opms_libary/myerrors"
 	opsdevmodel "dashoo.cn/opms_parent/app/model/opsdev"
 	services "dashoo.cn/opms_parent/app/service/opsdev"
-	"dashoo.cn/opms_libary/myerrors"
 	"github.com/gogf/gf/frame/g"
 	"github.com/gogf/gf/util/gvalid"
 )
@@ -256,3 +256,36 @@ func (h *OpsEventTaskHandler) AddRecord(ctx context.Context, req *opsdevmodel.Op
 	rsp.Data = g.Map{"msg": "添加成功"}
 	return nil
 }
+
+// AddWorkHour 添加工时登记
+func (h *OpsEventTaskHandler) AddWorkHour(ctx context.Context, req *opsdevmodel.OpsEventTaskWorkHourAddReq, rsp *comm_def.CommonMsg) error {
+	if err := gvalid.CheckStruct(ctx, req, nil); err != nil {
+		return myerrors.ValidError(err.Error())
+	}
+	s, err := services.NewOpsEventTaskService(ctx)
+	if err != nil {
+		return err
+	}
+	if err := s.AddWorkHour(req); err != nil {
+		return err
+	}
+	rsp.Data = g.Map{"msg": "工时登记成功"}
+	return nil
+}
+
+// GetWorkHourList 获取工时登记列表
+func (h *OpsEventTaskHandler) GetWorkHourList(ctx context.Context, req *opsdevmodel.OpsEventTaskWorkHourListReq, rsp *comm_def.CommonMsg) error {
+	if err := gvalid.CheckStruct(ctx, req, nil); err != nil {
+		return myerrors.ValidError(err.Error())
+	}
+	s, err := services.NewOpsEventTaskService(ctx)
+	if err != nil {
+		return err
+	}
+	list, err := s.GetWorkHourList(req)
+	if err != nil {
+		return err
+	}
+	rsp.Data = g.Map{"list": list}
+	return nil
+}

+ 1 - 0
opms_parent/app/handler/plat/followup_comment.go

@@ -2,6 +2,7 @@ package plat
 
 import (
 	"context"
+
 	"dashoo.cn/opms_libary/myerrors"
 
 	"dashoo.cn/common_definition/comm_def"

+ 1 - 0
opms_parent/app/handler/plat/questionnaire.go

@@ -2,6 +2,7 @@ package plat
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	model "dashoo.cn/opms_parent/app/model/plat"
 	server "dashoo.cn/opms_parent/app/service/plat"

+ 1 - 0
opms_parent/app/handler/proj/business.go

@@ -2,6 +2,7 @@ package base
 
 import (
 	"context"
+
 	"dashoo.cn/opms_libary/multipart"
 	"dashoo.cn/opms_libary/myerrors"
 	"github.com/gogf/gf/frame/g"

+ 1 - 0
opms_parent/app/handler/proj/business_contact.go

@@ -2,6 +2,7 @@ package base
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	"dashoo.cn/opms_libary/myerrors"
 	"github.com/gogf/gf/frame/g"

+ 1 - 0
opms_parent/app/handler/proj/business_file.go

@@ -2,6 +2,7 @@ package base
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	"dashoo.cn/opms_libary/myerrors"
 	"github.com/gogf/gf/util/gvalid"

+ 1 - 0
opms_parent/app/handler/proj/business_team.go

@@ -2,6 +2,7 @@ package base
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	"dashoo.cn/opms_libary/myerrors"
 	"github.com/gogf/gf/util/gvalid"

+ 1 - 0
opms_parent/app/handler/sysreport/sys_report.go

@@ -2,6 +2,7 @@ package sysreport
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	model "dashoo.cn/opms_parent/app/model/contract"
 	"dashoo.cn/opms_parent/app/model/sys_report"

+ 1 - 0
opms_parent/app/handler/sysreport/sys_role_report.go

@@ -2,6 +2,7 @@ package sysreport
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	model "dashoo.cn/opms_parent/app/model/contract"
 	"dashoo.cn/opms_parent/app/model/sys_report"

+ 1 - 0
opms_parent/app/handler/sysreport/work_order_report.go

@@ -2,6 +2,7 @@ package sysreport
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	"dashoo.cn/opms_parent/app/model/sys_report"
 	service "dashoo.cn/opms_parent/app/service/sys_report"

+ 1 - 0
opms_parent/app/handler/train/sale_apply.go

@@ -2,6 +2,7 @@ package train
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	model "dashoo.cn/opms_parent/app/model/train"
 	service "dashoo.cn/opms_parent/app/service/train"

+ 1 - 0
opms_parent/app/handler/train/sale_apply_summary.go

@@ -2,6 +2,7 @@ package train
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	model "dashoo.cn/opms_parent/app/model/train"
 	service "dashoo.cn/opms_parent/app/service/train"

+ 1 - 0
opms_parent/app/handler/train/train_head_office.go

@@ -2,6 +2,7 @@ package train
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	"dashoo.cn/opms_libary/myerrors"
 	"github.com/gogf/gf/frame/g"

+ 1 - 0
opms_parent/app/handler/work/work_order.go

@@ -2,6 +2,7 @@ package work
 
 import (
 	"context"
+
 	"dashoo.cn/opms_libary/multipart"
 	"dashoo.cn/opms_libary/myerrors"
 	"github.com/gogf/gf/util/gconv"

+ 1 - 0
opms_parent/app/handler/work/work_order_type.go

@@ -2,6 +2,7 @@ package work
 
 import (
 	"context"
+
 	"dashoo.cn/common_definition/comm_def"
 	"dashoo.cn/opms_libary/myerrors"
 	"github.com/gogf/gf/frame/g"

+ 1 - 0
opms_parent/app/model/opsdev/internal/ops_event_task.go

@@ -14,6 +14,7 @@ type OpsEventTask struct {
 	EventId          int         `orm:"event_id"           json:"eventId"`          // 关联事件ID(event表id)
 	EventType        string      `orm:"event_type"         json:"eventType"`        // 关联事件类型(10 交付 20 运维)
 	ProjectId        int         `orm:"project_id"         json:"projectId"`        // 交付项目ID
+	ProjectName      string      `orm:"project_name"       json:"projectName"`      // 项目名称
 	TaskNo           string      `orm:"task_no"            json:"taskNo"`           // 任务编码
 	TaskTitle        string      `orm:"task_title"         json:"taskTitle"`        // 任务标题
 	TaskDesc         string      `orm:"task_desc"          json:"taskDesc"`         // 任务描述

+ 27 - 0
opms_parent/app/model/opsdev/internal/ops_event_task_work_hour.go

@@ -0,0 +1,27 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. DO NOT EDIT THIS FILE MANUALLY.
+// ==========================================================================
+
+package internal
+
+import (
+	"github.com/gogf/gf/os/gtime"
+)
+
+// OpsEventTaskWorkHour is the golang structure for table ops_event_task_work_hour.
+type OpsEventTaskWorkHour struct {
+	Id             int         `orm:"id,primary"       json:"id"`             // ID
+	TaskId         int         `orm:"task_id"          json:"taskId"`         // 关联事件ID(event表id)
+	OpsUserId      int         `orm:"ops_user_id"      json:"opsUserId"`      // 执行人ID
+	OpsUserName    string      `orm:"ops_user_name"    json:"opsUserName"`    // 执行人姓名
+	ActualWorkDate *gtime.Time `orm:"actual_work_date" json:"actualWorkDate"` // 工作日期
+	ActualWorkHour float64     `orm:"actual_work_hour" json:"actualWorkHour"` // 工作时长(小时)
+	Remark         string      `orm:"remark"           json:"remark"`         // 工作说明
+	CreatedBy      int         `orm:"created_by"       json:"createdBy"`      // 创建人ID
+	CreatedName    string      `orm:"created_name"     json:"createdName"`    // 创建人
+	CreatedTime    *gtime.Time `orm:"created_time"     json:"createdTime"`    // 创建时间
+	UpdatedBy      int         `orm:"updated_by"       json:"updatedBy"`      // 更新人ID
+	UpdatedName    string      `orm:"updated_name"     json:"updatedName"`    // 更新人
+	UpdatedTime    *gtime.Time `orm:"updated_time"     json:"updatedTime"`    // 更新时间
+	DeletedTime    *gtime.Time `orm:"deleted_time"     json:"deletedTime"`    // 删除时间
+}

+ 1 - 1
opms_parent/app/model/opsdev/ops_delivery_project_event.go

@@ -37,7 +37,7 @@ type OpsDeliveryProjectEventSearchReq struct {
 	DeliveryEventResult []string `json:"deliveryEventResult"` // 事件结果(多选)
 	FeedbackSource      string   `json:"feedbackSource"`      // 反馈来源
 	FeedbackReporter    string   `json:"feedbackReporter"`    // 反馈人
-	OpsUserName         string   `json:"opsUserName"`         // 负责人
+	OpsUserName         []string `json:"opsUserName"`         // 负责人(多选)
 	FeedbackDateStart   string   `json:"feedbackDateStart"`   // 反馈时间开始
 	FeedbackDateEnd     string   `json:"feedbackDateEnd"`     // 反馈时间结束
 	CompleteTimeStart   string   `json:"completeTimeStart"`   // 处理时间开始

+ 25 - 1
opms_parent/app/model/opsdev/ops_event_task.go

@@ -61,7 +61,7 @@ type OpsEventTaskSearchReq struct {
 	TaskType         []string    `json:"taskType"`         // 任务类型(多选)
 	TaskStatus       []string    `json:"taskStatus"`       // 任务状态(多选)
 	Priority         []string    `json:"priority"`         // 优先级(多选)
-	OpsUserName      string      `json:"opsUserName"`      // 执行人姓名(模糊查询
+	OpsUserName      []string    `json:"opsUserName"`      // 执行人姓名(多选
 	SortFields       []SortField `json:"sortFields"`       // 排序字段
 	// 计划结束日期范围
 	PlanEndDateStart string `json:"planEndDateStart"` // 计划结束开始日期
@@ -244,3 +244,27 @@ type OpsEventTaskReleaseRsp struct {
 	ProjectId   int    `json:"projectId"`   // 项目ID
 	CreatedTime string `json:"createdTime"` // 创建时间
 }
+
+// OpsEventTaskWorkHourAddReq 添加工时登记请求
+type OpsEventTaskWorkHourAddReq struct {
+	TaskId      int     `json:"taskId" v:"required#任务ID不能为空"`      // 任务ID
+	WorkDate    string  `json:"workDate" v:"required#工作日期不能为空"`    // 工作日期
+	ActualHour  float64 `json:"actualHour" v:"required#实际工时不能为空"` // 实际工时
+	Remark      string  `json:"remark" v:"required#工作进展不能为空"`     // 工作进展
+}
+
+// OpsEventTaskWorkHourListReq 查询工时登记列表请求
+type OpsEventTaskWorkHourListReq struct {
+	TaskId int `json:"taskId" v:"required#任务ID不能为空"` // 任务ID
+}
+
+// OpsEventTaskWorkHourRsp 工时登记响应
+type OpsEventTaskWorkHourRsp struct {
+	Id             int     `json:"id"`             // ID
+	TaskId         int     `json:"taskId"`         // 任务ID
+	WorkDate       string  `json:"workDate"`       // 工作日期
+	ActualHour     float64 `json:"actualHour"`     // 实际工时
+	Remark         string  `json:"remark"`         // 工作进展
+	CreatedName    string  `json:"createdName"`    // 登记人
+	CreatedTime    string  `json:"createdTime"`    // 登记时间
+}

+ 14 - 0
opms_parent/app/model/opsdev/ops_event_task_work_hour.go

@@ -0,0 +1,14 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. Fill this file as you wish.
+// ==========================================================================
+
+package opsdev
+
+import (
+	"dashoo.cn/opms_parent/app/model/opsdev/internal"
+)
+
+// OpsEventTaskWorkHour is the golang structure for table ops_event_task_work_hour.
+type OpsEventTaskWorkHour internal.OpsEventTaskWorkHour
+
+// Fill with you ideas below.

+ 78 - 1
opms_parent/app/service/opsdev/delivery_project.go

@@ -78,7 +78,84 @@ func (s *DeliveryProjectService) GetList(req *opsdevmodel.OpsDeliveryProjectSear
 		m = m.Where("project_status", req.Status)
 	}
 	if req.ProductLine != "" {
-		m = m.Where("product_line", req.ProductLine)
+		productLineList := strings.Split(req.ProductLine, ",")
+		m = m.Where("product_line", productLineList)
+	}
+	if req.DeliveryUserId > 0 {
+		m = m.Where("delivery_user_id", req.DeliveryUserId)
+	}
+	if req.SalesUserId > 0 {
+		m = m.Where("sales_user_id", req.SalesUserId)
+	}
+
+	// 获取总数
+	total, err := m.Count()
+	if err != nil {
+		return 0, nil, err
+	}
+
+	// 分页查询
+	if req.PageNum <= 0 {
+		req.PageNum = 1
+	}
+	if req.PageSize <= 0 {
+		req.PageSize = 20
+	}
+
+	// 排序
+	orderBy := "created_time desc"
+	if req.SortField != "" && req.SortOrder != "" {
+		orderBy = req.SortField + " " + req.SortOrder
+	}
+
+	var list []*opsdevmodel.OpsDeliveryProject
+	err = m.Page(req.PageNum, req.PageSize).Order(orderBy).Scan(&list)
+	if err != nil {
+		return 0, nil, err
+	}
+
+	return int64(total), list, nil
+}
+
+// GetListAll 获取所有项目列表(不带权限校验,仅用于下拉选择等场景)
+func (s *DeliveryProjectService) GetListAll(req *opsdevmodel.OpsDeliveryProjectSearchReq) (int64, []*opsdevmodel.OpsDeliveryProject, error) {
+	m := s.Dao.Ctx(s.Ctx)
+
+	// 项目状态筛选(支持多选,逗号分隔)
+	if req.ProjectStatus != "" {
+		statusList := strings.Split(req.ProjectStatus, ",")
+		if len(statusList) > 0 {
+			m = m.Where("project_status", statusList)
+		}
+	}
+
+	// 项目名称模糊查询
+	if req.ProjectName != "" {
+		m = m.Where("project_name like ?", "%"+req.ProjectName+"%")
+	}
+
+	// 销售负责人模糊查询
+	if req.SalesUserName != "" {
+		m = m.Where("sales_user_name like ?", "%"+req.SalesUserName+"%")
+	}
+
+	// 交付负责人模糊查询
+	if req.DeliveryUserName != "" {
+		m = m.Where("delivery_user_name like ?", "%"+req.DeliveryUserName+"%")
+	}
+
+	// 关键词搜索(项目名称、合同编号)
+	if req.KeyWords != "" {
+		m = m.Where("project_name like ? OR contract_no like ?", "%"+req.KeyWords+"%", "%"+req.KeyWords+"%")
+	}
+
+	// 其他查询条件
+	if req.Status != "" {
+		m = m.Where("project_status", req.Status)
+	}
+	if req.ProductLine != "" {
+		productLineList := strings.Split(req.ProductLine, ",")
+		m = m.Where("product_line", productLineList)
 	}
 	if req.DeliveryUserId > 0 {
 		m = m.Where("delivery_user_id", req.DeliveryUserId)

+ 3 - 3
opms_parent/app/service/opsdev/delivery_project_event.go

@@ -84,9 +84,9 @@ func (s *DeliveryProjectEventService) GetList(req *eventmodel.OpsDeliveryProject
 		db = db.Where(s.EventDao.Columns.FeedbackReporter+" like ?", "%"+req.FeedbackReporter+"%")
 	}
 
-	// 负责人模糊查询
-	if req.OpsUserName != "" {
-		db = db.Where(s.EventDao.Columns.OpsUserName+" like ?", "%"+req.OpsUserName+"%")
+	// 负责人筛选(多选精确匹配)
+	if len(req.OpsUserName) > 0 {
+		db = db.Where(s.EventDao.Columns.OpsUserName+" in (?)", req.OpsUserName)
 	}
 
 	// 反馈时间范围

+ 147 - 43
opms_parent/app/service/opsdev/ops_event_task.go

@@ -23,6 +23,7 @@ type OpsEventTaskService struct {
 	AttachmentDao *opsdevdao.OpsEventTaskAttachmentDao
 	ProjectDao    *opsdevdao.OpsDeliveryProjectDao
 	ReleaseDao    *opsdevdao.OpsEventTaskReleaseDao
+	WorkHourDao   *opsdevdao.OpsEventTaskWorkHourDao
 }
 
 // NewOpsEventTaskService 初始化service
@@ -36,6 +37,7 @@ func NewOpsEventTaskService(ctx context.Context) (svc *OpsEventTaskService, err
 	svc.AttachmentDao = opsdevdao.NewOpsEventTaskAttachmentDao(svc.Tenant)
 	svc.ProjectDao = opsdevdao.NewOpsDeliveryProjectDao(svc.Tenant)
 	svc.ReleaseDao = opsdevdao.NewOpsEventTaskReleaseDao(svc.Tenant)
+	svc.WorkHourDao = opsdevdao.NewOpsEventTaskWorkHourDao(svc.Tenant)
 	return svc, nil
 }
 
@@ -68,9 +70,9 @@ func (s *OpsEventTaskService) GetList(req *opsdevmodel.OpsEventTaskSearchReq) (t
 		db = db.Where(s.TaskDao.Columns.Priority+" in (?)", req.Priority)
 	}
 
-	// 执行人模糊查询
-	if req.OpsUserName != "" {
-		db = db.Where(s.TaskDao.Columns.OpsUserName+" like ?", "%"+req.OpsUserName+"%")
+	// 执行人筛选(多选)
+	if len(req.OpsUserName) > 0 {
+		db = db.Where(s.TaskDao.Columns.OpsUserName+" in (?)", req.OpsUserName)
 	}
 
 	// 计划结束日期范围筛选
@@ -139,22 +141,6 @@ func (s *OpsEventTaskService) GetList(req *opsdevmodel.OpsEventTaskSearchReq) (t
 		return 0, nil, myerrors.DbError("数据转换失败")
 	}
 
-	// 填充项目名称
-	for _, item := range list {
-		if item.ProjectId > 0 {
-			var project opsdevmodel.OpsDeliveryProject
-			err := s.ProjectDao.FieldsEx(s.ProjectDao.Columns.DeletedTime).
-				WherePri(s.ProjectDao.Columns.Id, item.ProjectId).
-				Scan(&project)
-			if err != nil {
-				g.Log().Error(err)
-			} else if project.Id > 0 {
-				item.ProjectName = project.ProjectName
-				item.ContractNo = project.ContractNo
-			}
-		}
-	}
-
 	return
 }
 
@@ -162,15 +148,17 @@ func (s *OpsEventTaskService) GetList(req *opsdevmodel.OpsEventTaskSearchReq) (t
 func (s *OpsEventTaskService) getSortColumnName(field string) string {
 	// 前端字段名到数据库列名的映射
 	fieldMap := map[string]string{
-		"taskTitle":     s.TaskDao.Columns.TaskTitle,
-		"taskType":      s.TaskDao.Columns.TaskType,
-		"taskStatus":    s.TaskDao.Columns.TaskStatus,
-		"priority":      s.TaskDao.Columns.Priority,
-		"opsUserName":   s.TaskDao.Columns.OpsUserName,
-		"planStartTime": s.TaskDao.Columns.PlanStartTime,
-		"planEndTime":   s.TaskDao.Columns.PlanEndTime,
-		"completeTime":  s.TaskDao.Columns.CompleteTime,
-		"createdTime":   s.TaskDao.Columns.CreatedTime,
+		"taskTitle":      s.TaskDao.Columns.TaskTitle,
+		"functionName":   s.TaskDao.Columns.FunctionName,
+		"taskType":       s.TaskDao.Columns.TaskType,
+		"taskStatus":     s.TaskDao.Columns.TaskStatus,
+		"priority":       s.TaskDao.Columns.Priority,
+		"opsUserName":    s.TaskDao.Columns.OpsUserName,
+		"planStartTime":  s.TaskDao.Columns.PlanStartTime,
+		"planEndTime":    s.TaskDao.Columns.PlanEndTime,
+		"completeTime":   s.TaskDao.Columns.CompleteTime,
+		"releaseVersion": s.TaskDao.Columns.ReleaseVersion,
+		"createdTime":    s.TaskDao.Columns.CreatedTime,
 	}
 
 	if colName, ok := fieldMap[field]; ok {
@@ -194,6 +182,7 @@ func (s *OpsEventTaskService) Create(req *opsdevmodel.OpsEventTaskAddReq) error
 	data := g.Map{
 		s.TaskDao.Columns.TaskNo:           taskNo,
 		s.TaskDao.Columns.ProjectId:        req.ProjectId,
+		s.TaskDao.Columns.ProjectName:      s.getProjectName(req.ProjectId),
 		s.TaskDao.Columns.TaskTitle:        req.TaskTitle,
 		s.TaskDao.Columns.TaskDesc:         req.TaskDesc,
 		s.TaskDao.Columns.FunctionName:     req.FunctionName,
@@ -382,20 +371,6 @@ func (s *OpsEventTaskService) GetById(id int) (*opsdevmodel.OpsEventTaskRsp, err
 		return nil, myerrors.DbError("数据转换失败")
 	}
 
-	// 查询关联的项目信息
-	if entity.ProjectId > 0 {
-		var project opsdevmodel.OpsDeliveryProject
-		err := s.ProjectDao.FieldsEx(s.ProjectDao.Columns.DeletedTime).
-			WherePri(s.ProjectDao.Columns.Id, entity.ProjectId).
-			Scan(&project)
-		if err != nil {
-			g.Log().Error(err)
-		} else if project.Id > 0 {
-			rsp.ProjectName = project.ProjectName
-			rsp.ContractNo = project.ContractNo
-		}
-	}
-
 	return &rsp, nil
 }
 
@@ -544,6 +519,15 @@ func (s *OpsEventTaskService) Complete(req *opsdevmodel.OpsEventTaskCompleteReq)
 	// 补齐审计字段
 	service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
 
+	// 预生成下游任务编号(避免事务内查询重复)
+	var devTaskNo, testTaskNo string
+	if entity.TaskType == opsdevmodel.TaskTypeReqReview {
+		devTaskNo = s.generateTaskNo()
+	}
+	if entity.TaskType == opsdevmodel.TaskTypeFeatureDev {
+		testTaskNo = s.generateTaskNo()
+	}
+
 	// 使用事务
 	return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
 		// 1. 更新任务状态
@@ -615,7 +599,9 @@ func (s *OpsEventTaskService) Complete(req *opsdevmodel.OpsEventTaskCompleteReq)
 		// 4.1 需求评审完成时,自动创建功能开发任务
 		if entity.TaskType == opsdevmodel.TaskTypeReqReview {
 			devTaskData := g.Map{
+				s.TaskDao.Columns.TaskNo:       devTaskNo,
 				s.TaskDao.Columns.ProjectId:    entity.ProjectId,
+				s.TaskDao.Columns.ProjectName:  entity.ProjectName,
 				s.TaskDao.Columns.EventId:      entity.EventId,
 				s.TaskDao.Columns.EventType:    entity.EventType,
 				s.TaskDao.Columns.TaskTitle:    entity.TaskTitle,
@@ -655,7 +641,9 @@ func (s *OpsEventTaskService) Complete(req *opsdevmodel.OpsEventTaskCompleteReq)
 		// 4.2 功能开发完成时,自动创建功能测试任务
 		if entity.TaskType == opsdevmodel.TaskTypeFeatureDev {
 			testTaskData := g.Map{
+				s.TaskDao.Columns.TaskNo:       testTaskNo,
 				s.TaskDao.Columns.ProjectId:    entity.ProjectId,
+				s.TaskDao.Columns.ProjectName:  entity.ProjectName,
 				s.TaskDao.Columns.EventId:      entity.EventId,
 				s.TaskDao.Columns.EventType:    entity.EventType,
 				s.TaskDao.Columns.TaskTitle:    entity.TaskTitle,
@@ -916,7 +904,7 @@ func (s *OpsEventTaskService) GetRecords(req *opsdevmodel.OpsEventTaskRecordSear
 func (s *OpsEventTaskService) generateTaskNo() string {
 	// 格式: TSK + 年月日 + 4位序号
 	now := gtime.Now()
-	prefix := "TSK" + now.Format("Ymd")
+	prefix := "TSK" + now.Format("ymd")
 
 	// 查询当天最大序号
 	var maxNo string
@@ -970,6 +958,22 @@ func (s *OpsEventTaskService) canCancel(status string) bool {
 	return status != opsdevmodel.TaskStatusCompleted
 }
 
+// getProjectName 根据项目ID获取项目名称
+func (s *OpsEventTaskService) getProjectName(projectId int) string {
+	if projectId <= 0 {
+		return ""
+	}
+	var project opsdevmodel.OpsDeliveryProject
+	err := s.ProjectDao.FieldsEx(s.ProjectDao.Columns.DeletedTime).
+		WherePri(s.ProjectDao.Columns.Id, projectId).
+		Scan(&project)
+	if err != nil {
+		g.Log().Error(err)
+		return ""
+	}
+	return project.ProjectName
+}
+
 // GetAttachments 获取任务附件列表
 func (s *OpsEventTaskService) GetAttachments(taskId int) ([]*opsdevmodel.OpsEventTaskAttachment, error) {
 	var attachments []*opsdevmodel.OpsEventTaskAttachment
@@ -1083,3 +1087,103 @@ func (s *OpsEventTaskService) AddRecord(req *opsdevmodel.OpsEventTaskRecordAddRe
 		return nil
 	})
 }
+
+// AddWorkHour 添加工时登记(累加actual_work_hour并记录过程)
+func (s *OpsEventTaskService) AddWorkHour(req *opsdevmodel.OpsEventTaskWorkHourAddReq) error {
+	var entity opsdevmodel.OpsEventTask
+	err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.TaskId).Scan(&entity)
+	if err != nil {
+		g.Log().Error(err)
+		return myerrors.DbError("查询任务数据失败")
+	}
+	if entity.Id <= 0 {
+		return myerrors.TipsError("任务数据不存在")
+	}
+	if !s.canAddWorkHour(entity.TaskStatus) {
+		return myerrors.TipsError("只有处理中的任务可以登记工时")
+	}
+
+	return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
+		workHourData := g.Map{
+			s.WorkHourDao.Columns.TaskId:         req.TaskId,
+			s.WorkHourDao.Columns.OpsUserId:      s.GetCxtUserId(),
+			s.WorkHourDao.Columns.OpsUserName:    s.GetCxtUserName(),
+			s.WorkHourDao.Columns.ActualWorkDate: req.WorkDate,
+			s.WorkHourDao.Columns.ActualWorkHour: req.ActualHour,
+			s.WorkHourDao.Columns.Remark:         req.Remark,
+		}
+		service.SetCreatedInfo(workHourData, s.GetCxtUserId(), s.GetCxtUserName())
+		_, err := s.WorkHourDao.TX(tx).Data(workHourData).Insert()
+		if err != nil {
+			g.Log().Error(err)
+			return myerrors.DbError("工时登记失败")
+		}
+
+		newActualWorkHour := entity.ActualWorkHour + req.ActualHour
+		updateData := g.Map{
+			s.TaskDao.Columns.ActualWorkHour: newActualWorkHour,
+		}
+		service.SetUpdatedInfo(updateData, s.GetCxtUserId(), s.GetCxtUserName())
+		_, err = s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(updateData).WherePri(s.TaskDao.Columns.Id, req.TaskId).Update()
+		if err != nil {
+			g.Log().Error(err)
+			return myerrors.DbError("更新实际工时失败")
+		}
+
+		handleContent := fmt.Sprintf("工时登记<br/>工作日期: %s<br/>实际工时: %s小时<br/>工作进展: %s",
+			req.WorkDate, gconv.String(req.ActualHour), req.Remark)
+		recordData := g.Map{
+			s.RecordDao.Columns.TaskId:         req.TaskId,
+			s.RecordDao.Columns.HandleUserId:   s.GetCxtUserId(),
+			s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
+			s.RecordDao.Columns.HandleContent:  handleContent,
+		}
+		service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
+		_, err = s.RecordDao.TX(tx).Data(recordData).Insert()
+		if err != nil {
+			g.Log().Error(err)
+			return myerrors.DbError("新增过程记录失败")
+		}
+		return nil
+	})
+}
+
+// GetWorkHourList 获取工时登记列表
+func (s *OpsEventTaskService) GetWorkHourList(req *opsdevmodel.OpsEventTaskWorkHourListReq) ([]*opsdevmodel.OpsEventTaskWorkHourRsp, error) {
+	var entities []*opsdevmodel.OpsEventTaskWorkHour
+	err := s.WorkHourDao.FieldsEx(s.WorkHourDao.Columns.DeletedTime).
+		Where(s.WorkHourDao.Columns.TaskId, req.TaskId).
+		Order(s.WorkHourDao.Columns.CreatedTime + " desc").
+		Scan(&entities)
+	if err != nil {
+		g.Log().Error(err)
+		return nil, myerrors.DbError("查询工时登记列表失败")
+	}
+
+	list := make([]*opsdevmodel.OpsEventTaskWorkHourRsp, 0, len(entities))
+	for _, entity := range entities {
+		workDate := ""
+		if entity.ActualWorkDate != nil {
+			workDate = entity.ActualWorkDate.Format("Y-m-d")
+		}
+		createdTime := ""
+		if entity.CreatedTime != nil {
+			createdTime = entity.CreatedTime.Format("Y-m-d H:i:s")
+		}
+		list = append(list, &opsdevmodel.OpsEventTaskWorkHourRsp{
+			Id:          entity.Id,
+			TaskId:      entity.TaskId,
+			WorkDate:    workDate,
+			ActualHour:  entity.ActualWorkHour,
+			Remark:      entity.Remark,
+			CreatedName: entity.CreatedName,
+			CreatedTime: createdTime,
+		})
+	}
+	return list, nil
+}
+
+// canAddWorkHour 检查是否可以登记工时(仅处理中状态)
+func (s *OpsEventTaskService) canAddWorkHour(status string) bool {
+	return status == opsdevmodel.TaskStatusProcessing
+}

+ 5 - 5
opms_parent/config/config.toml

@@ -1,10 +1,10 @@
 # 应用系统设置
 [setting]
     logpath = "/tmp/log/admin"
-    bind-addr = "192.168.0.227:8002"
-    bind-mutipart-addr = "192.168.0.227:9999"
+    bind-addr = "192.168.0.188:8002"
+    bind-mutipart-addr = "192.168.0.188:9999"
     need-advertise-addr = false
-    srv-name = "dashoo.opms.parent-0.0.2"
+    srv-name = "dashoo.opms.parent-0.0.2-cj"
     env = "dev"
     swagger = true
 
@@ -32,8 +32,8 @@
         link = "mysql:oms_u:S3TFPnthe3SaJpmV@tcp(sh-cdb-3dhdnseg.sql.tencentcdb.com:59463)/dashoo_oms"
 
 [micro_srv]
-    auth = "dashoo.opms.admin-0.0.1"
-    tenant = "default"
+    auth = "dashoo.opms.admin-0.0.1-cj"
+    tenant = "8b9ec443"
 
 [cron]
     deliver_order_enabled = false

+ 9 - 9
opms_parent/main.go

@@ -15,7 +15,7 @@ import (
 	"dashoo.cn/opms_parent/app/handler/opsdev"
 	"dashoo.cn/opms_parent/app/handler/partner"
 	"dashoo.cn/opms_parent/app/handler/plat"
-	projHandler "dashoo.cn/opms_parent/app/handler/proj"
+	base4 "dashoo.cn/opms_parent/app/handler/proj"
 	"dashoo.cn/opms_parent/app/handler/sysreport"
 	"dashoo.cn/opms_parent/app/handler/train"
 	"dashoo.cn/opms_parent/app/handler/work"
@@ -49,10 +49,10 @@ func main() {
 	s.RegisterName("TaskLog", new(plat.TaskLogHandler), "")
 	s.RegisterName("TaskProgress", new(plat.TaskProgressHandler), "")
 	s.RegisterName("PunchRecords", new(plat.PunchRecordsHandler), "")
-	s.RegisterName("Business", new(projHandler.BusinessHandler), "")
-	s.RegisterName("BusinessContact", new(projHandler.BusinessContactHandler), "")
-	s.RegisterName("BusinessFile", new(projHandler.BusinessFileHandler), "")
-	s.RegisterName("BusinessTeam", new(projHandler.BusinessTeamHandler), "")
+	s.RegisterName("Business", new(base4.BusinessHandler), "")
+	s.RegisterName("BusinessContact", new(base4.BusinessContactHandler), "")
+	s.RegisterName("BusinessFile", new(base4.BusinessFileHandler), "")
+	s.RegisterName("BusinessTeam", new(base4.BusinessTeamHandler), "")
 	s.RegisterName("CtrContract", new(contract.CtrContract), "")
 	s.RegisterName("CtrContractCollectionPlan", new(contract.CtrContractCollectionPlan), "")
 	s.RegisterName("CtrContractCollection", new(contract.CtrContractCollection), "")
@@ -77,7 +77,7 @@ func main() {
 	s.RegisterName("Questionnaire", new(plat.QuestionnaireHandler), "")
 	s.RegisterName("TableColsConfig", new(plat.TableColsConfigHandler), "")
 	s.RegisterName("ContractReport", new(contract.ContractReportHandler), "")
-	s.RegisterName("BusinessReport", new(projHandler.BusinessReportHandler), "")
+	s.RegisterName("BusinessReport", new(base4.BusinessReportHandler), "")
 	s.RegisterName("ProductConsultRecord", new(cust.ProductConsultRecordHandler), "")
 	s.RegisterName("TrainHead", new(train.TrainHeadHander), "")
 	s.RegisterName("SaleApply", new(train.SaleApplyHandler), "")
@@ -88,8 +88,8 @@ func main() {
 	s.RegisterName("DeliveryProjectEvent", new(opsdev.DeliveryProjectEventHandler), "")
 	s.RegisterName("ProjectInventory", new(opsdev.ProjectInventoryHandler), "")
 	s.RegisterName("CtrContractEvent", new(contract.CtrContractEvent), "")
-    // 注册 OPS 任务处理器
-    s.RegisterName("OpsEventTask", new(opsdev.OpsEventTaskHandler), "")
+	// 注册 OPS 任务处理器
+	s.RegisterName("OpsEventTask", new(opsdev.OpsEventTaskHandler), "")
 
 	// 首页
 	s.RegisterName("Home", new(home.HomeHandler), "")
@@ -98,7 +98,7 @@ func main() {
 	s.RegisterName("DingEvent", new(dingtalk.DingHandler), "")
 
 	dynamic.Invoker.Register(new(contract.CtrContractHandler))
-	dynamic.Invoker.Register(new(projHandler.BusinessHandler))
+	dynamic.Invoker.Register(new(base4.BusinessHandler))
 	dynamic.Invoker.Register(new(work.WorkOrderHandler))
 	// 注册服务对象
 	//s.RegisterName("Auth", new(handler.Auth), "")

BIN
opms_parent/opms_parent_v2