fix: resolve go vet error and dayStart logic in ComputeDailyTotals

- Fix 'string(d)' int-to-rune conversion in rpg_test.go by using fmt.Sprintf
- Fix ComputeDailyTotals dayStart logic: initial state should be StateWorking
  when dayStart > 0, correctly counting work from dayStart to first out event
- Update README project structure to reflect new domain/handler/repo layout
- Implement missing achievements: rpg_pioneer (on RPG enable) and
  salary_setter (on first rate set)
- Add tryUnlockAchievement helper to Handler
This commit is contained in:
2026-06-25 09:54:22 +03:30
parent 3835b002a7
commit d15ed46066
45 changed files with 4947 additions and 5430 deletions

51
internal/domain/salary.go Normal file
View File

@@ -0,0 +1,51 @@
package domain
import (
"fmt"
"math"
"strings"
"unicode"
)
// ComputeDailySalary calculates salary for a single day's work.
func ComputeDailySalary(workSeconds int64, hourlyRate float64) float64 {
hours := float64(workSeconds) / SecondsPerHour
return math.Round(hours*hourlyRate*SalaryRoundFactor) / SalaryRoundFactor
}
// CurrencySymbol extracts the first token as symbol, defaulting to "$".
func CurrencySymbol(currency string) string {
if parts := strings.Fields(currency); len(parts) >= 1 && parts[0] != "" {
return parts[0]
}
return "$"
}
// FormatSalary formats a salary estimate into a readable string.
func (e SalaryEstimate) FormatSalary(currency string) string {
sym := CurrencySymbol(currency)
code := "USD"
if parts := strings.Fields(currency); len(parts) >= 2 {
code = parts[1]
}
return fmt.Sprintf("%s%.2f %s", sym, e.GrossEarning, code)
}
// FormatSalaryShort returns just the symbol + amount.
func (e SalaryEstimate) FormatSalaryShort(currency string) string {
return fmt.Sprintf("%s%.2f", CurrencySymbol(currency), e.GrossEarning)
}
// ParseCurrencyInput validates and normalizes currency input (symbol + code).
func ParseCurrencyInput(s string) (string, string) {
parts := strings.Fields(s)
if len(parts) < 2 {
return "", "Please provide both symbol and code.\nUsage: /setcurrency <symbol> <code>\nExamples:\n/setcurrency $ USD\n/setcurrency T Toman\n/setcurrency IRR Rial"
}
for _, p := range parts[:2] {
if len(p) > 10 || strings.ContainsFunc(p, unicode.IsControl) {
return "", "Invalid currency. Use short printable strings like $ USD, T Toman, IRR Rial."
}
}
return parts[0] + " " + parts[1], ""
}