package orders import ( "context" "errors" "fmt" "log" "os" "strings" "sync" "time" "google.golang.org/api/option" "google.golang.org/api/sheets/v4" ) const ( tab = "Zamówienia" statusPending = "oczekuje" statusConfirmed = "potwierdzone" ) var ( sheetsSvc *sheets.Service SheetID string rowMu sync.Mutex ) func init() { SheetID = os.Getenv("SHEET_ID") credsFile := os.Getenv("SHEETS_CREDS") var err error sheetsSvc, err = sheets.NewService(context.Background(), option.WithCredentialsFile(credsFile)) if err != nil { log.Fatal(err) } log.Println("init google sheets") } func sctx() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), 10*time.Second) } func cell(r []any, i int) string { if i < len(r) && r[i] != nil { return strings.TrimSpace(fmt.Sprint(r[i])) } return "" } func get(ctx context.Context, a1 string) ([][]any, error) { res, err := sheetsSvc.Spreadsheets.Values.Get(SheetID, a1).Context(ctx).Do() if err != nil { return nil, err } return res.Values, nil } func clearRow(ctx context.Context, row int) error { _, err := sheetsSvc.Spreadsheets.Values.BatchClear(SheetID, &sheets.BatchClearValuesRequest{ Ranges: []string{ fmt.Sprintf("%s!B%d:D%d", tab, row, row), fmt.Sprintf("%s!G%d", tab, row), fmt.Sprintf("%s!I%d:T%d", tab, row, row), }, }).Context(ctx).Do() return err } func insertPending(o order) (string, error) { ctx, cancel := sctx() defer cancel() rowMu.Lock() defer rowMu.Unlock() vals, err := get(ctx, tab+"!B:C") if err != nil { return "", err } row := max(len(vals)+1, 4) for i := 3; i < len(vals); i++ { if cell(vals[i], 0) == "" && cell(vals[i], 1) == "" { row = i + 1 break } } data := []*sheets.ValueRange{{ Range: fmt.Sprintf("%s!B%d:D%d", tab, row, row), Values: [][]any{{o.phone, o.name, statusPending}}, }} for _, it := range o.items { if it.count > 0 { data = append(data, &sheets.ValueRange{ Range: fmt.Sprintf("%s!%s%d", tab, it.id, row), Values: [][]any{{float64(it.count) / 10}}, }) } } _, err = sheetsSvc.Spreadsheets.Values.BatchUpdate(SheetID, &sheets.BatchUpdateValuesRequest{ ValueInputOption: "RAW", Data: data, }).Context(ctx).Do() if err != nil { for _, datum := range data { log.Println(datum) } return "", err } idVals, err := get(ctx, fmt.Sprintf("%s!A%d", tab, row)) if err != nil || len(idVals) == 0 || cell(idVals[0], 0) == "" { clearRow(ctx, row) return "", errors.New("row has no id") } return cell(idVals[0], 0), nil } func findPendingRow(ctx context.Context, id string) (int, error) { ids, err := get(ctx, tab+"!A:A") if err != nil { return 0, err } for i, r := range ids { if i == 0 || cell(r, 0) != id { continue } row := i + 1 v, err := get(ctx, fmt.Sprintf("%s!D%d", tab, row)) if err != nil { return 0, err } if len(v) > 0 && cell(v[0], 0) == statusPending { return row, nil } return 0, errors.New("row is not pending") } return 0, errors.New("no row with this id") } func confirmRow(id string) error { ctx, cancel := sctx() defer cancel() row, err := findPendingRow(ctx, id) if err != nil { return err } _, err = sheetsSvc.Spreadsheets.Values.BatchUpdate(SheetID, &sheets.BatchUpdateValuesRequest{ ValueInputOption: "RAW", Data: []*sheets.ValueRange{ {Range: fmt.Sprintf("%s!D%d", tab, row), Values: [][]any{{statusConfirmed}}}, {Range: fmt.Sprintf("%s!G%d", tab, row), Values: [][]any{{float64(retainer) / 100}}}, }, }).Context(ctx).Do() return err } func clearOrder(id string) error { ctx, cancel := sctx() defer cancel() row, err := findPendingRow(ctx, id) if err != nil { return err } return clearRow(ctx, row) }