LINUXTALKS.CO

Сколько стоит разработка телеграмм бота с функциями резервирования товара и интеграцией с гугл-таблицами?

 , , , ,

L


0

1

Спрашиваю что бы было понятно на какие цифры потом опираться в переговорах.

Бот общается с клиентами в телеграмм. Показывает им каталог, фотографии товаров, даёт ссылки на яндекс-диск с файлами их спецификаций. Но главное, отслеживает остатки и выполняет операции резервирования и снятия с резерва в пределах аванса. При этом если цена товара меняется, то снятие с резерва каждой единицы товара происходит по той цене по которой она была зарезервирована, то есть функция работает через журнал резервирования.

В качестве базы данных используются гугл-таблицы, держа часть логики в их формулах. Что так же позволяет легко формировать отчёты.

Автор в курсе про основные минусы использования гугл-таблиц и поэтому в программе активно используется кэширование, что бы каждый раз не перебирать полностью журнал или балансный столбец конкретного клиента.

В какую цену вы ориентировочно оценили бы разработку такого telegram-бота?

★★★★★★
Ответ на: комментарий от sorrow

В качестве базы данных используются гугл-таблицы

Пожелание заказчика.

TableService, err = createGoogleSheetsService(credentialsGoogleFile)
	if err != nil {
		log.Fatalf("Не удалось подключиться к Google Sheets. ", err)
		log.Panic(err)
	} else {
		GoogleTableOnline = true // Устновка флага
		log.Printf("Подключены google-таблицы")
	}
func createGoogleSheetsService(credentialsGoogleFile string) (*sheets.Service, error) {
	// Эта функция создаёт объект для доступа к гугл-таблице
	ctx := context.Background()

	srv, err := sheets.NewService(ctx, option.WithCredentialsFile(credentialsGoogleFile))
	if err != nil {
		return nil, err
	}

	return srv, nil
}
func getSpreadsheetData(srv *sheets.Service, spreadsheetID, sheetName, rangeName string) ([][]interface{}, error) {
	// Функция запрашивает диапазон данных из таблицы и возвращает в виде среза срезов.
	ctx := context.Background()

	resp, err := srv.Spreadsheets.Values.Get(spreadsheetID, sheetName+"!"+rangeName).Context(ctx).Do()
	if err != nil {
		return nil, err
	}

	return resp.Values, nil
}

func recalculateGoogleSpreadsheet(srv *sheets.Service, spreadsheetID string) error {
	// Функция запускает пересчёт всей google-таблицы. Это может занимать много времени.
	ctx := context.Background()
	recalcRequest := &sheets.BatchUpdateValuesRequest{
		ValueInputOption: "USER_ENTERED",
	}
	_, err := srv.Spreadsheets.Values.BatchUpdate(spreadsheetID, recalcRequest).Context(ctx).Do()
	if err != nil {
		return err
	}
	return nil
}

// Функция для записи значения в ячейку Google Sheets
func writeValueToGoogleSheet(srv *sheets.Service, spreadsheetID, SheetName, CellName, value string) error {
	// Создаем объект ValueRange для хранения данных
	data := sheets.ValueRange{
		Values: [][]interface{}{{value}},
	}
	// Определяем valueInputOption в зависимости от начала значения с символа "="
	valueInputOption := "RAW"
	if strings.HasPrefix(value, "=") {
		valueInputOption = "USER_ENTERED"
	}
	_, err := srv.Spreadsheets.Values.Update(spreadsheetID, SheetName+"!"+CellName, &data).ValueInputOption(valueInputOption).Context(context.Background()).Do()
	if err != nil {
		return err
	}
	return nil
}

// Прочие функции

func interfaceSliceToStringSlice(slice [][]interface{}) [][]string {
	// Эта функция преобразует срез в строковый. Сгенерирована ChatGPT
	result := make([][]string, len(slice))
	for i, row := range slice {
		result[i] = make([]string, len(row))
		for j, val := range row {
			if strVal, ok := val.(string); ok {
				result[i][j] = strVal
			} else {
				// Handle non-string values here. You can convert them to strings or handle the error as needed.
				// For example, you can use fmt.Sprintf to convert them to strings:
				result[i][j] = fmt.Sprintf("%v", val)
			}
		}
	}
	return result
}
func interfaceToFloat64Slice(input [][]interface{}) [][]float64 {
	// Эта функция преобразует срез в числа с плавающей запятой. Сгенерирована ChatGPT
	result := make([][]float64, len(input))
	for i, row := range input {
		result[i] = make([]float64, len(row))
		for j, v := range row {
			switch val := v.(type) {
			case float64:
				result[i][j] = val
			case int:
				result[i][j] = float64(val)
			case int64: // Эту секцию добавил сам
				result[i][j] = float64(val)
			case int32: // Эту секцию добавил сам
				result[i][j] = float64(val)
			default:
				result[i][j] = 0.0
			}
		}
	}
	return result
}
rezedent12    
★★★★★★
Windows / Firefox

У гугл таблиц вроде ж ограничения на 60 запросов в секунду в бесплатном тарифе. Клиентов мало? Если много, могут возникнуть проблемы. Может все-таки лучше нормальную БД, а в гугл таблицы, если это необходимо, выгружать периодически?

Qwentor    
★★★★★★★
Последнее исправление: Qwentor (всего исправлений: 2)

Windows / Firefox
Ответ на: комментарий от Qwentor

У гугл таблиц вроде ж ограничения на 60 запросов в секунду в бесплатном тарифе.

Разве этого не хватит чтобы раз в 1 секунду оформлять одного человека?

Клиентов мало?

3600 клиентов каждый час разве мало?
Это автотрейлерами торговать можно.

Скорее всего он хочет сделать магазин для своего колхоза, а это скажем 80 видов разгого мяса из свиней и куриц, яйца, молоко, сливки, сыр уже навряд ли.
Если учесть что цены на товар целесообразнее хранить в отдельном лежащем на сервере json’чике то на гугло таблицы ложится только регистрация заказа.
Так вот, какова вероятность что в течении 10 секунд будут оформлять покупку 2 и более человек?

torvn77    
★★★
Последнее исправление: torvn77 (всего исправлений: 1)

Android / Chrome
Ответ на: комментарий от deep-purple

Бот – это лишь интерфейс взаимодействия – фронтэнд.
У меня не столь чёткое разделение.

А бекенд у тебя уже готов? Его API поддерживает всё необходимое?

Когда происходит зачисление средств на баланс, то бот находит в гугл таблице столбец (максимум 26^3 клиентов), находит самую нижнюю заполненную строку в нём и на следующей пустой пишет величину изменения счёта. Затем совершает вызов для пересчёта таблицы. Вверху каждого столбца есть ячейка суммирующая весь столбец под ней. Из неё считывается текущий баланс.

Половина бэкенда в формулах в гугл-таблице.

rezedent12    
★★★★★★
Linux / Chrome
Ответ на: комментарий от rezedent12

А гугло таблицы у тебя будут на отдельном аккаунте, их не заблочат из-за неосторожных видео или комментариев в Ютубе.

И ещё, купи ключ для u2f аутентификации, если утратишь номер телефона или гугл заблокируют то хоть как то сможешь восстановить доступ к своим безнес документам.

torvn77    
★★★
Последнее исправление: torvn77 (всего исправлений: 1)

Android / Chrome
Ответ на: комментарий от Qwentor

У гугл таблиц вроде ж ограничения на 60 запросов в секунду в бесплатном тарифе.

Спасибо за конкретные значения, сделаю ограничитель что бы случайно не превысить.

Клиентов мало?

Клиентов пока нет.

Если много, могут возникнуть проблемы.

Если клиентов будет хотя бы тысяча и каждый из них сделает 3 продажи в день, то средства для привлечения сторонних разработчиков будут.

если это необходимо, выгружать периодически?

Для самых тяжёлых запросов используется кэширование. Для балансных столбцов кэшируются последние номера строк, что бы столбец каждый раз полностью не просматривать. Для журнала так же будет кэшироватся номер последней строки. Кэшируются литеры столбцов, что бы каждый раз не искать столбец нужного пользователя.

Ассортимент товаров будет кэшироватся и обновляться в боте не чаще чем раз в 10 минут или при резервировании.

Самой ресурсоёмкой в обращении к гугл-таблице является операция снятия с резерва. Потому что для неё нужно просматривать журнал и искать в нём когда и по какой цене сколько единиц было зарезервировано. Но думаю просто тупо закэшировать весь лист в map, всё равно будет быстрее.

По хорошему конечно надо большую часть запросов из таблицы переместить в отдельные специальные функции. Так будет легче потом более производительную базу данных подключить.

rezedent12    
★★★★★★
Linux / Chrome
Ответ на: комментарий от torvn77

А гугло таблицы у тебя будут на отдельном аккаунте, их не заблочат из-за неосторожных видео или комментариев в Ютубе.

Я не пишу комментарии на ютубе с рабочей учётной записи.

И ещё, купи ключ для u2f аутентификации, если утратишь номер телефона или гугл заблокируют то хоть как то сможешь восстановить доступ к своим безнес документам.

Наверно сделаю функцию регулярной выгрузки таблицы с формулами в TCV файл и сохраню копию шаблона таблицы в личной учётной записи.

А вообще, почему ты хочешь именно гуглотаблицу, чем тебе локальная БД на старом компе не подходит?

Наши предполагаемые клиенты, это малые B2B. Схема приблизительно такая. Клиент вносит деньги на баланс и резервирует товар. Сам выставляя его на маркетплейсе указывая наш склад. Когда у него покупают, то он присылает ярлыки которые мы клеим на товар и сдаём товар на пункт приёма маркетплейса. То есть клиент покупает товар у нас, в тот момент когда товар покупают у него.

Для клиента это удобно тем, что он может пробовать продавать разный товар, в любое время изменяя состав своего резерва.

Этим клиентам нужны выгрузки, интеграции, отчёты. А это удобно делать через функцию import в google-таблицах. Да и нашей бухгалтерии тоже будет удобно выгружать данные из гугл-таблицы. (Не беспокойся, публичная и приватная таблица в разных «файлах»).

Мне самому не охото ради изменения формы отчёта изменять код бота и перезапускать его. Кроме того гугл таблицы позволяют наглядно представить базу данных и легко вручную её редактировать если понадобиться. А ещё в них есть история редактирования.

rezedent12    
★★★★★★
Linux / Chrome
Ответ на: комментарий от ashot

Помню бух в екселе.. Как же это было лампово, весело, тупо и неэффективно.

Это было вполне эффективно пока фирма 1С не стала лоббировать постоянное усложнение отчётности. При помощи формул ВПР, ИНДЕКС, ЕСЛИ, ArrayFormula и filter, можно даже CRM реализовать.

rezedent12    
★★★★★★
Linux / Chrome
Ответ на: комментарий от rezedent12

пока фирма 1С не стала лоббировать постоянное усложнение отчётности

Че?

И до 1с было не мало прог. Отечественных и импортных.

Это было вполне эффективно

Из электронной таблицы можно сделать бд, как и из буханки хлеба троллейбус, но зачем?

ashot    
★★★★★★
Android / Chrome
Ответ на: комментарий от ashot

И до 1с было не мало прог. Отечественных и импортных.

Я не писал что их не было. Но они в меньшей степени были нужны.

Из электронной таблицы можно сделать бд, как и из буханки хлеба троллейбус, но зачем?

Электронная таблица - это база данных с хранимыми функциями.

rezedent12    
★★★★★★
Linux / Chrome
Ответ на: комментарий от ashot

Помню бух в екселе

Мммм, наебнувшиеся макросы, написаные хуй знает кем и когда и таскаемые с компа на комп 15 лет…

Из электронной таблицы можно сделать бд

MS Access, foxpro, BDE Administrator, аж запах напалма почудился…

kravzo    
★★★
Windows / Chrome
Ответ на: комментарий от kravzo

Мммм, наебнувшиеся макросы, написаные хуй знает кем и когда и таскаемые с компа на комп 15 лет…

Я использовал макрос только для функции вывода суммы прописью. Всё остальное в основном на ВПР и ArrayFormula делаю.

rezedent12    
★★★★★★
Windows / Firefox
Ответ на: комментарий от rezedent12

Мой первый проект на go, мой первый telegram-бот.

Ты вполне можешь сам тольком не понимать какими свойствами обладает код который ты сам же и написал и по этому лучше сделай так, как тебе советуют люди: раздели фронтенд и бекенд.

torvn77    
★★★
Android / Chrome
Ответ на: комментарий от torvn77

раздели фронтенд и бекенд.

Какой смысл делать API для доступа к API?

Если разделять функционал на «бэк» и «фронт», то будет бесмысленное дублирование кода.

Ещё бы посоветовали на микросервисах сделать…

Всё довольно просто, запрос - ответ. Какой смысл городить промежуточные слои? Сам телеграм таким слоем является.

rezedent12    
★★★★★★
Linux / Chrome
Ответ на: комментарий от deep-purple

Написал я функцию:

func GetGoodRemains(VendorCode string) (float64, error) {
	var result float64
	var ok bool
	Goods, errLoad := GetAllGoodsRemains()
	if errLoad == nil {
		for _, Good := range Goods {
			if Good.VendorCode == VendorCode { // Товар найден
				result = Good.Quantity // Извлекаем колличество
				ok = true
				break
			}
		}
		if ok {
			return result, nil
		} else {
			return 0, fmt.Errorf("товар %v не найден", VendorCode)
		}

	} else { // Не получилось запросить товары
		return 0, errLoad
	}
}

Попросил ChatGPT улучшить её, и получил:

func GetGoodRemains(VendorCode string) (float64, error) {
    Goods, errLoad := GetAllGoodsRemains()
    if errLoad != nil {
        return 0, errLoad
    }

    for _, Good := range Goods {
        if Good.VendorCode == VendorCode {
            return Good.Quantity, nil
        }
    }

    return 0, fmt.Errorf("товар %v не найден", VendorCode)
}

Вижу что функция компактнее. Но что то мне в ней не нравиться. Какая функция написана лучше и почему?

rezedent12    
★★★★★★
Windows / Firefox
Ответ на: комментарий от deep-purple

Гораздо большая загадка, почему количество (в данном случае, оставшихся) товарных единиц представлено типом float64?

Для универсальности, на будущее.

rezedent12    
★★★★★★
Windows / Firefox
Ответ на: комментарий от rezedent12

Подскажите, пожалуйста, а могу я у вас купить 0.8000003470009 пакета сахара? Мне надо именно столько и ни кристалликом меньше!

deep-purple    
★★★★★★★★★★
Последнее исправление: deep-purple (всего исправлений: 1)

Android / Firefox
Ответ на: комментарий от deep-purple

Подскажите, пожалуйста, а могу я у вас купить 0.8000003470009 пакета сахара? Мне надо именно столько и ни кристалликом меньше!

Ты можешь зарезервировать столько сахара на нашем складе. А сколько ты продашь на ozon и в каком виде - другое дело. Когда продашь, и пришлёшь ярлык, мы взвесим, упакуем, наклеим ярлык и доставим на пункт ozon.

rezedent12    
★★★★★★
Windows / Firefox
Ответ на: комментарий от rezedent12

Клиент всегда прав:

Мне чуть больше пол пары ботинок, пожалуйста. Ботинок должен быть левый – я на костылях, второй не нужен. Не хочу переплачивать. А шнурка нужно два – рвуться. Отвесьте мне 0.502 пары ботинок.

А стоимость товара ты тоже во флоате хранишь?

deep-purple    
★★★★★★★★★★
Android / Firefox
Ответ на: комментарий от deep-purple

А стоимость товара ты тоже во флоате хранишь?

YES. Цена наша.

Мне чуть больше пол пары ботинок, пожалуйста. Ботинок должен быть левый – я на костылях, второй не нужен. Не хочу переплачивать. А шнурка нужно два – рвуться. Отвесьте мне 0.502 пары ботинок.

Это решается не кодом программы, а юридически. Соглашение с контр-агентом будет предусматривать пункт про то что мы можем округлить в произвольную сторону дробное количество не дробного товара.

rezedent12    
★★★★★★
Windows / Firefox