Files
FamilyHUB/backend/src/integrations/receiptService/receipt_service.go
T

230 lines
5.3 KiB
Go

package receiptService
import (
"FamilyHub/src/domain"
"FamilyHub/src/repositories"
"FamilyHub/src/utils"
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"log"
"mime/multipart"
"net/http"
"strings"
"time"
)
type ReceiptService struct {
client *http.Client
repo repositories.ReceiptsRepository
transactionRepo repositories.TransactionRepository
}
func NewReceiptService(
repo repositories.ReceiptsRepository,
transactionRepo repositories.TransactionRepository,
) *ReceiptService {
return &ReceiptService{
client: &http.Client{
Timeout: 60 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
},
repo: repo,
transactionRepo: transactionRepo,
}
}
func (s *ReceiptService) GetReceipt(
ctx context.Context,
req domain.AddReceiptRequest,
) (*domain.Receipt, error) {
url := "https://ch.info-center.by/ajax/check1.php"
var receipt domain.Receipt
body, contentType := buildMultipartBody(req.Date, req.Number)
httpReq, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
url,
body,
)
if err != nil {
log.Println(err.Error())
return nil, err
}
httpReq.Header.Set("Content-Type", contentType)
resp, err := s.client.Do(httpReq)
if err != nil {
log.Println(err.Error())
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Printf("external service returned %d\n", resp.StatusCode)
return nil, fmt.Errorf("external service returned %d", resp.StatusCode)
}
var raw struct {
Message map[string]interface{} `json:"message"`
}
if err := json.NewDecoder(resp.Body).Decode(&raw); err != nil {
log.Printf("external service returned %s\n", err.Error())
return nil, err
}
bytes_, _ := json.Marshal(raw.Message)
if err := json.Unmarshal(bytes_, &receipt); err != nil {
return nil, err
}
if receipt.IssuedAtRaw == "" {
return nil, errors.New("receipt not found")
}
positions, err := parsePositions(receipt.PositionsRaw)
if err != nil {
log.Printf("failed to parse positions: %s", err.Error())
return nil, err
}
receipt.IssuedAt, err = utils.ParseIssuedAt(receipt.IssuedAtRaw)
if err != nil {
log.Printf("failed to parse issued at: %s", err.Error())
return nil, err
}
receipt.Positions = positions
for i := range receipt.Positions {
p := &receipt.Positions[i]
p.ProductCount, err = utils.ParseFloat(p.ProductCountRaw)
if err != nil {
log.Printf("failed to parse product count: %s", err.Error())
return nil, err
}
p.Amount, err = utils.ParseFloat(p.AmountRaw)
if err != nil {
log.Printf("failed to parse amount: %s", err.Error())
return nil, err
}
p.Discount, _ = utils.ParseFloat(p.DiscountRaw)
p.Surcharge, _ = utils.ParseFloat(p.SurchargeRaw)
}
receiptID, err := s.repo.Create(ctx, &receipt)
if err != nil {
return nil, err
}
receipt.ID = int(receiptID)
if s.shouldCreateTransaction(req) {
transaction, err := s.createTransactionForReceipt(ctx, &receipt, req, receiptID)
if err != nil {
if rollbackErr := s.repo.Delete(ctx, receiptID); rollbackErr != nil {
log.Printf("failed to rollback receipt %d after transaction error: %v", receiptID, rollbackErr)
}
return nil, err
}
receipt.TransactionID = &transaction.ID
}
return &receipt, nil
}
func buildMultipartBody(date, number string) (*bytes.Buffer, string) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
_ = writer.WriteField("orig_date", date)
_ = writer.WriteField("orig_ui", number)
_ = writer.Close()
return body, writer.FormDataContentType()
}
func parsePositions(raw string) ([]domain.Position, error) {
var positions []domain.Position
if raw == "" {
return positions, nil
}
if err := json.Unmarshal([]byte(raw), &positions); err != nil {
return nil, err
}
return positions, nil
}
func (s *ReceiptService) shouldCreateTransaction(req domain.AddReceiptRequest) bool {
return s.transactionRepo != nil && req.FamilyID != nil && req.CreatedBy != nil
}
func (s *ReceiptService) createTransactionForReceipt(
ctx context.Context,
receipt *domain.Receipt,
req domain.AddReceiptRequest,
receiptID int64,
) (*domain.Transaction, error) {
transactionType := "expense"
if req.Type != nil && strings.TrimSpace(*req.Type) != "" {
transactionType = strings.TrimSpace(*req.Type)
}
category := "receipt"
if req.Category != nil && strings.TrimSpace(*req.Category) != "" {
category = strings.TrimSpace(*req.Category)
}
description := buildReceiptTransactionDescription(receipt, req.Description)
transaction := &domain.Transaction{
FamilyID: *req.FamilyID,
Description: description,
Type: transactionType,
DateTime: receipt.IssuedAt,
Category: category,
Amount: receipt.TotalAmount,
CreatedBy: *req.CreatedBy,
ReceiptID: &receiptID,
}
if err := s.transactionRepo.Create(ctx, transaction); err != nil {
return nil, err
}
return transaction, nil
}
func buildReceiptTransactionDescription(receipt *domain.Receipt, explicit *string) *string {
if explicit != nil && strings.TrimSpace(*explicit) != "" {
value := strings.TrimSpace(*explicit)
return &value
}
if name := strings.TrimSpace(receipt.NameSPD); name != "" {
return &name
}
if number := strings.TrimSpace(receipt.ReceiptNumber); number != "" {
value := fmt.Sprintf("Receipt %s", number)
return &value
}
return nil
}