1
This commit is contained in:
313
internal/database/postgres/bank_account.go
Normal file
313
internal/database/postgres/bank_account.go
Normal file
@@ -0,0 +1,313 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
dberrors "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/errors"
|
||||
dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types"
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ALERT: intergrate safety checks
|
||||
|
||||
func (c *client) GetBankAccountList(
|
||||
ctx context.Context,
|
||||
request *dbtypes.BankAccountListGetRequest,
|
||||
) (*dbtypes.BankAccountListGetResponse, error) {
|
||||
if request == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
|
||||
)
|
||||
|
||||
getAccounts := psql.Select(
|
||||
"id", "owner_id", "account_number", "bank_name", "bik",
|
||||
"correspondent_account", "is_primary", "created_at", "updated_at",
|
||||
).From(bankAccountsTable).
|
||||
Where(squirrel.Eq{"owner_id": request.OwnerId})
|
||||
|
||||
query, args, err := getAccounts.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building get bank accounts query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rows, err := c.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing get bank accounts query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
var accounts []dbtypes.BankAccount
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
accountNumber, bankName, bik, correspondentAccount sql.NullString
|
||||
bankAccount dbtypes.BankAccount
|
||||
)
|
||||
|
||||
if err := rows.Scan(
|
||||
&bankAccount.Id, &bankAccount.OwnerId, &accountNumber, &bankName, &bik,
|
||||
&correspondentAccount, &bankAccount.IsPrimary, &bankAccount.CreatedAt, &bankAccount.UpdatedAt,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if accountNumber.Valid {
|
||||
bankAccount.AccountName = accountNumber.String
|
||||
}
|
||||
|
||||
if bankName.Valid {
|
||||
bankAccount.BankName = bankName.String
|
||||
}
|
||||
|
||||
if bik.Valid {
|
||||
bankAccount.Bik = bik.String
|
||||
}
|
||||
|
||||
if correspondentAccount.Valid {
|
||||
bankAccount.CorrespondentAccount = correspondentAccount.String
|
||||
}
|
||||
|
||||
accounts = append(accounts, bankAccount)
|
||||
}
|
||||
|
||||
return &dbtypes.BankAccountListGetResponse{
|
||||
BankAccounts: accounts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) CreateBankAccount(
|
||||
ctx context.Context,
|
||||
request *dbtypes.BankAccountCreateRequest,
|
||||
) (*dbtypes.BankAccountCreateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
|
||||
)
|
||||
|
||||
// TODO: use normal uuid after DB reengineering
|
||||
bankAccountId := fmt.Sprintf("%sBNK", strings.ReplaceAll(uuid.NewString(), "-", ""))
|
||||
|
||||
createBankAccount := psql.Insert(bankAccountsTable).
|
||||
Columns(
|
||||
"id", "owner_id", "account_number", "bank_name", "bik", "correspondent_account", "is_primary",
|
||||
).
|
||||
Values(
|
||||
bankAccountId, request.OwnerId, request.AccountNumber, request.BankName,
|
||||
request.Bik, request.CorrespondentAccount, request.IsPrimary,
|
||||
)
|
||||
|
||||
query, args, err := createBankAccount.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building create bank account query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := c.db.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing create bank account query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for create bank account query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return &dbtypes.BankAccountCreateResponse{
|
||||
Id: bankAccountId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) UpdateBankAccount(
|
||||
ctx context.Context,
|
||||
request *dbtypes.BankAccountUpdateRequest,
|
||||
) (*dbtypes.BankAccountUpdateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
tx, err := c.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error starting transaction: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
result, err := c.updateBankAccount(ctx, tx, request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating bank account: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("%w: error committing transaction: %w", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *client) updateBankAccount(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
request *dbtypes.BankAccountUpdateRequest,
|
||||
) (*dbtypes.BankAccountUpdateResponse, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
|
||||
)
|
||||
|
||||
updateAccount := psql.Update(bankAccountsTable).
|
||||
Where(squirrel.Eq{"id": request.Id})
|
||||
|
||||
if request.AccountNumber != nil {
|
||||
updateAccount = updateAccount.Set("account_number", *request.AccountNumber)
|
||||
}
|
||||
|
||||
// TODO: uncomment when DB supports it
|
||||
// if request.AccountName != nil {
|
||||
// updateAccount = updateAccount.Set("account_name", *request.AccountName)
|
||||
// }
|
||||
|
||||
if request.BankName != nil {
|
||||
updateAccount = updateAccount.Set("bank_name", *request.BankName)
|
||||
}
|
||||
|
||||
if request.Bik != nil {
|
||||
updateAccount = updateAccount.Set("bik", *request.Bik)
|
||||
}
|
||||
|
||||
if request.CorrespondentAccount != nil {
|
||||
updateAccount = updateAccount.Set("correspondent_account", *request.CorrespondentAccount)
|
||||
}
|
||||
|
||||
if request.IsPrimary != nil {
|
||||
if *request.IsPrimary {
|
||||
if err := c.unmarkPrimaryBankAccount(ctx, driver, request.Id); err != nil {
|
||||
return nil, fmt.Errorf("error unmarking currently primary bank account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// QnA: what if the update makes all BAs non-primary?
|
||||
updateAccount = updateAccount.Set("is_primary", *request.IsPrimary)
|
||||
}
|
||||
|
||||
query, args, err := updateAccount.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building 'update bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing 'update bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for 'update bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return &dbtypes.BankAccountUpdateResponse{}, nil
|
||||
}
|
||||
|
||||
func (c *client) unmarkPrimaryBankAccount(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
bankAccountId string,
|
||||
) error {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
|
||||
)
|
||||
|
||||
getOwnerId := psql.Select(
|
||||
"owner_id", "is_primary",
|
||||
).From(bankAccountsTable).
|
||||
Where(squirrel.Eq{"id": bankAccountId}).
|
||||
Suffix("FOR UPDATE")
|
||||
|
||||
query, args, err := getOwnerId.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error building 'get owner id of bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := driver.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var (
|
||||
ownerId string
|
||||
isPrimary bool
|
||||
)
|
||||
|
||||
if err := row.Scan(&ownerId, &isPrimary); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: error scanning row for 'get owner id of bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if isPrimary {
|
||||
return nil
|
||||
}
|
||||
|
||||
unmarkPrimaryBA := psql.Update(bankAccountsTable).
|
||||
Set("is_primary", false).
|
||||
Where(squirrel.Eq{
|
||||
"owner_id": ownerId,
|
||||
"is_primary": true,
|
||||
})
|
||||
|
||||
query, args, err = unmarkPrimaryBA.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error building 'unmark primary bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error executing 'unmark primary bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error getting rows affected for 'unmark primary bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return fmt.Errorf("%w: error unmarking primary bank account: no rows affected", dberrors.ErrInternal)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) DeleteBankAccount(
|
||||
ctx context.Context,
|
||||
request *dbtypes.BankAccountDeleteRequest,
|
||||
) (*dbtypes.BankAccountDeleteResponse, error) {
|
||||
return nil, dberrors.ErrUnimplemented
|
||||
}
|
Reference in New Issue
Block a user