commands

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 12, 2024 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Copyright 2023 PraserX

Copyright 2023 PraserX

Index

Constants

This section is empty.

Variables

View Source
var Billing = cli.Command{
	Name:    "billing",
	Aliases: []string{"b"},
	Usage:   "Billing periods and bills management",
	Flags: []cli.Flag{
		&flags.FlagPlainPrint,
	},
	Subcommands: []*cli.Command{
		&BillingPeriods,
		&BillingBills,
	},
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		bills, err := database.SelectAllBills()
		if err != nil {
			return fmt.Errorf("error: cannot get bills: %v", err.Error())
		}

		if !ctx.Bool("plain") {
			t := table.NewWriter()
			t.AppendHeader(table.Row{"Bill ID", "Period ID", "Amount", "Issued", "Paid", "Confirmed"})
			for _, bill := range bills {
				t.AppendRow(table.Row{bill.ID, bill.PeriodID, bill.Amount, bill.Issued, bill.Paid, bill.PaymentConfirmation})
			}
			fmt.Println(t.Render())
		} else {
			for _, bill := range bills {
				fmt.Printf("%d %d %.2f %t %t %t\n", bill.ID, bill.PeriodID, bill.Amount, bill.Issued, bill.Paid, bill.PaymentConfirmation)
			}
		}

		return nil
	},
}
View Source
var BillingBills = cli.Command{
	Name:    "bills",
	Aliases: []string{"b"},
	Usage:   "Bills management section (add, issue, pay, confirm)",
	Subcommands: []*cli.Command{
		&BillingBillsAdd,
		&BillingBillsIssue,
		&BillingBillsPay,
		&BillingBillsPayCSV,
		&BillingBillsConfirmPayment,
		&BillingBillsUnpaidNotification,
	},
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:    "all",
			Aliases: []string{"a"},
			Usage:   "print all bills overtime",
		},
		&cli.BoolFlag{
			Name:    "paid",
			Aliases: []string{"p"},
			Usage:   "print all bills which were already paid",
		},
		&cli.BoolFlag{
			Name:    "unpaid",
			Aliases: []string{"u"},
			Usage:   "print all bills which waiting for payment",
		},
		&cli.IntFlag{
			Name:    "sort",
			Aliases: []string{"s"},
			Usage:   "sort output by quantity (1: high-low, 2: low-high) or amount (3: high-low, 4:low-high)",
		},
	},
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		bills, err := database.SelectAllBills()
		if err != nil {
			return fmt.Errorf("error: cannot get billing periods: %v", err.Error())
		}

		var display []models.Bill
		var quantity int
		var amount float32

		for _, bill := range bills {
			if ctx.Bool("all") {
				quantity += bill.Quantity
				amount += bill.Amount
				display = append(display, bill)
			} else if ctx.Bool("paid") && bill.Paid {
				quantity += bill.Quantity
				amount += bill.Amount
				display = append(display, bill)
			} else if ctx.Bool("unpaid") && !bill.Paid {
				quantity += bill.Quantity
				amount += bill.Amount
				display = append(display, bill)
			}
		}

		switch variant := ctx.Int("sort"); variant {
		case 1:
			sort.Slice(display, func(i, j int) bool {
				return display[i].Quantity > display[j].Quantity
			})
		case 2:
			sort.Slice(display, func(i, j int) bool {
				return display[i].Quantity < display[j].Quantity
			})
		case 3:
			sort.Slice(display, func(i, j int) bool {
				return display[i].Amount > display[j].Amount
			})
		case 4:
			sort.Slice(display, func(i, j int) bool {
				return display[i].Amount < display[j].Amount
			})
		default:
		}

		t := table.NewWriter()
		t.AppendHeader(table.Row{"ID", "PID", "UID", "Name", "Quantity", "Amount", "Issued", "Paid", "Payment Confirmed"})
		for _, bill := range display {
			user, err := database.SelectUserByID(bill.UserID)
			if err != nil {
				return fmt.Errorf("error: cannot get user: %v", err.Error())
			}

			t.AppendRow(table.Row{bill.ID, bill.PeriodID, bill.UserID, user.Firstname + " " + user.Lastname, fmt.Sprintf("%d", bill.Quantity), fmt.Sprintf("%.2f", bill.Amount), fmt.Sprintf("%t", bill.Issued), fmt.Sprintf("%t", bill.Paid), fmt.Sprintf("%t", bill.PaymentConfirmation)})
		}
		t.AppendFooter(table.Row{"", "", "", "", fmt.Sprintf("%d", quantity), fmt.Sprintf("%.2f", amount), "", "", ""})
		fmt.Println(t.Render())

		return nil
	},
}
View Source
var BillingBillsAdd = cli.Command{
	Name:      "add",
	Aliases:   []string{"a"},
	Usage:     "Add bill for given user and period with specified quantity",
	ArgsUsage: "[period_id user_id quantity]",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		if ctx.NArg() != 3 {
			return fmt.Errorf("error: too few arguments: requires (4), get (%d)", ctx.NArg())
		}

		pid, err := strconv.ParseUint(ctx.Args().Get(0), 10, 32)
		if err != nil {
			return fmt.Errorf("error: cannot parse uint: %v", err)
		}

		uid, err := strconv.ParseUint(ctx.Args().Get(1), 10, 64)
		if err != nil {
			return fmt.Errorf("error: cannot parse uint: %v", err)
		}

		quantity, err := strconv.ParseInt(ctx.Args().Get(2), 10, 64)
		if err != nil {
			return fmt.Errorf("error: cannot parse int: %v", err)
		}

		user, err := database.SelectUserByID(uint(uid))
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fmt.Errorf("error: user not found")
		} else if err != nil {
			return fmt.Errorf("error: cannot get user: %v", err)
		}

		period, err := database.SelectPeriodByID(uint(pid))
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fmt.Errorf("error: billing period not found")
		} else if err != nil {
			return fmt.Errorf("error: cannot get billing period: %v", err)
		}

		if period.Closed {
			return fmt.Errorf("error: billing period is already closed")
		}

		bill := models.Bill{
			Quantity: int(quantity),
			UserID:   uint(uid),
			PeriodID: uint(pid),
		}

		id, err := database.InsertBill(bill)
		if err != nil {
			return fmt.Errorf("error: cannot add new bill to billing period: %v", err.Error())
		}

		logger.Info(fmt.Sprintf("new bill successfully added to billing period for user_id=%d, user_name=%s: new bill id: %d", uid, user.Firstname+" "+user.Lastname, id))

		return nil
	},
}
View Source
var BillingBillsConfirmPayment = cli.Command{
	Name:      "confirm",
	Aliases:   []string{"c"},
	Usage:     "Send payment confirmation for all paid bills for given period (e-mail will not be sent for already notified users)",
	ArgsUsage: "[period_id]",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		if err = helpers.SetupMail(ctx); err != nil {
			return err
		}

		if ctx.NArg() != 1 {
			return fmt.Errorf("error: too few arguments: requires (1), get (%d)", ctx.NArg())
		}

		pid, err := strconv.ParseUint(ctx.Args().Get(0), 10, 32)
		if err != nil {
			return fmt.Errorf("error: cannot parse uint: %v", err)
		}

		period, err := database.SelectPeriodByID(uint(pid))
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fmt.Errorf("error: billing period not found")
		} else if err != nil {
			return fmt.Errorf("error: cannot get billing period: %v", err)
		}

		if !period.Closed {
			return fmt.Errorf("error: cannot send payment confirmation: period is not closed")
		}

		bills, err := database.SelectAllBillsForPeriod(uint(pid))
		if err != nil {
			return fmt.Errorf("error: cannot get bills for given period: %v", err)
		}

		for _, bill := range bills {
			user, err := database.SelectUserByID(bill.UserID)
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return fmt.Errorf("error: user for giver bill is not found: user_id=%d", bill.UserID)
			} else if err != nil {
				return fmt.Errorf("error: cannot get user: user_id=%d: %v", bill.UserID, err)
			}

			if !bill.PaymentConfirmation && bill.Paid {
				if err = mail.SendPaymentConfirmation(user, period, bill); err != nil {
					logger.Error(fmt.Sprintf("error: billing e-mail has not been sent for bill_id=%d user_id=%d user_name='%s' user_email:'%s'", bill.ID, user.ID, user.Firstname+" "+user.Lastname, user.Email))
				} else {
					logger.Info(fmt.Sprintf("payment confirmation has been sent for bill_id=%d user_id=%d user_name='%s' user_email:'%s'", bill.ID, user.ID, user.Firstname+" "+user.Lastname, user.Email))

					err = database.UpdateBillOnPaymentConfirmation(bill.ID)
					if err != nil {
						return fmt.Errorf("error: cannot update bill: bill_id=%d: %v", bill.ID, err)
					}
				}
			}
		}

		return nil
	},
}
View Source
var BillingBillsIssue = cli.Command{
	Name:      "issue",
	Aliases:   []string{"i"},
	Usage:     "Issue all bills for giver billing period",
	ArgsUsage: "[period_id]",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		if err = helpers.SetupMail(ctx); err != nil {
			return err
		}

		if ctx.NArg() != 1 {
			return fmt.Errorf("error: too few arguments: requires (1), get (%d)", ctx.NArg())
		}

		pid, err := strconv.ParseUint(ctx.Args().Get(0), 10, 32)
		if err != nil {
			return fmt.Errorf("error: cannot parse uint: %v", err)
		}

		period, err := database.SelectPeriodByID(uint(pid))
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fmt.Errorf("error: billing period not found")
		} else if err != nil {
			return fmt.Errorf("error: cannot get billing period: %v", err)
		}

		if !period.Closed {
			return fmt.Errorf("error: cannot issue bills: period is not closed")
		}

		bills, err := database.SelectAllBillsForPeriod(uint(pid))
		if err != nil {
			return fmt.Errorf("error: cannot get bills for given period: %v", err)
		}

		for _, bill := range bills {
			user, err := database.SelectUserByID(bill.UserID)
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return fmt.Errorf("error: user for giver bill is not found: user_id=%d", bill.UserID)
			} else if err != nil {
				return fmt.Errorf("error: cannot get user: user_id=%d: %v", bill.UserID, err)
			}

			if err = mail.SendBill(user, period, bill, len(bills)); err != nil {
				logger.Error(fmt.Sprintf("error: billing e-mail has not been sent for bill_id=%d user_id=%d user_name='%s' user_email:'%s'", bill.ID, user.ID, user.Firstname+" "+user.Lastname, user.Email))
			} else {
				logger.Info(fmt.Sprintf("billing e-mail has been sent for bill_id=%d user_id=%d user_name='%s' user_email:'%s'", bill.ID, user.ID, user.Firstname+" "+user.Lastname, user.Email))

				err = database.UpdateBillOnIssued(bill.ID)
				if err != nil {
					return fmt.Errorf("error: cannot update bill: bill_id=%d: %v", bill.ID, err)
				}
			}
		}

		return nil
	},
}
View Source
var BillingBillsPay = cli.Command{
	Name:      "pay",
	Aliases:   []string{"p"},
	Usage:     "Pay (add payment tag) bills for giver billing period",
	ArgsUsage: "[bill_id]",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		if ctx.NArg() != 1 {
			return fmt.Errorf("error: too few arguments: requires (1), get (%d)", ctx.NArg())
		}

		bid, err := strconv.ParseUint(ctx.Args().Get(0), 10, 32)
		if err != nil {
			return fmt.Errorf("error: cannot parse uint: %v", err)
		}

		err = database.UpdateBillOnPaid(uint(bid))
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fmt.Errorf("error: bill not found: bill_id=%d", bid)
		} else if err != nil {
			return fmt.Errorf("error: cannot update bill_id=%d: %v", bid, err)
		}

		logger.Info(fmt.Sprintf("bill successfully marked as paid: bill_id=%d", bid))

		return nil
	},
}
View Source
var BillingBillsPayCSV = cli.Command{
	Name:      "pay-csv",
	Usage:     "Bulk pay (add payment tag) by CSV with appropriate variable symbol",
	ArgsUsage: "[csv]",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		var fr *os.File

		if fr, err = os.Open(ctx.Args().Get(0)); err != nil {
			fmt.Fprintf(os.Stderr, "cannot read file")
			os.Exit(1)
		}

		reader := bufio.NewReader(fr)
		defer func() {
			fr.Close()
		}()

		csv := [][]string{}
		for {
			line, _, err := reader.ReadLine()
			if err != nil {
				break
			}
			csv = append(csv, strings.Split(string(line), ";"))
		}

		csv = csv[1:]
		for i, line := range csv {
			unbid := strings.ReplaceAll(line[12], "\"", "")
			if len(unbid) == 0 {
				logger.Info(fmt.Sprintf("missing variable symbol, skipping: line=%d", i))
				continue
			}

			bid, err := strconv.ParseUint(unbid, 10, 64)
			if err != nil {
				return fmt.Errorf("error: cannot parse uint: %v", err)
			}
			bill, err := database.SelectBillByID(uint(bid))
			if errors.Is(err, gorm.ErrRecordNotFound) {
				logger.Error(fmt.Sprintf("error: bill not found: bill_id=%d", bid))
			} else if err != nil {
				return fmt.Errorf("error: get bill with bill_id=%d: %v", bid, err)
			}

			if !bill.Paid {
				logger.Info(fmt.Sprintf("bill is not paid: bill_id=%d", bid))
			}
		}

		return nil
	},
}
View Source
var BillingBillsUnpaidNotification = cli.Command{
	Name:      "notify",
	Aliases:   []string{"n"},
	Usage:     "Send notification e-mail for unpaid debt for all unpaid bills for given period",
	ArgsUsage: "[period_id]",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		if err = helpers.SetupMail(ctx); err != nil {
			return err
		}

		if ctx.NArg() != 1 {
			return fmt.Errorf("error: too few arguments: requires (1), get (%d)", ctx.NArg())
		}

		pid, err := strconv.ParseUint(ctx.Args().Get(0), 10, 32)
		if err != nil {
			return fmt.Errorf("error: cannot parse uint: %v", err)
		}

		period, err := database.SelectPeriodByID(uint(pid))
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fmt.Errorf("error: billing period not found")
		} else if err != nil {
			return fmt.Errorf("error: cannot get billing period: %v", err)
		}

		if !period.Closed {
			return fmt.Errorf("error: cannot send notification: period is not closed")
		}

		bills, err := database.SelectAllBillsForPeriod(uint(pid))
		if err != nil {
			return fmt.Errorf("error: cannot get bills for given period: %v", err)
		}

		for _, bill := range bills {
			user, err := database.SelectUserByID(bill.UserID)
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return fmt.Errorf("error: user for giver bill is not found: user_id=%d", bill.UserID)
			} else if err != nil {
				return fmt.Errorf("error: cannot get user: user_id=%d: %v", bill.UserID, err)
			}

			if !bill.Paid {
				if err = mail.SendUnpaidNotification(user, period, bill); err != nil {
					logger.Error(fmt.Sprintf("error: notification e-mail has not been sent for bill_id=%d user_id=%d user_name='%s' user_email:'%s'", bill.ID, user.ID, user.Firstname+" "+user.Lastname, user.Email))
				} else {
					logger.Info(fmt.Sprintf("debt notification has been sent for bill_id=%d user_id=%d user_name='%s' user_email:'%s'", bill.ID, user.ID, user.Firstname+" "+user.Lastname, user.Email))
				}
			}
		}

		return nil
	},
}
View Source
var BillingPeriods = cli.Command{
	Name:    "periods",
	Aliases: []string{"p"},
	Usage:   "Billing periods management (create, close, summary)",
	Subcommands: []*cli.Command{
		&BillingPeriodsNew,
		&BillingPeriodsClose,
		&BillingPeriodsSummary,
	},
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		periods, err := database.SelectAllPeriods()
		if err != nil {
			return fmt.Errorf("error: cannot get billing periods: %v", err.Error())
		}

		t := table.NewWriter()
		t.AppendHeader(table.Row{"ID", "From", "To", "UnitPrice", "Total Amount", "Total Quantity", "Avg. Package Price", "Cash"})
		for _, period := range periods {
			t.AppendRow(table.Row{period.ID, period.DateFrom.Format("2006-01-02"), period.DateTo.Format("2006-01-02"), fmt.Sprintf("%.2f", period.UnitPrice), fmt.Sprintf("%.2f", period.TotalAmount), fmt.Sprintf("%d", period.TotalQuantity), fmt.Sprintf("%.2f", period.AmountPerPackage), fmt.Sprintf("%.2f", period.Cash)})
		}
		fmt.Println(t.Render())

		return nil
	},
}
View Source
var BillingPeriodsClose = cli.Command{
	Name:      "close",
	Aliases:   []string{"c"},
	Usage:     "Close billing period and calculate remaining values such as total quantity or unit price",
	ArgsUsage: "[period_id]",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		if ctx.NArg() != 1 {
			return fmt.Errorf("error: too few arguments: requires (1), get (%d)", ctx.NArg())
		}

		pid, err := strconv.ParseUint(ctx.Args().Get(0), 10, 32)
		if err != nil {
			return fmt.Errorf("error: cannot parse uint: %v", err)
		}

		period, err := database.SelectPeriodByID(uint(pid))
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fmt.Errorf("error: billing period not found")
		} else if err != nil {
			return fmt.Errorf("error: cannot get billing period: %v", err)
		}

		bills, err := database.SelectAllBillsForPeriod(uint(pid))
		if err != nil {
			return fmt.Errorf("error: cannot get bills for given period: %v", err)
		}

		totalQuantity := 0
		for _, bill := range bills {
			totalQuantity += bill.Quantity
		}

		unitPrice := (period.TotalAmount - period.Cash) / float32(totalQuantity)

		err = database.UpdatePeriodOnClose(uint(pid), totalQuantity, unitPrice)
		if err != nil {
			return fmt.Errorf("error: cannot create new billing period: %v", err.Error())
		}
		logger.Info(fmt.Sprintf("billing period updated and closed successfully: period_id=%d, total_quantity=%d, unit_price=%.2f", pid, totalQuantity, unitPrice))

		for _, bill := range bills {
			amount := float32(bill.Quantity) * unitPrice
			payment := amount

			user, err := database.SelectUserByID(bill.UserID)
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return fmt.Errorf("error: user for giver bill is not found: user_id=%d", bill.UserID)
			} else if err != nil {
				return fmt.Errorf("error: cannot get user: user_id=%d: %v", bill.UserID, err)
			}

			if user.Credit != 0 {
				var ballance float32
				if float32(user.Credit) <= amount {
					payment = amount - float32(user.Credit)
					ballance = amount - payment
				} else {
					payment = 0
					ballance = amount
				}

				err = database.WithdrawMoney(bill.UserID, ballance)
				if err != nil {
					logger.Error(fmt.Sprintf("error: cannot withdraw credit for user: bill_id=%d user_id=%d ballance=%.2f: %v", bill.ID, bill.UserID, ballance, err.Error()))
				} else {
					logger.Info(fmt.Sprintf("credit has been reduced for user: bill_id=%d user_id=%d reduction=%.2f", bill.ID, bill.UserID, ballance))
				}

				_, err = database.InsertTransaction(models.Transaction{Type: models.WITHDRAW, Amount: ballance, UserID: bill.UserID})
				if err != nil {
					logger.Error(fmt.Sprintf("error: cannot insert withdraw transaction for user: bill_id=%d user_id=%d ballance=%.2f: %v", bill.ID, bill.UserID, ballance, err.Error()))
				}
			}

			err = database.UpdateBillOnPeriodClose(bill.ID, amount, payment)
			if err != nil {
				logger.Error(fmt.Sprintf("error: cannot update bill with bill_id=%d: %v", bill.ID, err.Error()))
			} else {
				logger.Info(fmt.Sprintf("bill has been updated successfully: bill_id=%d amount=%.2f", bill.ID, amount))
			}
		}

		return nil
	},
}
View Source
var BillingPeriodsNew = cli.Command{
	Name:      "new",
	Aliases:   []string{"n"},
	Usage:     "Add new billing period",
	ArgsUsage: "[from(2006-01-02) to(2006-01-02) issued(2006-01-02) total_amount amount_per_package cash]",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		if ctx.NArg() != 6 {
			return fmt.Errorf("error: too few arguments: requires (6), get (%d)", ctx.NArg())
		}

		DateFrom, err := time.Parse("2006-01-02", ctx.Args().Get(0))
		if err != nil {
			return fmt.Errorf("error: cannot parse date: %v", err)
		}

		DateTo, err := time.Parse("2006-01-02", ctx.Args().Get(1))
		if err != nil {
			return fmt.Errorf("error: cannot parse date: %v", err)
		}

		DateOfIssue, err := time.Parse("2006-01-02", ctx.Args().Get(2))
		if err != nil {
			return fmt.Errorf("error: cannot parse date: %v", err)
		}

		TotalAmount, err := strconv.ParseFloat(ctx.Args().Get(3), 32)
		if err != nil {
			return fmt.Errorf("error: cannot parse float: %v", err)
		}

		AmountPerPackage, err := strconv.ParseFloat(ctx.Args().Get(4), 32)
		if err != nil {
			return fmt.Errorf("error: cannot parse float: %v", err)
		}

		Cash, err := strconv.ParseFloat(ctx.Args().Get(5), 32)
		if err != nil {
			return fmt.Errorf("error: cannot parse float: %v", err)
		}

		totalMonths := 1
		if int(DateTo.Sub(DateFrom).Hours()/24/30) > 0 {
			totalMonths = int(DateTo.Sub(DateFrom).Hours() / 24 / 30)
		}

		period := models.Period{
			DateFrom:         DateFrom,
			DateTo:           DateTo,
			DateOfIssue:      DateOfIssue,
			TotalMonths:      totalMonths,
			TotalAmount:      float32(TotalAmount),
			AmountPerPackage: float32(AmountPerPackage),
			Cash:             float32(Cash),
		}

		id, err := database.InsertPeriod(period)
		if err != nil {
			return fmt.Errorf("error: cannot create new billing period: %v", err.Error())
		}
		logger.Info(fmt.Sprintf("new billing period successfully created: new period id: %d", id))

		return nil
	},
}
View Source
var BillingPeriodsSummary = cli.Command{
	Name:      "summary",
	Aliases:   []string{"s"},
	Usage:     "Get numbers and statistics for given billing period",
	ArgsUsage: "[period_id]",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		if ctx.NArg() != 1 {
			return fmt.Errorf("error: too few arguments: requires (1), get (%d)", ctx.NArg())
		}

		pid, err := strconv.ParseUint(ctx.Args().Get(0), 10, 32)
		if err != nil {
			return fmt.Errorf("error: cannot parse uint: %v", err)
		}

		period, err := database.SelectPeriodByID(uint(pid))
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fmt.Errorf("error: billing period not found")
		} else if err != nil {
			return fmt.Errorf("error: cannot get billing period: %v", err)
		}

		bills, err := database.SelectAllBillsForPeriod(uint(pid))
		if err != nil {
			return fmt.Errorf("error: cannot get bills for given period: %v", err)
		}

		fmt.Printf("ID:                %d\n", period.ID)
		fmt.Printf("Period:            %s - %s\n", period.DateFrom.Format("2006-01-02"), period.DateTo.Format("2006-01-02"))
		fmt.Printf("Unit price:        %.2f\n", period.UnitPrice)
		fmt.Printf("Total quantity:    %d\n", period.TotalQuantity)
		fmt.Printf("Total amount:      %.2f\n", period.TotalAmount)
		fmt.Printf("Total amount (wc): %.2f (total - cash)\n", period.TotalAmount-period.Cash)
		fmt.Printf("Total months:      %d\n", period.TotalMonths)
		fmt.Printf("Cash:              %.2f\n", period.Cash)
		fmt.Printf("Closed:            %t\n", period.Closed)
		fmt.Printf("Total bills:       %d\n", len(bills))

		return nil
	},
}
View Source
var Database = cli.Command{
	Name:    "database",
	Aliases: []string{"d"},
	Usage:   "Database initialization and checks",
	Subcommands: []*cli.Command{
		&DatabaseInitialize,
		&DatabaseMigrate,
	},
}
View Source
var DatabaseCheck = cli.Command{
	Name:  "check",
	Usage: "Check SQLite database",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		schema, err := database.SelectVersion()
		if err != nil {
			return fmt.Errorf("cannot get schema from database for version check: %v", err)
		}

		if schema.Version != models.VERSION {
			return fmt.Errorf("check failed: version miss match")
		}

		return nil
	},
}
View Source
var DatabaseInitialize = cli.Command{
	Name:  "initialize",
	Usage: "Initialize SQLite database",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		database.RunAutoMigration()

		_, err = database.InsertSchema(models.Schema{Version: models.VERSION})
		if err != nil {
			return fmt.Errorf("cannot update schema version for database: %v", err)
		}

		return nil
	},
}
View Source
var DatabaseMigrate = cli.Command{
	Name:  "migrate",
	Usage: "Migrate SQLite database",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		database.RunAutoMigration()

		schema, err := database.SelectVersion()
		if err != nil {
			return fmt.Errorf("cannot get schema from database for version check: %v", err)
		}

		if schema.Version != models.VERSION {
			err = database.UpdateVersion(models.VERSION)
			if err != nil {
				return fmt.Errorf("cannot update schema version for database: %v", err)
			}
		}

		return nil
	},
}
View Source
var Users = cli.Command{
	Name:    "users",
	Aliases: []string{"u"},
	Usage:   "User management operations",
	Flags: []cli.Flag{
		&flags.FlagPlainPrint,
	},
	Subcommands: []*cli.Command{
		&UsersAdd,
		&UsersAddBulk,
		&UsersContacts,
	},
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		users, err := database.SelectAllUsers()
		if err != nil {
			return fmt.Errorf("error: cannot get users: %v", err.Error())
		}

		if !ctx.Bool("plain") {
			t := table.NewWriter()
			t.AppendHeader(table.Row{"ID", "Name", "E-mail", "Location"})
			for _, user := range users {
				t.AppendRow(table.Row{user.ID, user.Firstname + " " + user.Lastname, user.Email, user.Location})
			}
			fmt.Println(t.Render())
		} else {
			for _, user := range users {
				fmt.Printf("%d %s %s, %s, %s\n", user.ID, user.Firstname, user.Lastname, user.Email, user.Location)
			}
		}

		return nil
	},
}
View Source
var UsersAdd = cli.Command{
	Name:      "add",
	Usage:     "Add new user",
	ArgsUsage: "[employee_id firstname lastname e-mail location]",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		if ctx.NArg() != 5 {
			return fmt.Errorf("error: too few arguments: requires (5), get (%d)", ctx.NArg())
		}

		user := models.User{
			EID:       ctx.Args().Get(0),
			Firstname: ctx.Args().Get(1),
			Lastname:  ctx.Args().Get(2),
			Email:     ctx.Args().Get(3),
			Location:  ctx.Args().Get(4),
		}

		if _, err = database.SelectUserByEID(user.EID); err != nil {
			id, err := database.InsertUser(user)
			if err != nil {
				return fmt.Errorf("error: cannot create user: %v", err.Error())
			}
			logger.Info(fmt.Sprintf("user successfully created: new user id: %d", id))
		} else {
			logger.Info("user already exists")
		}

		return nil
	},
}
View Source
var UsersAddBulk = cli.Command{
	Name:      "add-bulk",
	Usage:     "Add multiple users at once by csv file (eid,\"lastname firstname\",e-mail,location,...)",
	ArgsUsage: "[file.csv]",
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		if ctx.NArg() != 1 {
			fmt.Println("Too few arguments")
		}

		fmt.Println(ctx.Args().Get(0))

		const (
			CSV_EID = iota
			CSV_NAME
			CSV_EMAIL
			CSV_LOCATION
			CSV_COFFEES
		)

		var fd *os.File
		if fd, err = os.Open(ctx.Args().Get(0)); err != nil {
			return err
		}

		ioReader := bufio.NewReader(fd)
		reader := csv.NewReader(ioReader)

		for {
			var record []string
			if record, err = reader.Read(); err == io.EOF {
				break
			} else if err != nil {
				return err
			}

			user := models.User{
				EID:       record[CSV_EID],
				Firstname: strings.Fields(record[CSV_NAME])[1],
				Lastname:  strings.Fields(record[CSV_NAME])[0],
				Email:     record[CSV_EMAIL],
				Location:  record[CSV_LOCATION],
			}

			if _, err = database.SelectUserByEID(user.EID); err != nil {
				id, err := database.InsertUser(user)
				if err != nil {
					return fmt.Errorf("error: cannot create user: %v", err.Error())
				}
				logger.Info(fmt.Sprintf("user successfully created: new user id: %d", id))
			} else {
				logger.Info("user already exists")
			}
		}

		return nil
	},
}
View Source
var UsersContacts = cli.Command{
	Name:    "contacts",
	Aliases: []string{"c"},
	Usage:   "Get e-mail contacts of all customers",
	Flags: []cli.Flag{
		&flags.FlagPlainPrint,
	},
	Action: func(ctx *cli.Context) (err error) {
		if err = helpers.SetupDatabase(ctx); err != nil {
			return err
		}

		if ctx.NArg() != 0 {
			return fmt.Errorf("error: too few arguments: requires (1), get (%d)", ctx.NArg())
		}

		bills, err := database.SelectAllBills()
		if err != nil {
			return fmt.Errorf("error: cannot get billing period: %v", err)
		}

		var contacts []string
		for _, bill := range bills {
			user, err := database.SelectUserByID(bill.UserID)
			if err != nil {
				logger.Error(fmt.Sprintf("error: cannot get user by id: user_id=%d: %v", bill.UserID, err.Error()))
			}

			if !slices.Contains(contacts, user.Email) {
				contacts = append(contacts, user.Email)
			}
		}

		if !ctx.Bool("plain") {
			t := table.NewWriter()
			t.AppendHeader(table.Row{"E-mail"})
			for _, contact := range contacts {
				t.AppendRow(table.Row{contact})
			}
			fmt.Println(t.Render())
		} else {
			for _, contact := range contacts {
				fmt.Println(contact)
			}
		}

		return nil
	},
}

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL