Added transaction feature, fixed some mistakes

This commit is contained in:
2026-04-11 11:12:54 +03:00
parent 6872563c62
commit 545b05d5a0
37 changed files with 2509 additions and 115 deletions
-1
View File
@@ -93,7 +93,6 @@ func (r *FamilySQLRepository) Update(ctx context.Context, family *domain.Family)
family.Name,
family.TelegramChatID,
family.TelegramChatName,
family.UpdatedAt,
family.ID,
).Scan(&family.UpdatedAt)
}
+12 -3
View File
@@ -36,7 +36,7 @@ func (r *ReceiptsSQLRepository) Create(ctx context.Context, receipt *domain.Rece
}
res, err := tx.ExecContext(ctx, `
INSERT INTO receipts (
receipt_number, ui, status, issued_at,
transaction_id, receipt_number, ui, status, issued_at,
total_amount, payment_amount, cash_amount,
another_amount, clearing_amount, margin,
currency, payment_type,
@@ -46,8 +46,9 @@ func (r *ReceiptsSQLRepository) Create(ctx context.Context, receipt *domain.Rece
kod_soato, oblast_soato, rayon_soato, selsovet_soato,
doc_num, skno_number, unp,
success
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`,
receipt.TransactionID,
receipt.ReceiptNumber,
receipt.UI,
receipt.Status,
@@ -144,6 +145,7 @@ func (r *ReceiptsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.
err := r.db.QueryRowContext(ctx, `
SELECT
id,
transaction_id,
receipt_number, ui, status, issued_at,
total_amount, payment_amount, cash_amount,
another_amount, clearing_amount, margin,
@@ -158,6 +160,7 @@ func (r *ReceiptsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.
WHERE id = ?
`, id).Scan(
&receipt.ID,
&receipt.TransactionID,
&receipt.ReceiptNumber,
&receipt.UI,
&receipt.Status,
@@ -205,6 +208,7 @@ func (r *ReceiptsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.
rows, err := r.db.QueryContext(ctx, `
SELECT
id, receipt_id,
section_number, gtin_code, product_name,
product_count, amount,
discount, surcharge,
@@ -219,6 +223,8 @@ func (r *ReceiptsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.
for rows.Next() {
var p domain.Position
if err := rows.Scan(
&p.ID,
&p.ReceiptID,
&p.SectionNumber,
&p.GTINCode,
&p.ProductName,
@@ -241,7 +247,7 @@ func (r *ReceiptsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.
func (r *ReceiptsSQLRepository) GetAll(ctx context.Context, limit, offset int) ([]*domain.Receipt, error) {
rows, err := r.db.QueryContext(ctx, `
SELECT id, receipt_number, issued_at, total_amount, currency
SELECT id, transaction_id, receipt_number, issued_at, total_amount, currency
FROM receipts
ORDER BY issued_at DESC
LIMIT ? OFFSET ?
@@ -257,6 +263,7 @@ func (r *ReceiptsSQLRepository) GetAll(ctx context.Context, limit, offset int) (
var rct domain.Receipt
if err := rows.Scan(
&rct.ID,
&rct.TransactionID,
&rct.ReceiptNumber,
&rct.IssuedAt,
&rct.TotalAmount,
@@ -280,11 +287,13 @@ func (r *ReceiptsSQLRepository) Update(ctx context.Context, receipt *domain.Rece
_, err = tx.ExecContext(ctx, `
UPDATE receipts SET
transaction_id = ?,
issued_at = ?,
total_amount = ?,
currency = ?
WHERE id = ?
`,
receipt.TransactionID,
receipt.IssuedAt,
receipt.TotalAmount,
receipt.Currency,
+279
View File
@@ -0,0 +1,279 @@
package repositories
import (
"FamilyHub/src/domain"
"context"
"database/sql"
"errors"
"fmt"
"strings"
)
var ErrReceiptNotFound = errors.New("receipt not found")
type TransactionRepository interface {
Create(ctx context.Context, transaction *domain.Transaction) error
GetByID(ctx context.Context, id int64) (*domain.Transaction, error)
List(ctx context.Context, filter domain.TransactionListFilter) ([]*domain.Transaction, error)
Update(ctx context.Context, transaction *domain.Transaction, syncReceipt bool) error
Delete(ctx context.Context, id int64) error
}
type TransactionsSQLRepository struct {
db *sql.DB
}
func NewTransactionsSQLRepository(db *sql.DB) *TransactionsSQLRepository {
return &TransactionsSQLRepository{db: db}
}
func (r *TransactionsSQLRepository) Create(ctx context.Context, transaction *domain.Transaction) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
query := `
INSERT INTO transactions
(family_id, description, type, datetime, category, amount, created_by)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, created_at
`
if err := tx.QueryRowContext(
ctx,
query,
transaction.FamilyID,
transaction.Description,
transaction.Type,
transaction.DateTime,
transaction.Category,
transaction.Amount,
transaction.CreatedBy,
).Scan(&transaction.ID, &transaction.CreatedAt); err != nil {
return err
}
if err := r.rebindReceipt(ctx, tx, transaction.ID, transaction.ReceiptID); err != nil {
return err
}
return tx.Commit()
}
func (r *TransactionsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.Transaction, error) {
query := `
SELECT
t.id,
t.family_id,
t.description,
t.type,
t.datetime,
t.category,
t.amount,
t.created_at,
t.created_by,
r.id
FROM transactions t
LEFT JOIN receipts r ON r.transaction_id = t.id
WHERE t.id = $1
`
var transaction domain.Transaction
err := r.db.QueryRowContext(ctx, query, id).Scan(
&transaction.ID,
&transaction.FamilyID,
&transaction.Description,
&transaction.Type,
&transaction.DateTime,
&transaction.Category,
&transaction.Amount,
&transaction.CreatedAt,
&transaction.CreatedBy,
&transaction.ReceiptID,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, err
}
return &transaction, nil
}
func (r *TransactionsSQLRepository) List(ctx context.Context, filter domain.TransactionListFilter) ([]*domain.Transaction, error) {
var (
whereClauses []string
args []any
)
baseQuery := `
SELECT
t.id,
t.family_id,
t.description,
t.type,
t.datetime,
t.category,
t.amount,
t.created_at,
t.created_by,
r.id
FROM transactions t
LEFT JOIN receipts r ON r.transaction_id = t.id
`
appendFilter := func(condition string, value any) {
args = append(args, value)
whereClauses = append(whereClauses, fmt.Sprintf(condition, len(args)))
}
if filter.FamilyID != nil {
appendFilter("t.family_id = $%d", *filter.FamilyID)
}
if filter.CreatedBy != nil {
appendFilter("t.created_by = $%d", *filter.CreatedBy)
}
if filter.Type != nil {
appendFilter("t.type = $%d", *filter.Type)
}
if filter.Category != nil {
appendFilter("t.category = $%d", *filter.Category)
}
if filter.DateFrom != nil {
appendFilter("t.datetime >= $%d", *filter.DateFrom)
}
if filter.DateTo != nil {
appendFilter("t.datetime <= $%d", *filter.DateTo)
}
var queryBuilder strings.Builder
queryBuilder.WriteString(baseQuery)
if len(whereClauses) > 0 {
queryBuilder.WriteString(" WHERE ")
queryBuilder.WriteString(strings.Join(whereClauses, " AND "))
}
args = append(args, filter.Limit, filter.Offset)
queryBuilder.WriteString(fmt.Sprintf(" ORDER BY t.datetime DESC LIMIT $%d OFFSET $%d", len(args)-1, len(args)))
rows, err := r.db.QueryContext(ctx, queryBuilder.String(), args...)
if err != nil {
return nil, err
}
defer rows.Close()
var transactions []*domain.Transaction
for rows.Next() {
var transaction domain.Transaction
if err := rows.Scan(
&transaction.ID,
&transaction.FamilyID,
&transaction.Description,
&transaction.Type,
&transaction.DateTime,
&transaction.Category,
&transaction.Amount,
&transaction.CreatedAt,
&transaction.CreatedBy,
&transaction.ReceiptID,
); err != nil {
return nil, err
}
transactions = append(transactions, &transaction)
}
return transactions, rows.Err()
}
func (r *TransactionsSQLRepository) Update(ctx context.Context, transaction *domain.Transaction, syncReceipt bool) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
query := `
UPDATE transactions SET
description = $1,
type = $2,
datetime = $3,
category = $4,
amount = $5
WHERE id = $6
`
result, err := tx.ExecContext(
ctx,
query,
transaction.Description,
transaction.Type,
transaction.DateTime,
transaction.Category,
transaction.Amount,
transaction.ID,
)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return sql.ErrNoRows
}
if syncReceipt {
if err := r.rebindReceipt(ctx, tx, transaction.ID, transaction.ReceiptID); err != nil {
return err
}
}
return tx.Commit()
}
func (r *TransactionsSQLRepository) Delete(ctx context.Context, id int64) error {
result, err := r.db.ExecContext(ctx, `DELETE FROM transactions WHERE id = $1`, id)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return sql.ErrNoRows
}
return nil
}
func (r *TransactionsSQLRepository) rebindReceipt(ctx context.Context, tx *sql.Tx, transactionID int64, receiptID *int64) error {
if _, err := tx.ExecContext(ctx, `UPDATE receipts SET transaction_id = NULL WHERE transaction_id = $1`, transactionID); err != nil {
return err
}
if receiptID == nil {
return nil
}
result, err := tx.ExecContext(ctx, `UPDATE receipts SET transaction_id = $1 WHERE id = $2`, transactionID, *receiptID)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrReceiptNotFound
}
return nil
}