first commit

This commit is contained in:
jxw
2026-06-03 20:59:39 +08:00
commit 2f7309a45c
473 changed files with 81369 additions and 0 deletions

View File

@@ -0,0 +1,271 @@
package service
import (
"fmt"
"mime/multipart"
"os"
"path"
"strings"
"time"
"github.com/rovina/zsp-backend/internal/dto"
"github.com/rovina/zsp-backend/internal/model"
"github.com/rovina/zsp-backend/internal/repository"
)
type ZSPContractService interface {
CreateFolder(req *dto.ZSPFolderCreateRequest) error
GetFolders() ([]model.ZSPContractFolder, error)
GetFolderById(folderID string) (*model.ZSPContractFolder, error)
DeleteFolderById(folderID string) error
UpdateFolderById(folderID string, req *dto.ZSPFolderUpdateRequest) error
UploadContract(req *dto.ZSPContractCreateRequest, fileHeader *multipart.FileHeader) error
GetContractById(contractID string) (*model.ZSPContractFile, error)
GetContractWithDetails(contractID string) (*model.ZSPContractFile, error)
ListContracts(folderID string) ([]model.ZSPContractFile, error)
ListContractsWithDetails(folderID string) ([]model.ZSPContractFile, error)
DeleteContractById(contractID string) error
UpdateContract(contract *model.ZSPContractFile) error
UpdateContractById(contractID string, req *dto.ZSPContractUpdateRequest) error
// 合同详情相关
CreateContractDetail(req *dto.ZSPContractDetailCreateRequest) error
UpdateContractDetail(req *dto.ZSPContractDetailUpdateRequest) error
GetContractDetailById(detailID string) (*model.ZSPContractDetail, error)
ListContractDetails(contractFileID string) ([]model.ZSPContractDetail, error)
DeleteContractDetailById(detailID string) error
BatchCreateContractDetails(req *dto.ZSPContractDetailBatchCreateRequest) error
}
type zspContractService struct {
repo repository.ZSPContractRepository
}
func NewZSPContractService(repo repository.ZSPContractRepository) (ZSPContractService, error) {
return &zspContractService{repo: repo}, nil
}
func (s *zspContractService) CreateFolder(req *dto.ZSPFolderCreateRequest) error {
var folder model.ZSPContractFolder
folder.Name = req.Name
folder.Description = req.Description
folder.Count = 0
folder.CreateTime = time.Now()
return s.repo.CreateFolder(&folder)
}
func (s *zspContractService) GetFolders() ([]model.ZSPContractFolder, error) {
folders, err := s.repo.GetFolders()
if err != nil {
return nil, err
}
return folders, nil
}
func (s *zspContractService) GetFolderById(folderID string) (*model.ZSPContractFolder, error) {
return s.repo.GetFolderById(folderID)
}
func (s *zspContractService) DeleteFolderById(folderID string) error {
return s.repo.DeleteFolderById(folderID)
}
func (s *zspContractService) UpdateFolderById(folderID string, req *dto.ZSPFolderUpdateRequest) error {
folder, err := s.repo.GetFolderById(folderID)
if err != nil {
return err
}
folder.Name = req.Name
folder.Description = req.Description
return s.repo.UpdateFolder(folder)
}
func (s *zspContractService) UploadContract(req *dto.ZSPContractCreateRequest, fileHeader *multipart.FileHeader) error {
timeNow := time.Now()
var contract model.ZSPContractFile
contract.FolderID = req.FolderID
contract.Commodity = req.Commodity
contract.CompanyName = req.CompanyName
contract.ContractNumber = req.ContractNumber
contract.ContractType = req.ContractType
contract.FileName = fileHeader.Filename
// 提取扩展名便于前端展示(如 pdf、jpg
if ext := path.Ext(fileHeader.Filename); ext != "" {
contract.FileType = strings.TrimPrefix(strings.ToLower(ext), ".")
} else {
contract.FileType = fileHeader.Header.Get("Content-Type")
}
if req.SignDate == "" {
return fmt.Errorf("签订日期为必填项")
}
signDate, err := time.Parse("2006-01-02", req.SignDate)
if err != nil {
return fmt.Errorf("签订日期格式不正确,请使用 YYYY-MM-DD: %w", err)
}
contract.SignDate = signDate
contract.UploadTime = timeNow
// 确保目录存在后保存文件
saveDir := "./data/zsp-contract/files"
if err := os.MkdirAll(saveDir, os.ModePerm); err != nil {
return fmt.Errorf("create upload dir: %w", err)
}
storedName := fmt.Sprintf("%d_%s", timeNow.UnixNano(), fileHeader.Filename)
savePath := path.Join(saveDir, storedName)
if err := saveUploadedFile(fileHeader, savePath); err != nil {
return err
}
contract.FileURL = "files/" + storedName
contract.FileSize = fileHeader.Size
return s.repo.CreateContract(&contract)
}
func (s *zspContractService) GetContractById(contractID string) (*model.ZSPContractFile, error) {
return s.repo.GetContractById(contractID)
}
func (s *zspContractService) ListContracts(folderID string) ([]model.ZSPContractFile, error) {
return s.repo.ListContracts(folderID)
}
func (s *zspContractService) DeleteContractById(contractID string) error {
return s.repo.DeleteContractById(contractID)
}
func (s *zspContractService) UpdateContract(contract *model.ZSPContractFile) error {
return s.repo.UpdateContract(contract)
}
func (s *zspContractService) UpdateContractById(contractID string, req *dto.ZSPContractUpdateRequest) error {
contract, err := s.repo.GetContractById(contractID)
if err != nil {
return err
}
if req.ContractNumber != "" {
contract.ContractNumber = req.ContractNumber
}
if req.CompanyName != "" {
contract.CompanyName = req.CompanyName
}
if req.Commodity != "" {
contract.Commodity = req.Commodity
}
if req.ContractType != "" {
contract.ContractType = req.ContractType
}
if req.FolderID > 0 {
contract.FolderID = req.FolderID
}
if req.SignDate != "" {
signDate, err := time.Parse("2006-01-02", req.SignDate)
if err != nil {
return fmt.Errorf("签订日期格式不正确: %w", err)
}
contract.SignDate = signDate
}
return s.repo.UpdateContract(contract)
}
func (s *zspContractService) GetContractWithDetails(contractID string) (*model.ZSPContractFile, error) {
contract, err := s.repo.GetContractById(contractID)
if err != nil {
return nil, err
}
// 获取详情列表
details, err := s.repo.ListContractDetails(contractID)
if err != nil {
return nil, err
}
contract.Details = details
return contract, nil
}
func (s *zspContractService) ListContractsWithDetails(folderID string) ([]model.ZSPContractFile, error) {
return s.repo.ListContractsWithDetails(folderID)
}
// ============ 合同详情相关方法 ============
func (s *zspContractService) CreateContractDetail(req *dto.ZSPContractDetailCreateRequest) error {
var detail model.ZSPContractDetail
detail.ContractFileID = req.ContractFileID
detail.CommodityName = req.CommodityName
detail.CommodityCode = req.CommodityCode
detail.TotalQuantity = req.TotalQuantity
detail.Unit = req.Unit
detail.UnitPrice = req.UnitPrice
detail.DeliveredQty = req.DeliveredQty
detail.BillOfLading = req.BillOfLading
detail.Remark = req.Remark
// PendingQty 会在 BeforeCreate 钩子中自动计算
return s.repo.CreateContractDetail(&detail)
}
func (s *zspContractService) UpdateContractDetail(req *dto.ZSPContractDetailUpdateRequest) error {
detail, err := s.repo.GetContractDetailById(fmt.Sprintf("%d", req.ID))
if err != nil {
return err
}
if req.CommodityName != "" {
detail.CommodityName = req.CommodityName
}
if req.CommodityCode != "" {
detail.CommodityCode = req.CommodityCode
}
if req.TotalQuantity > 0 {
detail.TotalQuantity = req.TotalQuantity
}
if req.Unit != "" {
detail.Unit = req.Unit
}
if req.UnitPrice > 0 {
detail.UnitPrice = req.UnitPrice
}
// DeliveredQty 可以为 0所以需要特别处理
detail.DeliveredQty = req.DeliveredQty
if req.BillOfLading != "" {
detail.BillOfLading = req.BillOfLading
}
detail.Remark = req.Remark
// PendingQty 会在 BeforeUpdate 钩子中自动计算
return s.repo.UpdateContractDetail(detail)
}
func (s *zspContractService) GetContractDetailById(detailID string) (*model.ZSPContractDetail, error) {
return s.repo.GetContractDetailById(detailID)
}
func (s *zspContractService) ListContractDetails(contractFileID string) ([]model.ZSPContractDetail, error) {
return s.repo.ListContractDetails(contractFileID)
}
func (s *zspContractService) DeleteContractDetailById(detailID string) error {
return s.repo.DeleteContractDetailById(detailID)
}
func (s *zspContractService) BatchCreateContractDetails(req *dto.ZSPContractDetailBatchCreateRequest) error {
details := make([]model.ZSPContractDetail, 0, len(req.Details))
for _, detailReq := range req.Details {
detail := model.ZSPContractDetail{
ContractFileID: req.ContractFileID,
CommodityName: detailReq.CommodityName,
CommodityCode: detailReq.CommodityCode,
TotalQuantity: detailReq.TotalQuantity,
Unit: detailReq.Unit,
UnitPrice: detailReq.UnitPrice,
DeliveredQty: detailReq.DeliveredQty,
BillOfLading: detailReq.BillOfLading,
Remark: detailReq.Remark,
}
// PendingQty 会在 BeforeCreate 钩子中自动计算
details = append(details, detail)
}
return s.repo.BatchCreateContractDetails(details)
}