增加发票识别差额功能

This commit is contained in:
2026-08-13 18:00:46 +08:00
parent f818ecb74f
commit a344aaedf3
7 changed files with 921 additions and 19 deletions
+91 -4
View File
@@ -31,8 +31,9 @@ type reimbursementItemPayload struct {
UnitPrice float64 `json:"unit_price"`
Description string `json:"description"`
ReceiptFlag int8 `json:"receipt_flag"`
ReceiptFileIDs string `json:"receipt_file_ids"`
ReceiptURLs string `json:"receipt_urls"`
ReceiptFileIDs string `json:"receipt_file_ids"`
ReceiptURLs string `json:"receipt_urls"`
ReceiptAmounts *string `json:"receipt_amounts,omitempty"`
}
type reimbursementPayload struct {
@@ -122,12 +123,77 @@ func joinReceiptFileIDs(ids []uint64) string {
return strings.Join(parts, ",")
}
func parseReceiptAmounts(raw string) []string {
raw = strings.TrimSpace(raw)
if raw == "" {
return []string{}
}
parts := strings.Split(raw, ",")
result := make([]string, len(parts))
for i, part := range parts {
result[i] = strings.TrimSpace(part)
}
return result
}
func joinReceiptAmounts(parts []string) string {
if len(parts) == 0 {
return ""
}
return strings.Join(parts, ",")
}
func normalizeReceiptAmounts(raw string, idCount int) string {
parts := parseReceiptAmounts(raw)
if idCount <= 0 {
return ""
}
if len(parts) < idCount {
padding := make([]string, idCount-len(parts))
parts = append(parts, padding...)
} else if len(parts) > idCount {
parts = parts[:idCount]
}
normalized := make([]string, idCount)
for i := 0; i < idCount; i++ {
part := strings.TrimSpace(parts[i])
if part == "" {
normalized[i] = ""
continue
}
value, err := strconv.ParseFloat(part, 64)
if err != nil || value < 0 {
normalized[i] = ""
continue
}
normalized[i] = fmt.Sprintf("%.2f", value)
}
return joinReceiptAmounts(normalized)
}
func remapReceiptAmounts(oldIDs []uint64, oldAmounts []string, newIDs []uint64) string {
oldMap := map[uint64]string{}
for i, id := range oldIDs {
if i < len(oldAmounts) {
oldMap[id] = oldAmounts[i]
}
}
parts := make([]string, len(newIDs))
for i, id := range newIDs {
if amount, ok := oldMap[id]; ok {
parts[i] = amount
}
}
return joinReceiptAmounts(parts)
}
func resolveReceiptFiles(scheme, host string, item models.BackendReimbursementItem) []map[string]interface{} {
ids := parseReceiptFileIDs(item.ReceiptFileIDs)
if len(ids) == 0 {
return []map[string]interface{}{}
}
urlParts := strings.Split(item.ReceiptURLs, ",")
amountParts := parseReceiptAmounts(item.ReceiptAmounts)
files := make([]map[string]interface{}, 0, len(ids))
for i, id := range ids {
url := ""
@@ -147,8 +213,14 @@ func resolveReceiptFiles(scheme, host string, item models.BackendReimbursementIt
if name == "" && url != "" {
name = path.Base(strings.Split(url, "?")[0])
}
var amount interface{}
if i < len(amountParts) && amountParts[i] != "" {
if value, err := strconv.ParseFloat(amountParts[i], 64); err == nil {
amount = value
}
}
files = append(files, map[string]interface{}{
"file_id": id, "url": url, "name": name,
"file_id": id, "url": url, "name": name, "amount": amount,
})
}
return files
@@ -159,6 +231,7 @@ func syncReceiptFields(item *models.BackendReimbursementItem, scheme, host strin
if len(files) == 0 {
item.ReceiptFileIDs = ""
item.ReceiptURLs = ""
item.ReceiptAmounts = ""
item.ReceiptFlag = 0
return
}
@@ -178,21 +251,32 @@ func syncReceiptFields(item *models.BackendReimbursementItem, scheme, host strin
}
func applyReceiptToItem(item *models.BackendReimbursementItem, payload reimbursementItemPayload, scheme, host string) {
oldIDs := parseReceiptFileIDs(item.ReceiptFileIDs)
oldAmounts := parseReceiptAmounts(item.ReceiptAmounts)
if payload.ReceiptFlag == 0 && strings.TrimSpace(payload.ReceiptFileIDs) == "" {
item.ReceiptFileIDs = ""
item.ReceiptURLs = ""
item.ReceiptAmounts = ""
item.ReceiptFlag = 0
return
}
if strings.TrimSpace(payload.ReceiptFileIDs) != "" {
item.ReceiptFileIDs = strings.TrimSpace(payload.ReceiptFileIDs)
syncReceiptFields(item, scheme, host)
newIDs := parseReceiptFileIDs(item.ReceiptFileIDs)
if payload.ReceiptAmounts != nil {
item.ReceiptAmounts = normalizeReceiptAmounts(*payload.ReceiptAmounts, len(newIDs))
} else {
item.ReceiptAmounts = remapReceiptAmounts(oldIDs, oldAmounts, newIDs)
}
return
}
item.ReceiptFlag = payload.ReceiptFlag
item.ReceiptURLs = strings.TrimSpace(payload.ReceiptURLs)
if item.ReceiptURLs == "" && item.ReceiptFlag == 0 {
item.ReceiptFileIDs = ""
item.ReceiptAmounts = ""
}
}
@@ -216,13 +300,16 @@ func reimbursementItemDTO(item models.BackendReimbursementItem, scheme, host str
"amount": item.Amount, "quantity": item.Quantity, "unit_price": item.UnitPrice,
"description": item.Description, "receipt_flag": receiptFlag,
"receipt_file_ids": item.ReceiptFileIDs,
"receipt_urls": receiptURLs, "receipt_files": receiptFiles,
"receipt_urls": receiptURLs, "receipt_amounts": item.ReceiptAmounts,
"receipt_files": receiptFiles,
}
}
func reimbursementDTO(row models.BackendReimbursement, items []models.BackendReimbursementItem, scheme, host string) map[string]interface{} {
_, userName := services.OperationLogUser(&row.Tid, row.UserID)
result := map[string]interface{}{
"id": row.ID, "tid": row.Tid, "uid": row.UID, "user_id": row.UserID,
"user_name": userName,
"department_id": row.DepartmentID, "total_amount": row.TotalAmount,
"status": row.Status, "apply_date": row.ApplyDate.Format("2006-01-02"),
"description": row.Description, "current_approver_id": row.CurrentApproverID,