Compare commits

...

7 Commits

8 changed files with 542 additions and 183 deletions

View File

@ -2,4 +2,4 @@
Synchronizes Google Sheets with ArangoDB Synchronizes Google Sheets with ArangoDB
😼 Developed in 1 days for 100000 rubles ($1200) ~~😼 Developed in 1 day for 100000 rubles ($1200)~~ shit happens

96
composer.lock generated
View File

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "11076f3d90ec7beb78f179975cadfba0", "content-hash": "a68510590091701e9c9d83f85929c2ca",
"packages": [ "packages": [
{ {
"name": "firebase/php-jwt", "name": "firebase/php-jwt",
"version": "v6.5.0", "version": "v6.7.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/firebase/php-jwt.git", "url": "https://github.com/firebase/php-jwt.git",
"reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2" "reference": "71278f20b0a623389beefe87a641d03948a38870"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/e94e7353302b0c11ec3cfff7180cd0b1743975d2", "url": "https://api.github.com/repos/firebase/php-jwt/zipball/71278f20b0a623389beefe87a641d03948a38870",
"reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2", "reference": "71278f20b0a623389beefe87a641d03948a38870",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -65,9 +65,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/firebase/php-jwt/issues", "issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v6.5.0" "source": "https://github.com/firebase/php-jwt/tree/v6.7.0"
}, },
"time": "2023-05-12T15:47:07+00:00" "time": "2023-06-14T15:29:26+00:00"
}, },
{ {
"name": "flow-php/array-dot", "name": "flow-php/array-dot",
@ -299,16 +299,16 @@
}, },
{ {
"name": "google/apiclient-services", "name": "google/apiclient-services",
"version": "v0.303.0", "version": "v0.304.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/googleapis/google-api-php-client-services.git", "url": "https://github.com/googleapis/google-api-php-client-services.git",
"reference": "b9c143453a94d5e6ed7257d065dcc5662619eaf4" "reference": "6731fd0d3e2f1ff2794f36108b55c0a3480edf3d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/b9c143453a94d5e6ed7257d065dcc5662619eaf4", "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/6731fd0d3e2f1ff2794f36108b55c0a3480edf3d",
"reference": "b9c143453a94d5e6ed7257d065dcc5662619eaf4", "reference": "6731fd0d3e2f1ff2794f36108b55c0a3480edf3d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -337,9 +337,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/googleapis/google-api-php-client-services/issues", "issues": "https://github.com/googleapis/google-api-php-client-services/issues",
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.303.0" "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.304.0"
}, },
"time": "2023-06-04T01:12:12+00:00" "time": "2023-06-07T02:11:36+00:00"
}, },
{ {
"name": "google/auth", "name": "google/auth",
@ -1199,16 +1199,16 @@
}, },
{ {
"name": "phpseclib/phpseclib", "name": "phpseclib/phpseclib",
"version": "3.0.19", "version": "3.0.20",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpseclib/phpseclib.git", "url": "https://github.com/phpseclib/phpseclib.git",
"reference": "cc181005cf548bfd8a4896383bb825d859259f95" "reference": "543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/cc181005cf548bfd8a4896383bb825d859259f95", "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67",
"reference": "cc181005cf548bfd8a4896383bb825d859259f95", "reference": "543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1289,7 +1289,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/phpseclib/phpseclib/issues", "issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.19" "source": "https://github.com/phpseclib/phpseclib/tree/3.0.20"
}, },
"funding": [ "funding": [
{ {
@ -1305,7 +1305,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-03-05T17:13:09+00:00" "time": "2023-06-13T06:30:34+00:00"
}, },
{ {
"name": "psr/cache", "name": "psr/cache",
@ -1788,64 +1788,6 @@
"source": "https://github.com/arangodb/arangodb-php/tree/v3.8.0" "source": "https://github.com/arangodb/arangodb-php/tree/v3.8.0"
}, },
"time": "2021-06-18T12:06:02+00:00" "time": "2021-06-18T12:06:02+00:00"
},
{
"name": "webmozart/assert",
"version": "1.11.0",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
"reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
"reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"php": "^7.2 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<0.12.20",
"vimeo/psalm": "<4.6.1 || 4.6.2"
},
"require-dev": {
"phpunit/phpunit": "^8.5.13"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.10-dev"
}
},
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"description": "Assertions to validate method input/output with nice error messages.",
"keywords": [
"assert",
"check",
"validate"
],
"support": {
"issues": "https://github.com/webmozarts/assert/issues",
"source": "https://github.com/webmozarts/assert/tree/1.11.0"
},
"time": "2022-06-03T18:03:27+00:00"
} }
], ],
"packages-dev": [], "packages-dev": [],

View File

@ -1,2 +0,0 @@
storage/*
!storage

View File

@ -27,7 +27,7 @@ use Google\Client,
require __DIR__ . '/../../../../../../vendor/autoload.php'; require __DIR__ . '/../../../../../../vendor/autoload.php';
$arangodb = new connection(require '../settings/arangodb.php'); $arangodb = new connection(require __DIR__ . '/../settings/arangodb.php');
function generateLabel(string $name): string function generateLabel(string $name): string
{ {
@ -36,7 +36,7 @@ function generateLabel(string $name): string
'type', 'ТИП', 'Тип', 'тип' => 'type', 'type', 'ТИП', 'Тип', 'тип' => 'type',
'director', 'ДИРЕКТОР', 'Директор', 'директор' => 'director', 'director', 'ДИРЕКТОР', 'Директор', 'директор' => 'director',
'address', 'АДРЕС', 'Адрес', 'адрес' => 'address', 'address', 'АДРЕС', 'Адрес', 'адрес' => 'address',
default => throw new exception("Неизвестный столбец: $name") default => $name
}; };
} }
@ -47,7 +47,7 @@ function degenerateLabel(string $name): string
'ТИП', 'type' => 'ТИП', 'ТИП', 'type' => 'ТИП',
'ДИРЕКТОР', 'director' => 'ДИРЕКТОР', 'ДИРЕКТОР', 'director' => 'ДИРЕКТОР',
'АДРЕС', 'address' => 'АДРЕС', 'АДРЕС', 'address' => 'АДРЕС',
default => throw new exception("Неизвестный столбец: $name") default => $name
}; };
} }
@ -65,21 +65,79 @@ function sync(Row &$row, string $city = 'Красноярск'): void
{ {
global $arangodb; global $arangodb;
$_row = init($row->entries()->toArray()['row']); // Инициализация строки в Google Sheet
$_row = init($row->toArray()['row']);
if ($_row['id'] !== null)
if (collection::init($arangodb->session, 'markets')) if (collection::init($arangodb->session, 'markets'))
if ($market = collection::search($arangodb->session, sprintf("FOR d IN markets FILTER d.id == '%s' RETURN d", $_row['id']))) if (!empty($_row['id']) && $market = collection::search($arangodb->session, sprintf("FOR d IN markets FILTER d.id == '%s' RETURN d", $_row['id']))) {
if ($_row === $new = array_diff_key($market->getAll(), ['_key' => true, 'created' => true, 'city' => true])); // Найдена запись магазина (строки) в базе данных и включен режим перезаписи (приоритет - google sheets)
else $row = $row->set((new Flow())->read(From::array([init($new, true)]))->fetch(1)[0]->get('row'));
else if (collection::search($arangodb->session, sprintf("FOR d IN markets FILTER d._id == '%s' RETURN d", document::write($arangodb->session, 'markets', $_row + ['city' => $city])))); if ($market->transfer_to_sheets) {
else throw new exception('Не удалось создать или найти созданного магазина'); // Запрошен форсированный перенос данных из базы данных в таблицу
// Инициализация данных для записи в таблицу
$new = [
'id' => $market->id ?? '',
'type' => $market->type ?? '',
'director' => $market->director ?? '',
'address' => $market->address ?? '',
];
// Замена NULL на пустую строку
foreach ($new as $key => &$value) if ($value === null) $value = '';
// Реинициализация строки с новыми данными по ссылке (приоритет из базы данных)
if ($_row !== $new) $row = $row->set((new Flow())->read(From::array([init($new, true)]))->fetch(1)[0]->get('row'));
// Деактивация форсированного трансфера
$market->transfer_to_sheets = false;
} else {
// Перенос изменений из Google Sheet в инстанцию документа в базе данных
// Реинициализация данных в инстанции документа в базе данных с данными из Google Sheet
foreach ($market->getAll() as $key => $value) {
// Перебор всех записанных значений в инстанции документа в базе данных
// Конвертация
$market->{$key} = $_row[$key] ?? $value;
}
}
// Обновление инстанции документа в базе данных
document::update($arangodb->session, $market);
} else if (
!empty($_row['id'])
&& $market = collection::search(
$arangodb->session,
sprintf(
"FOR d IN markets FILTER d._id == '%s' RETURN d",
document::write($arangodb->session, 'markets', [
'id' => $_row['id'] ?? '',
'type' => $_row['type'] ?? '',
'director' => $_row['director'] ?? '',
'address' => $_row['address'] ?? '',
'city' => $city,
'transfer_to_sheets' => false
])
)
)
) {
// Не найдена запись магазина (строки) в базе данных и была создана
/* // Реинициализация строки с новыми данными по ссылке (приоритет из Google Sheets)
$row = $row->set((new Flow())->read(From::array([init([
'id' => $_row['id'] ?? '',
'type' => $_row['type'] ?? '',
'director' => $_row['director'] ?? '',
'address' => $_row['address'] ?? '',
], true)]))->fetch(1)[0]->get('row')); */
} else return;
else throw new exception('Не удалось инициализировать коллекцию'); else throw new exception('Не удалось инициализировать коллекцию');
} }
$settings = json_decode(require('../settings/markets/google.php'), true); $settings = json_decode(require(__DIR__ . '/../settings/markets/google.php'), true);
$document = require('../settings/markets/document.php'); $document = require(__DIR__ . '/../settings/markets/document.php');
$sheets = require('../settings/markets/sheets.php'); $sheets = require(__DIR__ . '/../settings/markets/sheets.php');
$client = new Client(); $client = new Client();
$client->setScopes(Sheets::SPREADSHEETS); $client->setScopes(Sheets::SPREADSHEETS);

View File

@ -27,28 +27,28 @@ use Google\Client,
require __DIR__ . '/../../../../../../vendor/autoload.php'; require __DIR__ . '/../../../../../../vendor/autoload.php';
$arangodb = new connection(require '../settings/arangodb.php'); $arangodb = new connection(require __DIR__ . '/../settings/arangodb.php');
function generateLabel(string $name): string function generateLabel(string $name): string
{ {
return match ($name) { return match ($name) {
'id', 'ID', '1', '11', '111', '1111', '11111', 'index', 'Index', 'код', 'Код', 'ключ', 'Ключ', 'айди', 'Айди', 'идентификатор', 'Идентификатор' => 'id', 'id', 'ID' => 'id',
'name', 'ФИО', 'фио', 'ф.и.о.', 'ф. и. о.', 'Ф.И.О.', 'Ф. И. О.' => 'name', 'name', 'ФИО' => 'name',
'phone', 'Номер', 'номер', 'телефон', 'Телефон' => 'phone', 'phone', 'Номер' => 'phone',
'birth', 'Дата рождения', 'дата рождения', 'Год рождения', 'год рождения', 'Год', 'год', 'День рождения', 'день рождения' => 'birth', 'birth', 'Дата рождения' => 'birth',
'address', 'Адрес регистрации', 'адрес регистрации', 'Адрес', 'адрес', 'Регистрация', 'регистрация' => 'address', 'address', 'Регистрация' => 'address',
'commentary', 'Комментарий', 'ПРИМЕЧАНИЕ', 'Примечание', 'примечание' => 'commentary', 'commentary', 'Комментарий' => 'commentary',
'activity', 'Работа', 'работа', 'Вид Работы', 'Вид работы', 'вид работы' => 'activity', 'activity', 'Работа' => 'activity',
'passport', 'Паспорт', 'паспорт', 'серия и номер паспорта', 'Серия и номер паспорта' => 'passport', 'passport', 'Паспорт' => 'passport',
'issued', 'Выдан', 'выдан' => 'issued', 'issued', 'Выдан' => 'issued',
'department', 'Код подразделения', 'код подразделения', 'Подразделение', 'подразделение' => 'department', 'department', 'Подразделение' => 'department',
'hiring', 'Дата присоединения', 'Когда устроили', 'когда устроили' => 'hiring', 'hiring', 'Нанят' => 'hiring',
'district', 'Район', 'район' => 'district', 'district', 'Район', 'район' => 'district',
'requisites', 'Реквизиты', 'реквизиты' => 'requisites', 'requisites', 'Реквизиты', 'реквизиты' => 'requisites',
'fired', 'Дата увольнения', 'дата увольнения', 'Уволен', 'уволен', 'Увольнение', 'увольнение' => 'fired', 'fired', 'Уволен' => 'fired',
'payment', 'Оплата', 'оплата' => 'payment', 'payment', 'Оплата', 'оплата' => 'payment',
'tax', 'ИНН', 'инн' => 'tax', 'tax', 'ИНН', 'инн' => 'tax',
default => throw new exception("Неизвестный столбец: $name") default => $name
}; };
} }
@ -59,22 +59,34 @@ function degenerateLabel(string $name): string
'ФИО', 'name' => 'ФИО', 'ФИО', 'name' => 'ФИО',
'Номер', 'phone' => 'Номер', 'Номер', 'phone' => 'Номер',
'Дата рождения', 'birth' => 'Дата рождения', 'Дата рождения', 'birth' => 'Дата рождения',
'Адрес регистрации', 'address' => 'Адрес регистрации', 'Регистрация', 'address' => 'Регистрация',
'Комментарий', 'commentary' => 'Комментарий', 'Комментарий', 'commentary' => 'Комментарий',
'Работа', 'activity' => 'Работа', 'Работа', 'activity' => 'Работа',
'Паспорт', 'passport' => 'Паспорт', 'Паспорт', 'passport' => 'Паспорт',
'Выдан', 'issued' => 'Выдан', 'Выдан', 'issued' => 'Выдан',
'Код подразделения', 'department' => 'Код подразделения', 'Подразделение', 'department' => 'Подразделение',
'Дата присоединения', 'hiring' => 'Дата присоединения', 'Нанят', 'hiring' => 'Нанят',
'Район', 'district' => 'Район', 'Район', 'district' => 'Район',
'Реквизиты', 'requisites' => 'Реквизиты', 'Реквизиты', 'requisites' => 'Реквизиты',
'Дата увольнения', 'fired' => 'Дата увольнения', 'Уволен', 'fired' => 'Дата увольнения',
'Оплата', 'payment' => 'Оплата', 'Оплата', 'payment' => 'Оплата',
'ИНН', 'tax' => 'ИНН', 'ИНН', 'tax' => 'ИНН',
default => throw new exception("Неизвестный столбец: $name") default => $name
}; };
} }
function convertNumber(string $number): string
{
// Очистка всего кроме цифр, а потом поиск 10 первых чисел (без восьмёрки)
preg_match('/^8(\d{10})/', preg_replace("/[^\d]/", "", $number), $matches);
// Инициализация номера
$number = isset($matches[1]) ? 7 . $matches[1] : $number;
return $number;
}
function init(array $row, bool $reverse = false): array function init(array $row, bool $reverse = false): array
{ {
$buffer = []; $buffer = [];
@ -140,28 +152,125 @@ function sync(Row &$row, string $city = 'Красноярск'): void
{ {
global $arangodb; global $arangodb;
$_row = init($row->entries()->toArray()['row']); // Инициализация строки в Google Sheet
$_row = init($row->toArray()['row']);
if ($_row['id'] !== null)
if (collection::init($arangodb->session, 'workers')) if (collection::init($arangodb->session, 'workers'))
if ($worker = collection::search($arangodb->session, sprintf("FOR d IN workers FILTER d.id == '%s' RETURN d", $_row['id'])) if (!empty($_row['id']) && $worker = collection::search($arangodb->session, sprintf("FOR d IN workers FILTER d.id == '%s' RETURN d", $_row['id']))) {
?? collection::search($arangodb->session, sprintf("FOR d IN workers FILTER d._id == '%s' RETURN d", document::write($arangodb->session, 'workers', $_row + ['city' => $city]))) // Найдена запись работника (строки) в базе данных и включен режим перезаписи (приоритет - google sheets)
) {
// Инициализирован работник
// Реинициализация строки с актуальными записями (приоритет у базы данных) if ($worker->transfer_to_sheets) {
if ($_row !== $new = array_diff_key($worker->getAll(), ['_key' => true, 'created' => true, 'city' => true])) // Запрошен форсированный перенос данных из базы данных в таблицу
$row = $row->set((new Flow())->read(From::array([init($new, true)]))->fetch(1)[0]->get('row'));
// Инициализация данных для записи в таблицу
$new = [
'id' => $worker->id ?? '',
'name' => $worker->name ?? '',
'phone' => convertNumber($worker->phone ?? ''),
'birth' => $worker->birth ?? '',
'address' => $worker->address ?? '',
'commentary' => $worker->commentary ?? '',
'activity' => $worker->activity ?? '',
'passport' => $worker->passport ?? '',
'issued' => $worker->issued ?? '',
'department' => $worker->department ?? '',
'hiring' => $worker->hiring ?? '',
'district' => $worker->district ?? '',
'requisites' => $worker->requisites ?? '',
'fired' => $worker->fired ?? '',
'payment' => $worker->payment ?? '',
'tax' => $worker->tax ?? '',
];
// Замена NULL на пустую строку
foreach ($new as $key => &$value) if ($value === null) $value = '';
// Реинициализация строки с новыми данными по ссылке (приоритет из базы данных)
if ($_row !== $new) $row = $row->set((new Flow())->read(From::array([init($new, true)]))->fetch(1)[0]->get('row'));
// Деактивация форсированного трансфера
$worker->transfer_to_sheets = false;
} else {
// Перенос изменений из Google Sheet в инстанцию документа в базе данных
// Реинициализация данных в инстанции документа в базе данных с данными из Google Sheet
foreach ($worker->getAll() as $key => $value) {
// Перебор всех записанных значений в инстанции документа в базе данных
// Конвертация
$worker->{$key} = $_row[$key] ?? $value;
}
}
// Обновление инстанции документа в базе данных
document::update($arangodb->session, $worker);
// Подключение к чат-роботам // Подключение к чат-роботам
connectAll($worker); connectAll($worker);
} else throw new exception('Не удалось создать или найти созданного работника'); } else if (
!empty($_row['id'])
&& !empty($_row['phone'])
&& $worker = collection::search(
$arangodb->session,
sprintf(
"FOR d IN workers FILTER d._id == '%s' RETURN d",
document::write($arangodb->session, 'workers', [
'id' => $_row['id'] ?? '',
'name' => $_row['name'] ?? '',
'phone' => convertNumber($_row['phone'] ?? ''),
'birth' => $_row['birth'] ?? '',
'address' => $_row['address'] ?? '',
'commentary' => $_row['commentary'] ?? '',
'activity' => $_row['activity'] ?? '',
'passport' => $_row['passport'] ?? '',
'issued' => $_row['issued'] ?? '',
'department' => $_row['department'] ?? '',
'hiring' => $_row['hiring'] ?? '',
'district' => $_row['district'] ?? '',
'requisites' => $_row['requisites'] ?? '',
'fired' => $_row['fired'] ?? '',
'payment' => $_row['payment'] ?? '',
'tax' => $_row['tax'] ?? '',
'city' => $city,
'transfer_to_sheets' => false
])
)
)
) {
// Не найдена запись работника (строки) в базе данных и была создана
// Инициализация номера
$number = convertNumber($_row['phone'] ?? '');
// Реинициализация строки с новыми данными по ссылке (приоритет из Google Sheets)
$row = $row->set((new Flow())->read(From::array([init([
'id' => $_row['id'] ?? '',
'name' => $_row['name'] ?? '',
'phone' => "=HYPERLINK(\"https://call.ctrlq.org/+$number\"; \"$number\")",
'birth' => $_row['birth'] ?? '',
'address' => $_row['address'] ?? '',
'commentary' => $_row['commentary'] ?? '',
'activity' => $_row['activity'] ?? '',
'passport' => $_row['passport'] ?? '',
'issued' => $_row['issued'] ?? '',
'department' => $_row['department'] ?? '',
'hiring' => $_row['hiring'] ?? '',
'district' => $_row['district'] ?? '',
'requisites' => $_row['requisites'] ?? '',
'fired' => $_row['fired'] ?? '',
'payment' => $_row['payment'] ?? '',
'tax' => $_row['tax'] ?? '',
], true)]))->fetch(1)[0]->get('row'));
// Подключение к чат-роботам
connectAll($worker);
} else return;
else throw new exception('Не удалось инициализировать коллекцию'); else throw new exception('Не удалось инициализировать коллекцию');
} }
$settings = json_decode(require('../settings/workers/google.php'), true); $settings = json_decode(require(__DIR__ . '/../settings/workers/google.php'), true);
$document = require('../settings/workers/document.php'); $document = require(__DIR__ . '/../settings/workers/document.php');
$sheets = require('../settings/workers/sheets.php'); $sheets = require(__DIR__ . '/../settings/workers/sheets.php');
$client = new Client(); $client = new Client();
$client->setScopes(Sheets::SPREADSHEETS); $client->setScopes(Sheets::SPREADSHEETS);

View File

@ -27,39 +27,89 @@ use Google\Client,
require __DIR__ . '/../../../../../../vendor/autoload.php'; require __DIR__ . '/../../../../../../vendor/autoload.php';
$arangodb = new connection(require '../settings/arangodb.php'); $arangodb = new connection(require __DIR__ . '/../settings/arangodb.php');
function generateLabel(string $name): string function generateLabel(string $name): string
{ {
return match ($name) { return match ($name) {
'_id', 'ID' => '_id', 'imported_created_in_sheets', 'Отметка времени' => 'imported_created_in_sheets',
'market', 'Магазин' => 'market', 'imported_date', 'Дата заявки' => 'imported_date',
'worker', 'Сотрудник' => 'worker', 'imported_market', 'Ваш магазин' => 'imported_market',
'work', 'Работа' => 'work', 'imported_worker', 'Требуемый сотрудник' => 'imported_worker',
'imported_work', 'Вид работы' => 'imported_work',
'imported_start', 'Начало работы' => 'imported_start',
'imported_end', 'Конец работы' => 'imported_end',
'imported_hours', 'Часы работы' => 'imported_hours',
'created_in_sheets', 'Создано' => 'created_in_sheets',
'date', 'Дата' => 'date', 'date', 'Дата' => 'date',
'market', 'Магазин' => 'market',
'type', 'Тип' => 'type',
'address', 'Адрес' => 'address',
'worker', 'Сотрудник' => 'worker',
'name', 'ФИО' => 'name',
'work', 'Работа' => 'work',
'start', 'Начало' => 'start', 'start', 'Начало' => 'start',
'end', 'Конец' => 'end', 'end', 'Конец' => 'end',
'hours', 'Часы' => 'hours',
'tax', 'ИНН' => 'tax',
'confirmed', 'Подтверждено' => 'confirmed', 'confirmed', 'Подтверждено' => 'confirmed',
'commentary', 'Комментарий' => 'commentary', 'commentary', 'Комментарий' => 'commentary',
'response', 'Ответ' => 'response', 'response', 'Ответ' => 'response',
default => throw new exception("Неизвестный столбец: $name") '_id', 'ID' => '_id',
default => $name
}; };
} }
function degenerateLabel(string $name): string function degenerateLabel(string $name): string
{ {
return match ($name) { return match ($name) {
'ID', '_id' => 'ID', 'Отметка времени', 'imported_created_in_sheets' => 'Отметка времени',
'Магазин', 'market' => 'Магазин', 'Дата заявки', 'imported_date' => 'Дата заявки',
'Сотрудник', 'worker' => 'Сотрудник', 'Ваш магазин', 'imported_market' => 'Ваш магазин',
'Работа', 'work' => 'Работа', 'Требуемый сотрудник', 'imported_worker' => 'Требуемый сотрудник',
'Вид работы', 'imported_work' => 'Вид работы',
'Начало работы', 'imported_start' => 'Начало работы',
'Конец работы', 'imported_end' => 'Конец работы',
'Часы работы', 'imported_hours' => 'Часы работы',
'Создано', 'created_in_sheets' => 'Создано',
'Дата', 'date' => 'Дата', 'Дата', 'date' => 'Дата',
'Магазин', 'market' => 'Магазин',
'Тип', 'type' => 'Тип',
'Адрес', 'address' => 'Адрес',
'Сотрудник', 'worker' => 'Сотрудник',
'ФИО', 'name' => 'ФИО',
'Работа', 'work' => 'Работа',
'Начало', 'start' => 'Начало', 'Начало', 'start' => 'Начало',
'Конец', 'end' => 'Конец', 'Конец', 'end' => 'Конец',
'Часы', 'hours' => 'Часы',
'ИНН', 'tax' => 'ИНН',
'Подтверждено', 'confirmed' => 'Подтверждено', 'Подтверждено', 'confirmed' => 'Подтверждено',
'Комментарий', 'commentary' => 'Комментарий', 'Комментарий', 'commentary' => 'Комментарий',
'Ответ', 'response' => 'Ответ', 'Ответ', 'response' => 'Ответ',
default => throw new exception("Неизвестный столбец: $name") 'ID', '_id' => 'ID',
default => $name
};
}
function filterWorker(?string $worker): string
{
global $arangodb;
return match ($worker) {
'', 'Отмена', 'отмена', 0, 00, 000, 0000, 00000, 000000, 0000000, 00000000, 000000000, 0000000000 => '',
default => (function () use ($worker, $arangodb) {
if (
collection::init($arangodb->session, 'workers')
&& collection::search(
$arangodb->session,
sprintf(
"FOR d IN workers FILTER d.id == %s RETURN d",
$worker
)
)
) return $worker;
else return '';
})()
}; };
} }
@ -73,56 +123,216 @@ function init(array $row, bool $reverse = false): array
} }
function sync(Row &$row): void function sync(int $_i, Row &$row, ?array $raw = null): void
{ {
global $arangodb; global $arangodb;
$_row = init($row->entries()->toArray()['row']); // Инициализация строки в Google Sheet
$_row = init($row->toArray()['row']);
if (collection::init($arangodb->session, 'works')) if (collection::init($arangodb->session, 'works'))
if (!empty($_row['_id']) && $work = collection::search($arangodb->session, sprintf("FOR d IN works FILTER d._id == '%s' RETURN d", $_row['_id']))) { if (!empty($_row['_id']) && $work = collection::search($arangodb->session, sprintf("FOR d IN works FILTER d._id == '%s' RETURN d", $_row['_id']))) {
// Найдена запись работы (строки) в базе данных // Найдена запись работы (строки) в базе данных
// Очистка перед записью в таблицу if ($work->transfer_to_sheets) {
$new = array_diff_key($work->getAll(), ['_key' => true, 'created' => true]); // Запрошен форсированный перенос данных из базы данных в таблицу
// Инициализация выбранного сотрудника // Инициализация данных для записи в таблицу
$new = [
'imported_created_in_sheets' => $work->imported_created_in_sheets['converted'],
'imported_date' => $work->imported_date['converted'],
'imported_market' => $work->imported_market,
'imported_worker' => $work->imported_worker,
'imported_work' => $work->imported_work,
'imported_start' => $work->imported_start['converted'],
'imported_end' => $work->imported_end['converted'],
'imported_hours' => $work->imported_hours,
'created_in_sheets' => $work->created_in_sheets['converted'],
'date' => $work->date['converted'],
'market' => $work->market,
'type' => $work->type,
'address' => $work->address,
'worker' => $work->worker,
'name' => $work->name,
'work' => $work->work,
'start' => $work->start['converted'],
'end' => $work->end['converted'],
'hours' => $work->hours,
'tax' => $work->tax,
'confirmed' => $work->confirmed,
'commentary' => $work->commentary,
'response' => $work->response,
'_id' => $work->getId(),
];
// Инициализация сотрудника
if (collection::init($arangodb->session, 'readinesses', true) && collection::init($arangodb->session, 'workers')) if (collection::init($arangodb->session, 'readinesses', true) && collection::init($arangodb->session, 'workers'))
$new = ['worker' => collection::search( $new['worker'] = collection::search(
$arangodb->session, $arangodb->session,
sprintf( $worker = sprintf(
"FOR d IN workers LET e = (FOR e IN readinesses FILTER e._to == '%s' RETURN e._from)[0] FILTER d._id == e RETURN d", "FOR d IN workers LET e = (FOR e IN readinesses FILTER e._to == '%s' RETURN e._from)[0] FILTER d._id == e RETURN d",
$_row['_id'] $_row['_id']
) )
)->id ?? ''] + $new; )?->id;
else throw new exception('Не удалось инициализировать коллекции'); else throw new exception('Не удалось инициализировать коллекции');
// Инициализация магазина // Инициализация магазина
if (collection::init($arangodb->session, 'requests', true) && collection::init($arangodb->session, 'markets')) if (collection::init($arangodb->session, 'requests', true) && collection::init($arangodb->session, 'markets'))
if ($market = collection::search( if ($new['market'] = collection::search(
$arangodb->session, $arangodb->session,
sprintf( sprintf(
"FOR d IN markets LET e = (FOR e IN requests FILTER e._to == '%s' RETURN e._from)[0] FILTER d._id == e RETURN d", "FOR d IN markets LET e = (FOR e IN requests FILTER e._to == '%s' RETURN e._from)[0] FILTER d._id == e RETURN d",
$_row['_id'] $_row['_id']
) )
)) $new = ['market' => $market->id] + $new; )?->id);
else throw new exception('Не удалось найти магазин'); else throw new exception('Не удалось найти магазин');
else throw new exception('Не удалось инициализировать коллекции'); else throw new exception('Не удалось инициализировать коллекции');
// Запись идентификатора только что созданной записи в базе данных для записи в таблицу // Замена NULL на пустую строку
$new = ['_id' => $work->getId()] + $new; foreach ($new as $key => &$value) if ($value === null) $value = '';
// Реинициализация строки с новыми данными по ссылке (приоритет из базы данных) // Реинициализация строки с новыми данными по ссылке (приоритет из базы данных)
if ($_row !== $new) $row = $row->set((new Flow())->read(From::array([init($new, true)]))->fetch(1)[0]->get('row')); if ($_row !== $new) $row = $row->set((new Flow())->read(From::array([init($new, true)]))->fetch(1)[0]->get('row'));
// Деактивация форсированного трансфера
$work->transfer_to_sheets = false;
} else {
// Перенос изменений из Google Sheet в инстанцию документа в базе данных
if (
collection::init($arangodb->session, 'readinesses', true) && collection::init($arangodb->session, 'workers')
&& ($worker = collection::search(
$arangodb->session,
sprintf(
"FOR d IN workers LET e = (FOR e IN readinesses FILTER e._to == '%s' RETURN e._from)[0] FILTER d._id == e RETURN d",
$_row['_id']
)
))
&& $_row['worker'] !== $work->worker
) {
// Изменён сотрудник (подразумевается, что внутри google sheet)
if ($readiness = collection::search(
$arangodb->session,
sprintf(
"FOR e IN readinesses FILTER e._from == '%s' && e._to == '%s' LIMIT 1 RETURN e",
$worker->getId(),
$_row['_id']
)
)) {
// Инициализировано ребро: worker => work
if ($_worker = collection::search(
$arangodb->session,
sprintf(
"FOR d IN workers FILTER d.id == '%s' LIMIT 1 RETURN d",
$_row['worker']
)
)) {
// Инициализирована инстанция документа в базе данных нового работника
// Реинициализация работника
$readiness->_from = $_worker->getId();
// Обновление в базе данных
document::update($arangodb->session, $readiness);
}
}
}
if (
collection::init($arangodb->session, 'requests', true) && collection::init($arangodb->session, 'markets')
&& ($market = collection::search(
$arangodb->session,
sprintf(
"FOR d IN markets LET e = (FOR e IN requests FILTER e._to == '%s' RETURN e._from)[0] FILTER d._id == e RETURN d",
$_row['_id']
)
))
&& $_row['market'] !== $work->market
) {
// Изменён магазин (подразумевается, что внутри google sheet)
if ($request = collection::search(
$arangodb->session,
sprintf(
"FOR e IN requests FILTER e._from == '%s' && e._to == '%s' LIMIT 1 RETURN e",
$market->getId(),
$_row['_id']
)
)) {
// Инициализировано ребро: market => work
if ($_market = collection::search(
$arangodb->session,
sprintf(
"FOR d IN markets FILTER d.id == '%s' LIMIT 1 RETURN d",
$_row['market']
)
)) {
// Инициализирована инстанция документа в базе данных нового мазагина
// Реинициализация магазина
$request->_from = $_market->getId();
// Обновление в базе данных
document::update($arangodb->session, $request);
}
}
}
// Инициализация счётчика итераций
$i = 0;
// Реинициализация данных в инстанции документа в базе данных с данными из Google Sheet
foreach (array_diff_key($work->getAll(), ['_key' => true, 'created' => true]) as $key => $value) {
// Перебор всех записанных значений в инстанции документа в базе данных
// Конвертация
$work->{$key} = is_array($value) ? ['number' => $_row[$key] ?? $value, 'converted' => $raw[$i]] : $_row[$key] ?? $value;
// Запись в счётчик итераций
++$i;
}
}
// Обновление инстанции документа в базе данных
document::update($arangodb->session, $work);
} else if ( } else if (
!empty($_row['market']) !empty($_row['imported_market'])
&& collection::init($arangodb->session, 'requests', true) && collection::init($arangodb->session, 'markets') && collection::init($arangodb->session, 'requests', true) && collection::init($arangodb->session, 'markets')
&& ($market = collection::search($arangodb->session, sprintf("FOR d IN markets FILTER d.id == '%s' RETURN d", $_row['market']))) && ($market = collection::search($arangodb->session, sprintf("FOR d IN markets FILTER d.id == '%s' RETURN d", $_row['imported_market'])))
&& $work = collection::search( && $work = collection::search(
$arangodb->session, $arangodb->session,
sprintf( sprintf(
"FOR d IN works FILTER d._id == '%s' RETURN d", "FOR d IN works FILTER d._id == '%s' RETURN d",
document::write($arangodb->session, 'works', array_diff_key($_row, ['_id' => true, 'market' => true, 'worker' => true])) document::write($arangodb->session, 'works', [
'imported_created_in_sheets' => ['number' => $_row['imported_created_in_sheets'] ?? '', 'converted' => $raw[0] ?? ''],
'imported_date' => ['number' => $_row['imported_date'] ?? '', 'converted' => $raw[1] ?? ''],
'imported_market' => $_row['imported_market'] ?? '',
'imported_worker' => $_row['imported_worker'] ?? '',
'imported_work' => $_row['imported_work'] ?? '',
'imported_start' => ['number' => $_row['imported_start'] ?? '', 'converted' => $raw[5] ?? ''],
'imported_end' => ['number' => $_row['imported_end'] ?? '', 'converted' => $raw[6] ?? ''],
'imported_hours' => $_row['imported_hours'] ?? '',
'created_in_sheets' => ['number' => $_row['imported_created_in_sheets'] ?? '', 'converted' => $raw[0] ?? ''],
'date' => ['number' => $_row['imported_date'] ?? '', 'converted' => $raw[1] ?? ''],
'market' => $_row['imported_market'] ?? '',
'type' => empty($_row['type']) ? "=ЕСЛИ(СОВПАД(I$_i;\"\");\"\"; IFNA(ВПР(K$_i;part_import_KRSK!\$B\$2:\$E\$15603;2;);\"Нет в базе\"))" : $_row['type'],
'address' => empty($_row['address']) ? "=ЕСЛИ(СОВПАД(I$_i;\"\");\"\"; IFNA(ВПР(K$_i;part_import_KRSK!\$B\$2:\$E\$15603;4;);\"Нет в базе\"))" : $_row['address'],
'worker' => $_row['imported_worker'] ?? '',
'name' => empty($_row['name']) ? "=ЕСЛИ(СОВПАД(\$I$_i;\"\");\"\"; ЕСЛИ( НЕ(СОВПАД(IFNA(ВПР(\$N$_i;part_import_KRSK!\$R$2:\$R$4999;1;);\"\");\$N$_i)); ЕСЛИ((СОВПАД(IFNA(ВПР(\$N$_i;part_import_KRSK!\$I\$2:\$L\$4999;4);\"\");\"\")); IFNA(ВПР(\$N$_i;part_import_KRSK!\$I\$2:\$J\$4999;2;); \"Сотрудник не назначен\"); \"УВОЛЕН (В списке работающих)\"); \"УВОЛЕН (В списке уволенных)\"))" : $_row['name'],
'work' => $_row['imported_work'] ?? '',
'start' => ['number' => $_row['imported_start'] ?? '', 'converted' => $raw[5] ?? ''],
'end' => ['number' => $_row['imported_end'] ?? '', 'converted' => $raw[6] ?? ''],
'hours' => $_row['imported_hours'] ?? '',
'tax' => empty($_row['tax']) ? "=ЕСЛИ(СОВПАД(\$I$_i;\"\");\"\"; IFNA(ВПР(\$N$_i;part_import_KRSK!\$I\$2:\$K\$5000;3;); IFNA(ВПР(\$N$_i;part_import_KRSK!\$R\$2:\$T\$5000;3;);\"000000000000\")))" : $_row['tax'],
'confirmed' => $_row['confirmed'] ?? '',
'commentary' => $_row['commentary'] ?? '',
'response' => $_row['response'] ?? '',
'transfer_to_sheets' => false
])
) )
) )
) { ) {
@ -154,35 +364,77 @@ function sync(Row &$row): void
else throw new exception('Не удалось создать готовность сотрудника'); else throw new exception('Не удалось создать готовность сотрудника');
} }
// Реинициализация строки с новыми данными по ссылке (приоритет из базы данных) // Запись идентификатора только что созданной инстанции документа в базе данных
$row = $row->set((new Flow())->read(From::array([init(['_id' => $work->getId()] + $_row, true)]))->fetch(1)[0]->get('row')); $_row['_id'] = $work->getId();
// Реинициализация строки с новыми данными по ссылке (приоритет из Google Sheets)
$row = $row->set((new Flow())->read(From::array([init([
'imported_created_in_sheets' => $raw[0] ?? '',
'imported_date' => $raw[1] ?? '',
'imported_market' => $_row['imported_market'] ?? '',
'imported_worker' => $_row['imported_worker'] ?? '',
'imported_work' => $_row['imported_work'] ?? '',
'imported_start' => $raw[5] ?? '',
'imported_end' => $raw[6] ?? '',
'imported_hours' => $_row['imported_hours'] ?? '',
'created_in_sheets' => $raw[0] ?? '',
'date' => $raw[1] ?? '',
'market' => $_row['imported_market'] ?? '',
'type' => empty($_row['type']) ? "=ЕСЛИ(СОВПАД(I$_i;\"\");\"\"; IFNA(ВПР(K$_i;part_import_KRSK!\$B\$2:\$E\$15603;2;);\"Нет в базе\"))" : $_row['type'],
'address' => empty($_row['address']) ? "=ЕСЛИ(СОВПАД(I$_i;\"\");\"\"; IFNA(ВПР(K$_i;part_import_KRSK!\$B\$2:\$E\$15603;4;);\"Нет в базе\"))" : $_row['address'],
'worker' => filterWorker($_row['imported_worker']),
'name' => empty($_row['name']) ? "=ЕСЛИ(СОВПАД(\$I$_i;\"\");\"\"; ЕСЛИ( НЕ(СОВПАД(IFNA(ВПР(\$N$_i;part_import_KRSK!\$R$2:\$R$4999;1;);\"\");\$N$_i)); ЕСЛИ((СОВПАД(IFNA(ВПР(\$N$_i;part_import_KRSK!\$I\$2:\$L\$4999;4);\"\");\"\")); IFNA(ВПР(\$N$_i;part_import_KRSK!\$I\$2:\$J\$4999;2;); \"Сотрудник не назначен\"); \"УВОЛЕН (В списке работающих)\"); \"УВОЛЕН (В списке уволенных)\"))" : $_row['name'],
'work' => $_row['imported_work'] ?? '',
'start' => $raw[5] ?? '',
'end' => $raw[6] ?? '',
'hours' => $_row['imported_hours'] ?? '',
'tax' => empty($_row['tax']) ? "=ЕСЛИ(СОВПАД(\$I$_i;\"\");\"\"; IFNA(ВПР(\$N$_i;part_import_KRSK!\$I\$2:\$K\$5000;3;); IFNA(ВПР(\$N$_i;part_import_KRSK!\$R\$2:\$T\$5000;3;);\"000000000000\")))" : $_row['tax'],
'confirmed' => $_row['confirmed'] ?? '',
'commentary' => $_row['commentary'] ?? '',
'response' => $_row['response'] ?? '',
'_id' => $_row['_id'] ?? '',
], true)]))->fetch(1)[0]->get('row'));
} else return; } else return;
else throw new exception('Не удалось инициализировать коллекцию'); else throw new exception('Не удалось инициализировать коллекцию');
} }
$settings = json_decode(require('../settings/works/google.php'), true); $settings = json_decode(require(__DIR__ . '/../settings/works/google.php'), true);
$document = require('../settings/works/document.php'); $document = require(__DIR__ . '/../settings/works/document.php');
$sheets = require('../settings/works/sheets.php'); $sheets = require(__DIR__ . '/../settings/works/sheets.php');
$client = new Client(); $client = new Client();
$client->setScopes(Sheets::SPREADSHEETS); $client->setScopes(Sheets::SPREADSHEETS);
$client->setAuthConfig($settings); $client->setAuthConfig($settings);
foreach ($sheets as $sheet) { foreach ($sheets as $sheet) {
// Перебор таблиц
// Инициализация обработчика таблиц
$sheets = new Sheets($client); $sheets = new Sheets($client);
$rows = (new Flow())->read(new GoogleSheetExtractor($sheets, $document, new Columns($sheet, 'A', 'J'), true, 1000, 'row')); // Инициализация инстанции Flow для Google Sheet API
$rows = (new Flow())->read(new GoogleSheetExtractor($sheets, $document, new Columns($sheet, 'A', 'X'), true, 1000, 'row', ['valueRenderOption' => 'FORMULA']));
// Инициализация счётчика итераций
$i = 1; $i = 1;
foreach ($rows->fetch(10000) as $row) { foreach ($rows->fetch(10000) as $row) {
// Перебор строк
// Запись счётчика
++$i; ++$i;
// Инициализация буфера строки
$buffer = $row; $buffer = $row;
sync($row);
// Синхронизация с базой данных
sync($i, $row, $sheets->spreadsheets_values->get($document, "$sheet!A$i:X$i")[0] ?? null);
// Запись изменений строки в Google Sheet
if ($buffer !== $row) if ($buffer !== $row)
$sheets->spreadsheets_values->update( $sheets->spreadsheets_values->update(
$document, $document,
"$sheet!A$i:J$i", "$sheet!A$i:X$i",
new ValueRange(['values' => [array_values($row->entries()->toArray()['row'])]]), new ValueRange(['values' => [array_values($row->entries()->toArray()['row'])]]),
['valueInputOption' => 'USER_ENTERED'] ['valueInputOption' => 'USER_ENTERED']
); );

BIN
sheets/works.xlsx Normal file

Binary file not shown.

Binary file not shown.