Как автоматически менять цены на сайте без загрузки и выгрузки Excel файла с прайсом
Сегодня на связи отдел разработки и поддержки сайтов Зекслер. Продолжаем рассказывать, как автоматизируем бизнес процессы клиентов (писали как автоматизировали создание коммерческих предложений в Битрикс 24), и в этот раз сделали синхронизацию цен из Google-таблицы (дабы не грузить Excel и прочее) и цен на сайте производителя гаражей и хозблоков SKOGGY (да, для многих производственников делаем полный цикл по маркетингу, продажам, автоматизации и управленческому учету).
В статье будут строчки кода и прочая внутрянка, но донесению сути она не должна помешать. А если всё же помешала, пиши в комментариях, учтём в следующих материалах.
Подписывайтесь на наш Телеграм-канал, где мы ежедневно публикуем посты по теме не только разработки, но и дизайна, маркетинга, продвижения и т.д. Будем рады новым подписчикам!
Возвращаемся к модулю. Задача состояла в разработке модуля для CMS Drupal, который синхронизирует данные из гугл документа формата Excel (Google Spreadsheet) в колонке с ценами в поля для цен на сайте.
Подготовка колонки с артикулами в Google таблице
Предварительно необходимо было задать связь записи продукта и таблицы. Для это в файле документа организовали колонку с уникальными полями (Артикулы), отвечающими за связь этой строки (продукта) с вариантом продукта в Drupal.
В нашем случае, это колонка C.
Колонка с ценами – это N.
Начальная строка 5, а конечная 105.
После добавления всех артикулов в таблицу и правильных цен, переходим к следующему этапу.
Создание приложения в Google консоли
Следующий шаг – создание приложения. Переходим в Google Cloud Console и создаем отдельное приложение. Подключаем к нему (ENABLE APIS AND SERVICES) Google Drive API.
Далее нужно получить доступ к приложению. Для этого переходим в credentials и нажимаем Create credentials. После заполнения и добавления тестового пользователя, нужно скачать Client secrets в формате json (справа в строке).
После этого необходимо дать разрешения для доменов Authorized JavaScript origins и Authorized redirect URIs. В нашем случае это localhost и localhost/state.
Сохраненный файл json нужен для получения токена, который будем получать с помощью php-файла в отдельном скрипте в консоле.
Получение токена для ClientApi Google
Теперь нужно организовать получение токена. Для этого создаем в папке микропроекта composer.json и добавим туда google/apiclient посредством команды:
composer require google/apiclient
Делаем файл php с подгрузкой require __DIR__ . '/vendor/autoload.php';
А скачанный файл credentials размещаем в той же папке composer.json
{
"name": "my/google_spreadsheet_api",
"type": "my-api",
"description": "Library for google api",
"require": {
"google/apiclient": "^2.0"
},
"minimum-stability": "dev"
}
Файл index.php:
require __DIR__ .'/vendor/autoload.php';
if (php_sapi_name() != 'cli') {
throw new Exception('This application must be run on the command line.');
}
$pre_dir = __DIR__ . '/';
$relocation_url = 'http://localhost/state';
$path_to_credentials = $pre_dir.'credentials.json';
$path_to_result_token = $pre_dir. 'token.json';
use Google\Client;
/**
* Returns an authorized API client.
* @return Client the authorized client object
*/
function getClient()
{
global $relocation_url, $path_to_credentials, $path_to_result_token;
$client = new Google\Client();
$client->setApplicationName('Google Sheets API PHP Quickstart');
$client->setScopes('https://www.googleapis.com/auth/spreadsheets');
$client->setAuthConfig($path_to_credentials);
$client->setAccessType('offline');
$client->setPrompt('select_account consent');
$client->setRedirectUri($relocation_url);
// Load previously authorized token from a file, if it exists.
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
$tokenPath = $path_to_result_token;
if (file_exists($tokenPath)) {
$accessToken = json_decode(file_get_contents($tokenPath), true);
$client->setAccessToken($accessToken);
}
// If there is no previous token or it's expired.
if ($client->isAccessTokenExpired()) {
// Refresh the token if possible, else fetch a new one.
if ($client->getRefreshToken()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
} else {
// Request authorization from the user.
$authUrl = $client->createAuthUrl();
printf("Open the following link in your browser:\n%s\n", $authUrl);
print 'Enter verification code: ';
$authCode = trim(fgets(STDIN));
// Exchange authorization code for an access token.
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);
// Check to see if there was an error.
if (array_key_exists('error', $accessToken)) {
throw new Exception(join(', ', $accessToken));
}
}
// Save the token to a file.
if (!file_exists(dirname($tokenPath))) {
mkdir(dirname($tokenPath), 0700, true);
}
file_put_contents($tokenPath, json_encode($client->getAccessToken()));
}
return $client;
}
// Get the API client and construct the service object.
$client = getClient();
Показываем пример файла credentials.json
{"web":{"client_id":"...","project_id":"gpru-artdetox-driven-bulwark","auth_uri":
"https://accounts.google.com/o/oauth2/auth","token_uri"
:"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.
com/oauth2/v1/certs","client_secret":"...","redirect_uris":["http://localhost/state"],"javascript_origins":
["http://localhost"]}}
Скрипт в index.php выполняется только в консоли php index.php
В результате выполнения скрипта в терминале (консоли) получим примерно следующее:
Копируем полученную ссылку и вводим в строку браузера. Сейчас нужно получить код авторизации для того адреса email, который будет работать с этим приложением.
После авторизации и получения доступа к приложению, к вашим файлам Excel будет перенаправление примерно по такой ссылке:
http://localhost/state?code=4/0AWyig&scope=https://www.googleapis.com/auth/spreadsheets
Копируем code и вставляем в терминал. После этого получим файл token.json.
Возможные проблемы получения токена
При получении токена могут возникнуть ошибки. Рассмотрим наиболее частые из них.
Проблема 1: Нет доступа к приложению или приложение не проверялось.
Решение: Добавьте тестового пользователя в разделе OAuth consent screen.
Проблема 2: Fatal error: Uncaught InvalidArgumentException: Redirect URI must be absolute in /var/www/vendor/google/auth/src/OAuth2.php:793
Решение: Эта ошибка связана с тем, что в разделе credentials (https://console.cloud.google.com/apis/credentials) проекта не указаны Authorized JavaScript origins и Authorized redirect URIs. Необходимо их заполнить и сохранить. При этом сохранение будет происходит в течение 5 минут.
Завершение настройки модуля
После проведения всех манипуляций, организация получения данных и отправление их на обработку в Batch-процесс выглядит так.
Важно! Файл с которым работаете должен быть доступен для тестового пользователя, либо должен быть открыт доступ по ссылке (чтение).
К примеру, SheetId с файла https://docs.google.com/spreadsheets/d/3xS34U5аZ8zVzU65ho-VFRVg/edit#gid=0 – это «3xS34U5аZ8zVzU65ho-VFRVg».
use Drupal\mw_google_spreadsheet\GetClient;
use Google\Service\Sheets;
/*
Это колонка - например E.
Колонка с ценами - например N
Начальная строка 5, а конечная 105.
*/
/* Получаем доступ к гугл файлу */
$columnArticul = 'E';
$columnPrice = 'N';
$start = 5;
$end = 105;
/* Ид рабочей области - файла гугл, берётся со ссылки на файл. */
$spreadsheetId = '1xS34U5vZ8z5jyblSJOvLUUShINhgKEVzU35ho-VFRVg';
$client = new GetClient();
$cl = $client->get();
$service = new Sheets($cl);
$range = $columnArticul . $start . ':' . $columnPrice . $end;
try {
$response = $service->spreadsheets_values->get($spreadsheetId, $range);
$values = $response->getValues();
}catch (Exception $e){
\Drupal::messenger()->addMessage( 'Выброшено исключение: ', $e->getMessage(), "\n
", 'error', TRUE); // Displays nothing (at UID=235)
die();
}
$num_operations = count($values);
$this->messenger()->addMessage($this->t('Creating an array of @num operations', ['@num' => $num_operations]));
$operations = [];
$i = 1;
foreach ($values as $value){
$operations[] = [
'mw_google_spreadsheet_op_1',
[
$value,
$this->t('(Operation @operation)', ['@operation' => $i]),
],
];
$i++;
}
$batch = [
'title' => $this->t('Creating an array of @num operations', ['@num' => $num_operations]),
'operations' => $operations,
'finished' => 'mw_google_spreadsheet_finished',
];
return $batch;
Функция обработки каждой строки 'mw_google_spreadsheet_op_1' в Batch-процессе выглядит так (описание в комментариях):
/**
* Batch operation for batch 1: one at a time.
*
* This is the function that is called on each operation in batch 1.
*/
function mw_google_spreadsheet_op_1($rowFromExcel, $operation_details, &$context) {
/* Пример полученной строки из файла
0 => "КХС-03-П-Т-Б"
1 => "Контейнер"
2 => "Стандарт"
3 => "3,06м"
4 => "2,16"
5 => "2,06"
6 => "200"
7 => "Торцевая"
8 => "Плоская"
9 => "Пол OSB"
10 => "Цинк"
11 => "р.106 800"
*/
//название поля из настройки сущности вариантов продукта - field_excele_artikul
/* получение всех записей вариантов для обработки по артикулу с колонки 0 */
$query = \Drupal::database()->select('commerce_product_variation__field_excele_artikul', 'cv_fea');
$query->fields('cv_fea', ['entity_id', 'revision_id', 'bundle', 'field_excele_artikul_value', 'langcode']);
$query->where('field_excele_artikul_value = :name', [':name' => $rowFromExcel[0]]);
$result = $query->execute()->fetchAll();
$ids = []; // все ид вариантов
foreach ($result as $item){
$ids[] = $item->entity_id;
}
// загружаем все варианты
$variations = \Drupal\commerce_product\Entity\ProductVariation::loadMultiple($ids);
//считаем их количество
$cont = count($rowFromExcel);
//из колонки цены удаляем все записи (кстати цена должна быть без копеек)
$price_new = preg_replace('/([^0-9]+)/','', $rowFromExcel[$cont - 1]);
/** @var \Drupal\commerce_product\Entity\ProductVariation $variation */
foreach ($variations as &$variation){
// в полученную из коллекции сущностей по артиклу вариантов записываем цену в нужном формате
// валюту можно брать из настроек магазина
$variation->setPrice(new \Drupal\commerce_price\Price($price_new, 'RUB'));
$variation->save();
}
$context['results'][] = implode(',', $ids);
// Optional message displayed under the progressbar.
$context['message'] = t('Running Batch "@id" @details',
['@id' => implode(',', $ids), '@details' => $operation_details]
);
}
В итоге получили, что каждая строка обрабатывает все записи вариантов по артикулу и присваивает им нужную цену с валютой RUB. Процесс отображается пользователю.
Можно менять строки начальной и конечной строки, не обновляя старые.
Как это работает для менеджера
Если цена меняется, менеджер исправляет ее в Google таблице. После этого в админке сайта необходимо выполнить одну простую команду «Обновить цены».
В результате на сайте автоматически цены будут обновлены.
Надеемся, статья была для вас полезна, и вы смогли по инструкции настроить самостоятельно синхронизацию цен и упростить работу себе и менеджерам. Подписывайтесь на наш Телеграм-канал, где мы рассказываем, как делаем дизайн, нейминг, продвижение, публикуем короткие инструкции и т.д. Будем рады новым подписчикам.
Пишите комментарии, если остались вопросы по настройкам.