github.com/shoshinnikita/budget-manager@v0.7.1-0.20220131195411-8c46ff1c6778/internal/db/base/spend.go (about)

     1  package base
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"time"
     7  
     8  	common "github.com/ShoshinNikita/budget-manager/internal/db"
     9  	"github.com/ShoshinNikita/budget-manager/internal/db/base/internal/sqlx"
    10  	"github.com/ShoshinNikita/budget-manager/internal/db/base/types"
    11  	"github.com/ShoshinNikita/budget-manager/internal/pkg/errors"
    12  	"github.com/ShoshinNikita/budget-manager/internal/pkg/money"
    13  )
    14  
    15  type Spend struct {
    16  	ID     uint         `db:"id"`
    17  	DayID  uint         `db:"day_id"`
    18  	Title  string       `db:"title"`
    19  	TypeID types.Uint   `db:"type_id"`
    20  	Notes  types.String `db:"notes"`
    21  	Cost   money.Money  `db:"cost"`
    22  
    23  	Type *SpendType `db:"type"`
    24  }
    25  
    26  // ToCommon converts Spend to common Spend structure from
    27  // "github.com/ShoshinNikita/budget-manager/internal/db" package
    28  func (s Spend) ToCommon(year int, month time.Month, day int) common.Spend {
    29  	return common.Spend{
    30  		ID:    s.ID,
    31  		Year:  year,
    32  		Month: month,
    33  		Day:   day,
    34  		Title: s.Title,
    35  		Type:  s.Type.ToCommon(),
    36  		Notes: string(s.Notes),
    37  		Cost:  s.Cost,
    38  	}
    39  }
    40  
    41  // AddSpend adds a new Spend
    42  func (db DB) AddSpend(ctx context.Context, args common.AddSpendArgs) (id uint, err error) {
    43  	err = db.db.RunInTransaction(ctx, func(tx *sqlx.Tx) (err error) {
    44  		if !checkDay(tx, args.DayID) {
    45  			return common.ErrDayNotExist
    46  		}
    47  		if args.TypeID != 0 && !checkSpendType(tx, args.TypeID) {
    48  			return common.ErrSpendTypeNotExist
    49  		}
    50  
    51  		err = tx.Get(
    52  			&id,
    53  			`INSERT INTO spends(day_id, title, notes, type_id, cost) VALUES(?, ?, ?, ?, ?) RETURNING id`,
    54  			args.DayID, args.Title, args.Notes, types.Uint(args.TypeID), args.Cost,
    55  		)
    56  		if err != nil {
    57  			return err
    58  		}
    59  
    60  		monthID, err := db.selectMonthIDByDayID(tx, args.DayID)
    61  		if err != nil {
    62  			return err
    63  		}
    64  		return db.recomputeAndUpdateMonth(tx, monthID)
    65  	})
    66  	if err != nil {
    67  		return 0, err
    68  	}
    69  
    70  	return id, nil
    71  }
    72  
    73  // EditSpend edits existeng Spend
    74  func (db DB) EditSpend(ctx context.Context, args common.EditSpendArgs) error {
    75  	return db.db.RunInTransaction(ctx, func(tx *sqlx.Tx) error {
    76  		if !checkSpend(tx, args.ID) {
    77  			return common.ErrSpendNotExist
    78  		}
    79  		if args.TypeID != nil && *args.TypeID != 0 && !checkSpendType(tx, *args.TypeID) {
    80  			return common.ErrSpendTypeNotExist
    81  		}
    82  
    83  		dayID, err := db.selectSpendDayID(tx, args.ID)
    84  		if err != nil {
    85  			return err
    86  		}
    87  
    88  		query := newUpdateQueryBuilder("spends", args.ID)
    89  		if args.Title != nil {
    90  			query.Set("title", *args.Title)
    91  		}
    92  		if args.TypeID != nil {
    93  			if *args.TypeID == 0 {
    94  				query.Set("type_id", nil)
    95  			} else {
    96  				query.Set("type_id", *args.TypeID)
    97  			}
    98  		}
    99  		if args.Notes != nil {
   100  			query.Set("notes", *args.Notes)
   101  		}
   102  		if args.Cost != nil {
   103  			query.Set("cost", *args.Cost)
   104  		}
   105  		if _, err := tx.ExecQuery(query); err != nil {
   106  			return err
   107  		}
   108  
   109  		if args.Cost != nil {
   110  			// Recompute month only when cost has been changed
   111  			monthID, err := db.selectMonthIDByDayID(tx, dayID)
   112  			if err != nil {
   113  				return err
   114  			}
   115  			return db.recomputeAndUpdateMonth(tx, monthID)
   116  		}
   117  		return nil
   118  	})
   119  }
   120  
   121  // RemoveSpend removes Spend with passed id
   122  func (db DB) RemoveSpend(ctx context.Context, id uint) error {
   123  	return db.db.RunInTransaction(ctx, func(tx *sqlx.Tx) error {
   124  		if !checkSpend(tx, id) {
   125  			return common.ErrSpendNotExist
   126  		}
   127  
   128  		dayID, err := db.selectSpendDayID(tx, id)
   129  		if err != nil {
   130  			return err
   131  		}
   132  
   133  		_, err = tx.Exec(`DELETE FROM spends WHERE id = ?`, id)
   134  		if err != nil {
   135  			return err
   136  		}
   137  
   138  		monthID, err := db.selectMonthIDByDayID(tx, dayID)
   139  		if err != nil {
   140  			return err
   141  		}
   142  		return db.recomputeAndUpdateMonth(tx, monthID)
   143  	})
   144  }
   145  
   146  func (DB) selectSpendDayID(tx *sqlx.Tx, id uint) (dayID uint, err error) {
   147  	err = tx.Get(&dayID, `SELECT day_id FROM spends WHERE id = ?`, id)
   148  	if err != nil {
   149  		return 0, errors.Wrap(err, "couldn't select day id of Spend")
   150  	}
   151  	return dayID, nil
   152  }
   153  
   154  func (DB) selectMonthIDByDayID(tx *sqlx.Tx, dayID uint) (monthID uint, err error) {
   155  	err = tx.Get(&monthID, `SELECT month_id FROM days WHERE id = ?`, dayID)
   156  	if err != nil {
   157  		if errors.Is(err, sql.ErrNoRows) {
   158  			return 0, common.ErrDayNotExist
   159  		}
   160  		return 0, errors.Wrap(err, "couldn't get Month which contains Day with passed id")
   161  	}
   162  	return monthID, nil
   163  }