Jelajahi Sumber

goodsï: 商品出库

Yi 4 bulan lalu
induk
melakukan
1ac978f036

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20241112033919-d1548b594232
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20241112090416-295c2aa69dab
 	gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/eko/gocache v1.1.0

+ 4 - 0
go.sum

@@ -54,6 +54,10 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20241112031751-6a55598719ad h1:x6hPcphI
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241112031751-6a55598719ad/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241112033919-d1548b594232 h1:kERughSYPdXPKXcA/xXPXxxIlfCiV7R3sI7TcjWC438=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241112033919-d1548b594232/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112070903-def17e1caa43 h1:DotHkoSwWIHuOdQBcZ0Vky6jXAbf0rOnsHChmr3mnqg=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112070903-def17e1caa43/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112090416-295c2aa69dab h1:72cBosC+SBsz5m42b47i9L+YdoL1mF8l9xd5uo81jx4=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112090416-295c2aa69dab/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b h1:w05MxH7yqveRlaRbxHhbif5YjPrJFodRPfOjYhXn7Zk=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

+ 75 - 0
http/handler/goods/drugs.go

@@ -166,3 +166,78 @@ func NeckRingCreateOrUpdate(c *gin.Context) {
 		Data: &operationPb.Success{Success: true},
 	})
 }
+
+func OutboundApply(c *gin.Context) {
+	var req pasturePb.OutboundApplyCreatedRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.OutType, valid.Required),
+		valid.Field(&req.Goods, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.OutboundApply(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func OutboundList(c *gin.Context) {
+	var req pasturePb.SearchOutboundApplyRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	pagination := &pasturePb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.OutboundList(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+func OutboundAudit(c *gin.Context) {
+	var req pasturePb.OutboundApplyAuditRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Id, valid.Required),
+		valid.Field(&req.AuditStatus, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.OutboundAudit(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}

+ 4 - 0
http/route/goods_api.go

@@ -19,5 +19,9 @@ func GoodsManageAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		goodsRoute.POST("/medical/equipment/createOrUpdate", goods.MedicalEquipmentCreateOrUpdate)
 		goodsRoute.POST("/neck/ring/list", goods.NeckRingList)
 		goodsRoute.POST("/neck/ring/createOrUpdate", goods.NeckRingCreateOrUpdate)
+		goodsRoute.POST("/outbound/apply", goods.OutboundApply)
+		goodsRoute.POST("/outbound/list", goods.OutboundList)
+		goodsRoute.POST("/outbound/audit", goods.OutboundAudit)
+		goodsRoute.POST("/outbound/cancel", goods.OutboundAudit)
 	}
 }

+ 81 - 0
model/outbound.go

@@ -0,0 +1,81 @@
+package model
+
+import (
+	"fmt"
+	"kpt-pasture/util"
+	"time"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
+
+type Outbound struct {
+	Id               int32                      `json:"id"`
+	Number           string                     `json:"number"`
+	OutType          pasturePb.OutType_Kind     `json:"outType"`
+	AuditStatus      pasturePb.AuditStatus_Kind `json:"auditStatus"`
+	ApplicantId      int32                      `json:"applicantId"`
+	ApplicantName    string                     `json:"applicantName"`
+	ApplicantAt      int64                      `json:"applicantAt"`
+	ApplicantRemarks string                     `json:"applicantRemarks"`
+	ExamineId        int32                      `json:"examineId"`
+	ExamineName      string                     `json:"examineName"`
+	ExamineAt        int64                      `json:"examineAt"`
+	ExamineRemarks   string                     `json:"examineRemarks"`
+	CreatedAt        int64                      `json:"createdAt"`
+	UpdatedAt        int64                      `json:"updatedAt"`
+}
+
+func (o *Outbound) TableName() string {
+	return "outbound"
+}
+
+func NewOutbound(req *pasturePb.OutboundApplyCreatedRequest, currentUser *SystemUser) *Outbound {
+	return &Outbound{
+		Number:           fmt.Sprintf("%s%s", util.GenerateRandomNumberString(6), time.Now().Format(LayoutTime)),
+		OutType:          req.OutType,
+		AuditStatus:      pasturePb.AuditStatus_Pending,
+		ApplicantId:      int32(currentUser.Id),
+		ApplicantName:    currentUser.Name,
+		ApplicantAt:      time.Now().Unix(),
+		ApplicantRemarks: req.ApplicantRemarks,
+	}
+}
+
+type OutboundSlice []*Outbound
+
+func (o OutboundSlice) ToPB(outTypeMap map[pasturePb.OutType_Kind]string, auditStatusMap map[pasturePb.AuditStatus_Kind]string) []*pasturePb.SearchOutboundApplyList {
+	res := make([]*pasturePb.SearchOutboundApplyList, len(o))
+	for i, v := range o {
+		applicantAtFormat, examineAtFormat, outTypeName, auditStatusName := "", "", "", ""
+		if v.ApplicantAt > 0 {
+			applicantAtFormat = time.Unix(v.ApplicantAt, 0).Format(LayoutTime)
+		}
+		if v.ExamineAt > 0 {
+			examineAtFormat = time.Unix(v.ExamineAt, 0).Format(LayoutTime)
+		}
+
+		if outType, ok := outTypeMap[v.OutType]; ok {
+			outTypeName = outType
+		}
+
+		if auditStatus, ok := auditStatusMap[v.AuditStatus]; ok {
+			auditStatusName = auditStatus
+		}
+
+		res[i] = &pasturePb.SearchOutboundApplyList{
+			Id:                v.Id,
+			Number:            v.Number,
+			OutType:           v.OutType,
+			OutTypeName:       outTypeName,
+			AuditStatus:       v.AuditStatus,
+			AuditStatusName:   auditStatusName,
+			ApplicantName:     v.ApplicantName,
+			ApplicantRemarks:  v.ApplicantRemarks,
+			ExamineName:       v.ExamineName,
+			ExamineRemarks:    v.ExamineRemarks,
+			ApplicantAtFormat: applicantAtFormat,
+			ExamineAtFormat:   examineAtFormat,
+		}
+	}
+	return res
+}

+ 45 - 0
model/outbound_log.go

@@ -0,0 +1,45 @@
+package model
+
+import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+
+type OutboundLog struct {
+	Id          int64               `json:"id"`
+	OutboundId  int64               `json:"outboundId"`
+	GoodsId     int64               `json:"goodsId"`
+	GoodsName   string              `json:"goodsName"`
+	Specs       string              `json:"specs"`
+	Producer    string              `json:"producer"`
+	BatchNumber string              `json:"batchNumber"`
+	Price       int32               `json:"price"`
+	Unit        pasturePb.Unit_Kind `json:"unit"`
+	UnitName    string              `json:"unitName"`
+	Quantity    int32               `json:"quantity"`
+	CreatedAt   int64               `json:"createdAt"`
+	UpdatedAt   int64               `json:"updatedAt"`
+}
+
+func (o *OutboundLog) TableName() string {
+	return "outband_log"
+}
+
+func NewOutboundLogList(req []*pasturePb.OutboundApplyGoodsItem, unitMap map[pasturePb.Unit_Kind]string) []*OutboundLog {
+	res := make([]*OutboundLog, 0)
+	for _, v := range req {
+		unitName := ""
+		if unit, ok := unitMap[v.Unit]; ok {
+			unitName = unit
+		}
+		res = append(res, &OutboundLog{
+			GoodsId:     int64(v.GoodsId),
+			GoodsName:   v.GoodsName,
+			Specs:       v.Specs,
+			Producer:    v.Producer,
+			BatchNumber: v.BatchNumber,
+			Price:       int32(v.Price * 100),
+			Unit:        v.Unit,
+			UnitName:    unitName,
+			Quantity:    int32(v.Quantity),
+		})
+	}
+	return res
+}

+ 211 - 0
module/backend/config_data_breed.go

@@ -248,3 +248,214 @@ func (s *StoreEntry) NeckRingStatusEnumList(isAll string) []*pasturePb.ConfigOpt
 	})
 	return configOptions
 }
+
+func (s *StoreEntry) WorkOrderSubUnitEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.WorkOrderSubscribeUnit_Person),
+		Label:    "个人",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.WorkOrderSubscribeUnit_dept),
+		Label:    "部门",
+		Disabled: true,
+	})
+	return configOptions
+}
+
+func (s *StoreEntry) WorkOrderPriorityEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.Priority_Low),
+		Label:    "低",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.Priority_Middle),
+		Label:    "一般",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.Priority_High),
+		Label:    "紧急",
+		Disabled: true,
+	})
+	return configOptions
+}
+
+func (s *StoreEntry) WorkOrderCategoryEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.WorkOrderCategory_Health),
+		Label:    "保健",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.WorkOrderCategory_Breed),
+		Label:    "繁殖",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.WorkOrderCategory_Nutrition),
+		Label:    "营养",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.WorkOrderCategory_Ordinary),
+		Label:    "日常",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.WorkOrderCategory_Other),
+		Label:    "其他",
+		Disabled: true,
+	})
+	return configOptions
+}
+
+func CalendarTypeEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	if isAll == model.IsAllYes {
+		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+			Value:    int32(pasturePb.SameTimeStatus_Invalid),
+			Label:    "全部",
+			Disabled: true,
+		})
+	}
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.CalendarType_Immunisation),
+		Label:    "免疫",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.CalendarType_PG),
+		Label:    "同期PG",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.CalendarType_RnGH),
+		Label:    "同期RnGH",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.CalendarType_Pregnancy_Check),
+		Label:    "孕检",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.CalendarType_WorkOrder),
+		Label:    "工单",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.CalendarType_Weaning),
+		Label:    "断奶",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.CalendarType_Treatment),
+		Label:    "治疗",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.CalendarType_Mating),
+		Label:    "配种",
+		Disabled: true,
+	})
+	return configOptions
+}
+
+func (s *StoreEntry) AbortionReasonsEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	if isAll == model.IsAllYes {
+		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+			Value:    int32(pasturePb.AbortionReasons_Invalid),
+			Label:    "全部",
+			Disabled: true,
+		})
+	}
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.AbortionReasons_Mechanical_Abortion),
+		Label:    "机械性流产",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.AbortionReasons_Malnutrition_Abortion),
+		Label:    "营养不良性流产",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.AbortionReasons_Mycotoxin_Abortion),
+		Label:    "霉菌毒素流产",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.AbortionReasons_Habitual_Abortion),
+		Label:    "习惯性流产",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.AbortionReasons_Brucellosis_Abortion),
+		Label:    "布病流产",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.AbortionReasons_Inflammatory_Abortion),
+		Label:    "产道炎症性流产",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.AbortionReasons_Heat_Stress_Abortion),
+		Label:    "热应激流产",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.AbortionReasons_Infectious_Abortion),
+		Label:    "传染病性流产",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.AbortionReasons_Other),
+		Label:    "其他",
+		Disabled: true,
+	})
+	return configOptions
+}
+
+func (s *StoreEntry) HealthStatusEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	if isAll == model.IsAllYes {
+		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+			Value:    int32(pasturePb.HealthStatus_Invalid),
+			Label:    "全部",
+			Disabled: true,
+		})
+	}
+
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.HealthStatus_Health),
+		Label:    "健康",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.HealthStatus_Disease),
+		Label:    "发病",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.HealthStatus_Treatment),
+		Label:    "治疗",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.HealthStatus_Curable),
+		Label:    "治愈",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.HealthStatus_Out),
+		Label:    "淘汰",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.HealthStatus_Dead),
+		Label:    "死亡",
+		Disabled: true,
+	})
+	return configOptions
+}
+
+func (s *StoreEntry) CalvingAnalysisMethodEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	if isAll == model.IsAllYes {
+		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+			Value:    int32(pasturePb.CalvingAnalysisMethod_Invalid),
+			Label:    "全部",
+			Disabled: true,
+		})
+	}
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.CalvingAnalysisMethod_Months),
+		Label:    "按月份统计",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.CalvingAnalysisMethod_CowKind),
+		Label:    "按照品种统计",
+		Disabled: true,
+	})
+	return configOptions
+}

+ 26 - 185
module/backend/config_data_other.go

@@ -326,212 +326,53 @@ func (s *StoreEntry) WorkOrderFrequencyEnumList(isAll string) []*pasturePb.Confi
 	return configOptions
 }
 
-func (s *StoreEntry) WorkOrderSubUnitEnumList(isAll string) []*pasturePb.ConfigOptionsList {
-	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
-	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.WorkOrderSubscribeUnit_Person),
-		Label:    "个人",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.WorkOrderSubscribeUnit_dept),
-		Label:    "部门",
-		Disabled: true,
-	})
-	return configOptions
-}
-
-func (s *StoreEntry) WorkOrderPriorityEnumList(isAll string) []*pasturePb.ConfigOptionsList {
-	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
-	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.Priority_Low),
-		Label:    "低",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.Priority_Middle),
-		Label:    "一般",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.Priority_High),
-		Label:    "紧急",
-		Disabled: true,
-	})
-	return configOptions
-}
-
-func (s *StoreEntry) WorkOrderCategoryEnumList(isAll string) []*pasturePb.ConfigOptionsList {
-	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
-	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.WorkOrderCategory_Health),
-		Label:    "保健",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.WorkOrderCategory_Breed),
-		Label:    "繁殖",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.WorkOrderCategory_Nutrition),
-		Label:    "营养",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.WorkOrderCategory_Ordinary),
-		Label:    "日常",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.WorkOrderCategory_Other),
-		Label:    "其他",
-		Disabled: true,
-	})
-	return configOptions
-}
-
-func CalendarTypeEnumList(isAll string) []*pasturePb.ConfigOptionsList {
-	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
-	if isAll == model.IsAllYes {
-		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-			Value:    int32(pasturePb.SameTimeStatus_Invalid),
-			Label:    "全部",
-			Disabled: true,
-		})
-	}
-	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.CalendarType_Immunisation),
-		Label:    "免疫",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.CalendarType_PG),
-		Label:    "同期PG",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.CalendarType_RnGH),
-		Label:    "同期RnGH",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.CalendarType_Pregnancy_Check),
-		Label:    "孕检",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.CalendarType_WorkOrder),
-		Label:    "工单",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.CalendarType_Weaning),
-		Label:    "断奶",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.CalendarType_Treatment),
-		Label:    "治疗",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.CalendarType_Mating),
-		Label:    "配种",
-		Disabled: true,
-	})
-	return configOptions
-}
-
-func (s *StoreEntry) AbortionReasonsEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+func (s *StoreEntry) OutTypeEnumList(isAll string) []*pasturePb.ConfigOptionsList {
 	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
 	if isAll == model.IsAllYes {
-		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-			Value:    int32(pasturePb.AbortionReasons_Invalid),
-			Label:    "全部",
-			Disabled: true,
-		})
+		configOptions = append(configOptions,
+			&pasturePb.ConfigOptionsList{
+				Value:    int32(pasturePb.OutType_Invalid),
+				Label:    "全部",
+				Disabled: true,
+			})
 	}
 	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.AbortionReasons_Mechanical_Abortion),
-		Label:    "机械性流产",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.AbortionReasons_Malnutrition_Abortion),
-		Label:    "营养不良性流产",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.AbortionReasons_Mycotoxin_Abortion),
-		Label:    "霉菌毒素流产",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.AbortionReasons_Habitual_Abortion),
-		Label:    "习惯性流产",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.AbortionReasons_Brucellosis_Abortion),
-		Label:    "布病流产",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.AbortionReasons_Inflammatory_Abortion),
-		Label:    "产道炎症性流产",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.AbortionReasons_Heat_Stress_Abortion),
-		Label:    "热应激流产",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.AbortionReasons_Infectious_Abortion),
-		Label:    "传染病性流产",
+		Value:    int32(pasturePb.OutType_Drugs),
+		Label:    "药品",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.AbortionReasons_Other),
-		Label:    "其他",
+		Value:    int32(pasturePb.OutType_Medical_Equipment),
+		Label:    "医疗器械",
 		Disabled: true,
 	})
 	return configOptions
 }
 
-func (s *StoreEntry) HealthStatusEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+func (s *StoreEntry) AuditStatusEnumList(isAll string) []*pasturePb.ConfigOptionsList {
 	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
 	if isAll == model.IsAllYes {
-		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-			Value:    int32(pasturePb.HealthStatus_Invalid),
-			Label:    "全部",
-			Disabled: true,
-		})
+		configOptions = append(configOptions,
+			&pasturePb.ConfigOptionsList{
+				Value:    int32(pasturePb.AuditStatus_Invalid),
+				Label:    "全部",
+				Disabled: true,
+			})
 	}
-
 	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.HealthStatus_Health),
-		Label:    "健康",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.HealthStatus_Disease),
-		Label:    "发病",
+		Value:    int32(pasturePb.AuditStatus_Pending),
+		Label:    "待审核",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.HealthStatus_Treatment),
-		Label:    "治疗",
+		Value:    int32(pasturePb.AuditStatus_Pass),
+		Label:    "已通过",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.HealthStatus_Curable),
-		Label:    "治愈",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.HealthStatus_Out),
-		Label:    "淘汰",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.HealthStatus_Dead),
-		Label:    "死亡",
-		Disabled: true,
-	})
-	return configOptions
-}
-
-func (s *StoreEntry) CalvingAnalysisMethodEnumList(isAll string) []*pasturePb.ConfigOptionsList {
-	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
-	if isAll == model.IsAllYes {
-		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-			Value:    int32(pasturePb.CalvingAnalysisMethod_Invalid),
-			Label:    "全部",
-			Disabled: true,
-		})
-	}
-	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.CalvingAnalysisMethod_Months),
-		Label:    "按月份统计",
+		Value:    int32(pasturePb.AuditStatus_Reject),
+		Label:    "已拒绝",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.CalvingAnalysisMethod_CowKind),
-		Label:    "按照品种统计",
+		Value:    int32(pasturePb.AuditStatus_Cancel),
+		Label:    "已取消",
 		Disabled: true,
 	})
 	return configOptions

+ 16 - 0
module/backend/enum_map.go

@@ -198,3 +198,19 @@ func (s *StoreEntry) NeckRingStatusMap() map[pasturePb.NeckRingStatus_Kind]strin
 	}
 	return res
 }
+
+func (s *StoreEntry) OutTypeMap() map[pasturePb.OutType_Kind]string {
+	res := make(map[pasturePb.OutType_Kind]string)
+	for _, v := range s.OutTypeEnumList("") {
+		res[pasturePb.OutType_Kind(v.Value)] = v.Label
+	}
+	return res
+}
+
+func (s *StoreEntry) AuditStatusMap() map[pasturePb.AuditStatus_Kind]string {
+	res := make(map[pasturePb.AuditStatus_Kind]string)
+	for _, v := range s.AuditStatusEnumList("") {
+		res[pasturePb.AuditStatus_Kind(v.Value)] = v.Label
+	}
+	return res
+}

+ 2 - 0
module/backend/enum_options.go

@@ -190,6 +190,8 @@ func (s *StoreEntry) SystemBaseConfigOptions(ctx context.Context, optionsName, i
 		"multiFactorAnalysisMethod":  s.MultiFactorAnalysisMethodEnumList,
 		"saleCowAnalysisMethod":      s.SaleCowAnalysisMethodEnumList,
 		"neckRingStatus":             s.NeckRingStatusEnumList,
+		"outType":                    s.OutTypeEnumList,
+		"auditStatus":                s.AuditStatusEnumList,
 	}
 
 	getConfigFunc, ok := getConfigFuncMap[optionsName]

+ 155 - 0
module/backend/goods.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
 	"net/http"
 	"time"
 
@@ -206,3 +207,157 @@ func (s *StoreEntry) NeckRingLogList(ctx context.Context, req *pasturePb.SearchN
 		},
 	}, nil
 }
+
+func (s *StoreEntry) OutboundApply(ctx context.Context, req *pasturePb.OutboundApplyCreatedRequest) error {
+	currentUser, err := s.GetCurrentSystemUser(ctx)
+	if err != nil {
+		return xerr.Custom("登录人信息失效")
+	}
+
+	if len(req.Goods) <= 0 {
+		return xerr.Custom("请选择要出库商品")
+	}
+	goodsItems := make([]*pasturePb.OutboundApplyGoodsItem, 0)
+	switch req.OutType {
+	case pasturePb.OutType_Drugs:
+		for _, v := range req.Goods {
+			if v.Quantity <= 0 {
+				return xerr.Custom("请填写商品数量")
+			}
+			if v.GoodsId <= 0 {
+				return xerr.Custom("请选择要出库商品")
+			}
+
+			newDrugs := &model.Drugs{}
+			if err = s.DB.Model(new(model.Drugs)).
+				Where("id = ?", v.GoodsId).
+				Where("quantity >= ?", v.Quantity).
+				First(newDrugs).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+			goodsItems = append(goodsItems, &pasturePb.OutboundApplyGoodsItem{
+				GoodsId:  v.GoodsId,
+				Quantity: v.Quantity,
+				Unit:     v.Unit,
+			})
+		}
+	case pasturePb.OutType_Medical_Equipment:
+		for _, v := range req.Goods {
+			if v.Quantity <= 0 {
+				return xerr.Custom("请填写商品数量")
+			}
+			if v.GoodsId <= 0 {
+				return xerr.Custom("请选择要出库商品")
+			}
+
+			newMedicalEquipment := &model.MedicalEquipment{}
+			if err = s.DB.Model(new(model.Drugs)).
+				Where("id = ?", v.GoodsId).
+				Where("quantity >= ?", v.Quantity).
+				First(newMedicalEquipment).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+			goodsItems = append(goodsItems, &pasturePb.OutboundApplyGoodsItem{
+				GoodsId:  v.GoodsId,
+				Quantity: v.Quantity,
+				Unit:     v.Unit,
+			})
+		}
+	default:
+		return xerr.Custom("未知的出库类型")
+	}
+	unitMap := s.UnitMap()
+	outbound := model.NewOutbound(req, currentUser)
+	outboundLog := model.NewOutboundLogList(goodsItems, unitMap)
+	if err = s.DB.Transaction(func(tx *gorm.DB) error {
+
+		// 创建出库申请
+		if err = tx.Create(outbound).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+
+		if err = tx.Create(outboundLog).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+		return nil
+	}); err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+func (s *StoreEntry) OutboundList(ctx context.Context, req *pasturePb.SearchOutboundApplyRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchOutboundApplyResponse, error) {
+	startUnix := util.TimeParseLocalUnix(req.StartDayTime)
+	endUnix := util.TimeParseLocalEndUnix(req.EndDayTime)
+
+	var count int64 = 0
+	outboundList := make([]*model.Outbound, 0)
+	pref := s.DB.Model(new(model.Outbound))
+	if req.OutType > 0 {
+		pref.Where("out_type = ?", req.OutType)
+	}
+
+	if startUnix > 0 && endUnix > 0 && startUnix <= endUnix {
+		pref.Where("applicant_at BETWEEN ? AND ?", startUnix, endUnix)
+	}
+
+	if err := pref.Order("id desc").
+		Count(&count).
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&outboundList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	outTypeMap := s.OutTypeMap()
+	auditStatusMap := s.AuditStatusMap()
+
+	return &pasturePb.SearchOutboundApplyResponse{
+		Code:    http.StatusOK,
+		Message: "ok",
+		Data: &pasturePb.SearchOutboundApplyData{
+			List:     model.OutboundSlice(outboundList).ToPB(outTypeMap, auditStatusMap),
+			Total:    int32(count),
+			PageSize: pagination.PageSize,
+			Page:     pagination.Page,
+		},
+	}, nil
+}
+
+func (s *StoreEntry) OutboundAudit(ctx context.Context, req *pasturePb.OutboundApplyAuditRequest) error {
+	outbound, err := s.GetOutboundById(ctx, int64(req.Id))
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+
+	if outbound == nil {
+		return xerr.Custom("出库单不存在")
+	}
+
+	if req.AuditStatus != pasturePb.AuditStatus_Pass && req.AuditStatus != pasturePb.AuditStatus_Reject && req.AuditStatus != pasturePb.AuditStatus_Cancel {
+		return xerr.Custom("审核状态异常")
+	}
+
+	if outbound.AuditStatus != pasturePb.AuditStatus_Pending {
+		return xerr.Custom("异常出库单")
+	}
+
+	currentUser, err := s.GetCurrentSystemUser(ctx)
+	if err != nil {
+		return xerr.Custom("登录人信息失效")
+	}
+
+	if err = s.DB.Model(outbound).
+		Where("id = ?", outbound.Id).
+		Updates(map[string]interface{}{
+			"audit_status":    req.AuditStatus,
+			"examine_id":      currentUser.Id,
+			"examine_name":    currentUser.Name,
+			"examine_remarks": req.ExamineRemarks,
+			"examine_at":      time.Now().Unix(),
+		}).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+
+	return nil
+}

+ 3 - 0
module/backend/interface.go

@@ -205,6 +205,9 @@ type GoodsService interface {
 	MedicalEquipmentCreateOrUpdate(ctx context.Context, req *pasturePb.SearchMedicalEquipmentList) error
 	NeckRingLogCreateOrUpdate(ctx context.Context, req *pasturePb.NeckRingCreateRequest) error
 	NeckRingLogList(ctx context.Context, req *pasturePb.SearchNeckRingRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchNeckRingResponse, error)
+	OutboundApply(ctx context.Context, req *pasturePb.OutboundApplyCreatedRequest) error
+	OutboundList(ctx context.Context, req *pasturePb.SearchOutboundApplyRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchOutboundApplyResponse, error)
+	OutboundAudit(ctx context.Context, req *pasturePb.OutboundApplyAuditRequest) error
 }
 
 //go:generate mockgen -destination mock/AnalyseService.go -package kptservicemock kpt-pasture/module/backend AnalyseService

+ 8 - 0
module/backend/sql.go

@@ -285,3 +285,11 @@ func (s *StoreEntry) GetLastEventMating(ctx context.Context, cowId int64) (*mode
 	}
 	return res, nil
 }
+
+func (s *StoreEntry) GetOutboundById(ctx context.Context, id int64) (*model.Outbound, error) {
+	res := &model.Outbound{}
+	if err := s.DB.Where("id = ?", id).First(res).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return res, nil
+}

+ 12 - 1
util/util.go

@@ -3,6 +3,7 @@ package util
 import (
 	"fmt"
 	"math"
+	"math/rand"
 	"time"
 
 	"gitee.com/xuyiping_admin/pkg/xerr"
@@ -14,6 +15,17 @@ const (
 	LayoutMonth = "2006-01"
 )
 
+// GenerateRandomNumberString 生成指定长度的数字串
+func GenerateRandomNumberString(length int) string {
+	const charset = "0123456789"
+	seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
+	result := make([]byte, length)
+	for i := range result {
+		result[i] = charset[seededRand.Intn(len(charset))]
+	}
+	return string(result)
+}
+
 // TimeParseLocalUnix 获取当天零点的时间戳
 // eg 2023-02-22 => 1676995200
 func TimeParseLocalUnix(DayTime string) int64 {
@@ -275,7 +287,6 @@ func RemoveDuplicates(slice []string) []string {
 			result = append(result, v)
 		}
 	}
-
 	// 返回不重复的切片
 	return result
 }