Импорт данных в систему учета из .excel таблицы (Symfony)
170 000 руб. за проект
ВНИМАНИЕ : только фреймворк Symfony
оставляйте свой TG для связи!
Надо получить механизм, который позволяет
вгрузить к нам данные (создать и/ли обновить сущности) заданного типа
до загрузки получить предпросмотр, чтобы юзер смог указать наглядно "что куда грузим"
в зависимости от условий и выбранных данных, доуказать какие-то опции "как обрабатывать"
когда всего хватает - обработать импорт
Как сделано у другой системы (flow), для референса.
Там хорошо и правильно. Очень рекомендую посмотреть другие системы
выбираешь "импорт" - например товары
грузишь файл, excel-источник
бек его читает, собирает первые NN строк со столбцами, выводит в ответе предпросмотра
юзер в окне предпросмотра видит колонки с примером данных, выбирает им "заголовок", т.е куда грузить. По умолчанию не выбраны
бек получает выбранные колонки/типы данных, и отдает в ответе "какие еще опции надо указать"
юзер указывает (на фронте) доп параметры, которые бек запросил на этапе предпросмотра. Например "как обрабатывать остатки", создавать ли оприходование итп
юзер аппрувит, шлёт данные. Бек ставит задачу по обработке данных, со всеми указанными параметрами - отдает фронту "запустили обработку, вон ее ID"
обработка задачи выполняет то, что от нее просили, и финалится по готовности
Как бы я сделал, с учетом нашей имеющейся машинерии
У нас есть миграшки. Всё чем они занимаются - берут входящие параметры (любые), обрабатывают эти параметры, ставят себе в миграшку задачи, которые уже обрабатывают и вливают данные в зависимости от того, что попросили. Мне кажется - вполне наш вариант.
Чего нет: нам надо получать файлик, выдавать предпросмотр, дальше (до постановки задачи в обработку) понимать, чего от нас хотят и чего не хватает, запрашивать у фронта необходимые параметры (доп.опции и доп.обработки), и когда всего хватает - ставить и запускать миграшки. Миграшка умеет писать лог, чего реально происходило и как обработалось - окай.
Я бы сделал объект ImportTask, и у него type=excel (мало ли потом захотят еще там с 1C, YML как у МСклада итд итп)
Фронт запрашивает метод ImportTask/create entity=product (например), мы ее создаем пустую: фронт знает айдишник, может ее дальше доконфигурить
Фронт грузит к ней файлик (возможно, больше одного раза) - т.е метод upload
Мы по type=excel и entity=product понимаем, какие поля объекта существуют, и читаем первые 20 строк файла. Говорим OK - файлик получили (вдруг нет, или формат не тот)
Отдаем а) строки предпросмотра б) набор возможных полей (метод configure для таски)
В зависимости от уже загруженных данных в ImportTask, определяем, чего не хватает. Эта логика зависит от уже выбранных данных! так что имеет смысл для importTask написать ряд проверок, которые посмотрят на данные задачи, и что-то запишут в ее requirements + выдадут какой-то лог (например, "ваш файл ваще говно склизское")
Я бы сделал отдельный метод ImportTask/configure, в который фронт шлет тупо набор данных, собранных из формы. Прямо json. Тут тебе и выбранные поля, и опции входной обработки (не создавать товары, только обновлять / выбраны остатки, создавать приход, какого контрагента итд итп). Мы данные получаем, прогоняем обработчики по type+entity, и отвечаем на configure: вот снова предпросмотр, вот такие requirements, вот такой ответ валидации, вот такой набор справочных строк (например, сколько данных там вообще в файле нашли подходящих)
Для всей ImportTask я бы ввел статус, если он не ready - значит юзеру надо что-то дозаполнить
Фронт, когда видит дополнительные requirements, показывает юзеру доп.формы и доп.блоки, которых изначально не было, и которые у юзера дособерут нужные данные
Цикл повторяется: юзер дозаполняет или меняет поля (или даже грузит другой файл), мы снова всё проверяем, и так пока все наши проверки не пройдет, а для фронта ImportTask не станет ready
Когда у importTask status=ready, фронт вызывает метод execute, который по полностью заполненному importTask создает миграшку, и отвечает ok + ID созданной миграшки
Метод execute внутри бека создает MigrationTask - нужного типа для type=excel и entity=product - и передает ей входящий набор данных целиком
Созданная MigrationTaskImportExcelProduct (например) забирает уже полностью готовые данные, и создает Subtask нужного типа, сколько их там понадобится. Одна создает Products, вторая создает оприходование, третья заполняет кастомные поля товаров например, четвертая баркоды итп.
Где здесь узкие места
процесс конфигурации задачи может быть достаточно просторным (цикл вызовов /configure и дозаполнения данных). Может вообще хз когда завершиться. Я выбрал файл, вот предпросмотр. Я указываю что вот этот столбец - это остатки, сохраняюсь.
Бек смотрит "ага, у нас используются остатки - ставим в requirements что-то типа [stocks]".
Фронт это получает, выводит компонент с формой про оприходование.
Юзер на него смотрит, чот сложно, убирает выбор поля остатков для столбца.
Мы снова получаем данные configure - ага, остатки больше не хотят, в requirements stocks убираем (может там что-то еще осталось другое). Прогоняем остальные проверки. Итп.
Юзер внезапно понял что вообще мудак, и загрузил другой файл. Ловим другой файл, обновляем importTask, снова проверки, снова ответ.
Когда-то весь пакет данных станет ready.
фронту придется иметь набор подключаемых компонент для различных requirements. Мы не можем с бека сказать "вот такие контролы нам тут нарисуй", это придется понимать фронту (и у них блоки под остатки, под прочее = статически уже есть в коде, но включаются фронтом на странице в зависимости от ответа)
таски мигратора. Именно им придется читать файлик и собственно проводить операции над данными. Файлик на этом этапе валяется у нас на харде, условия обработки - в миграшке
читалка Excel. Тут надо найти подходящую, Леха нашел https://github.com/openspout/openspout , читает она норм. Читать придется и на предпросмотре (importTask type=excel), и на обработке (миграшки).
обработчики для requirements. Как можно заметить, они очень узко-специализированные, и их несколько.
базовые данные конфига. Можем сделать отдельный requirements:[product] например, для type=product.
И там уже с фронта прилетит набор опций: создавать/только обновлять товары, искать по id/по sku, итд.
Список данных нам на этом этапе - пофиг. Нам надо только собрать их по списку, убедиться что всё нужное заполнено, записать это в конфиг, и потом передать в миграшку - пускай уже она там разбирается, что конкретно наконфигурили.
Для importTask
id, created, account_id, modified, всякие стандартные поля
type, entity - что грузим и из чего (type=excel, entity=product)
source: имя загруженного файла, который уже у нас на диске
status: статус задачи на импорт, мне кажется [need_source, need_config, ready, executed] как минимум
requirements[], чтобы фронту однозначно показывать, какие блоки формы выводить
fields[] со списком всех возможных полей, чтобы фронт знал что выводить в формах (их можно тоже обработчиком заполнять из product - см ниже про configure)
fields_map[] со списком столбец_юзера = наше_поле. Выбраны для импорта могут быть далеко не все столбцы!
preview: json кеш прочитанных N строк со столбцами из файла, чтобы на диск не бегать
config: json набор произвольных данных ключ-значение
validation[] набор строк, которые мы заполним, когда проверим данные своими
migration_class чтобы понимать, какую миграшку создавать (по type=excel и entity=product)
migration_id чтобы понимать, что мы этому конфигу на execute завели миграцию, и вон она там по id живет
Цикл проверки configure, еще разок
вытаскиваем все подходящие обработчики по type=excel & entity=product
гоняем им всем метод check у нас на беке
каждый обработчик выполняет свою проверку, может читать всё что есть в importTask, и писать свои результаты проверки - выставлять/снимать requirements, дописывать в validation[] результаты проверки ("укажи контрагента, сцк!"), смотреть в fields[] (что у нас например остатки хотят)
если source нет, задача в статус need_source
если validation[] не пустой, задача в статус need_config
а если всего хватает, то status=ready, ждем финального execute когда юзер нажмет кнопку
Что надо сейчас
собрать эту машинерию на примере type=excel и entity=product. Остальные пока не трогаем, начнем с малого
поддержать как минимум один requirement, когда нам указывают работу с остатками
сделать загрузку upload, цикл configure, доведение importTask до ready
написать класс таска миграции (пока тупо один, excel+product), и к нему сабтаски: залив товаров, обработка остатков
чтобы не ждать фронт, проверить api-вызовами, что наша часть работает: importTask создается, к ней можно залить файл, к ней можно получить предпросмотр, к ней можно указать данные конфига, ответ попросит requirements:[stocks], и если доуказать все данные - importTask станет ready
по заполненной importTask status=ready запустить execute, создастся и запустится миграшка с сабтасками
Пока на детали самой миграшки и сабтасок можно плотно не заморачиваться (они могут быть вообще пустые). Потом задачами допилим.
Сейчас главное механизму с importTask собрать и проверить.
Эту задачу можно и нужно бить на сабтаски, и даже отдельные коммиты. Вот один метод, вот второй, вот добавили обработчики, вот фиксы, вот проверили requirements, вот миграшка, вот execute итд итп. Слона лучше жрать по частям.
оставляйте свой TG для связи!
Надо получить механизм, который позволяет
вгрузить к нам данные (создать и/ли обновить сущности) заданного типа
до загрузки получить предпросмотр, чтобы юзер смог указать наглядно "что куда грузим"
в зависимости от условий и выбранных данных, доуказать какие-то опции "как обрабатывать"
когда всего хватает - обработать импорт
Как сделано у другой системы (flow), для референса.
Там хорошо и правильно. Очень рекомендую посмотреть другие системы
выбираешь "импорт" - например товары
грузишь файл, excel-источник
бек его читает, собирает первые NN строк со столбцами, выводит в ответе предпросмотра
юзер в окне предпросмотра видит колонки с примером данных, выбирает им "заголовок", т.е куда грузить. По умолчанию не выбраны
бек получает выбранные колонки/типы данных, и отдает в ответе "какие еще опции надо указать"
юзер указывает (на фронте) доп параметры, которые бек запросил на этапе предпросмотра. Например "как обрабатывать остатки", создавать ли оприходование итп
юзер аппрувит, шлёт данные. Бек ставит задачу по обработке данных, со всеми указанными параметрами - отдает фронту "запустили обработку, вон ее ID"
обработка задачи выполняет то, что от нее просили, и финалится по готовности
Как бы я сделал, с учетом нашей имеющейся машинерии
У нас есть миграшки. Всё чем они занимаются - берут входящие параметры (любые), обрабатывают эти параметры, ставят себе в миграшку задачи, которые уже обрабатывают и вливают данные в зависимости от того, что попросили. Мне кажется - вполне наш вариант.
Чего нет: нам надо получать файлик, выдавать предпросмотр, дальше (до постановки задачи в обработку) понимать, чего от нас хотят и чего не хватает, запрашивать у фронта необходимые параметры (доп.опции и доп.обработки), и когда всего хватает - ставить и запускать миграшки. Миграшка умеет писать лог, чего реально происходило и как обработалось - окай.
Я бы сделал объект ImportTask, и у него type=excel (мало ли потом захотят еще там с 1C, YML как у МСклада итд итп)
Фронт запрашивает метод ImportTask/create entity=product (например), мы ее создаем пустую: фронт знает айдишник, может ее дальше доконфигурить
Фронт грузит к ней файлик (возможно, больше одного раза) - т.е метод upload
Мы по type=excel и entity=product понимаем, какие поля объекта существуют, и читаем первые 20 строк файла. Говорим OK - файлик получили (вдруг нет, или формат не тот)
Отдаем а) строки предпросмотра б) набор возможных полей (метод configure для таски)
В зависимости от уже загруженных данных в ImportTask, определяем, чего не хватает. Эта логика зависит от уже выбранных данных! так что имеет смысл для importTask написать ряд проверок, которые посмотрят на данные задачи, и что-то запишут в ее requirements + выдадут какой-то лог (например, "ваш файл ваще говно склизское")
Я бы сделал отдельный метод ImportTask/configure, в который фронт шлет тупо набор данных, собранных из формы. Прямо json. Тут тебе и выбранные поля, и опции входной обработки (не создавать товары, только обновлять / выбраны остатки, создавать приход, какого контрагента итд итп). Мы данные получаем, прогоняем обработчики по type+entity, и отвечаем на configure: вот снова предпросмотр, вот такие requirements, вот такой ответ валидации, вот такой набор справочных строк (например, сколько данных там вообще в файле нашли подходящих)
Для всей ImportTask я бы ввел статус, если он не ready - значит юзеру надо что-то дозаполнить
Фронт, когда видит дополнительные requirements, показывает юзеру доп.формы и доп.блоки, которых изначально не было, и которые у юзера дособерут нужные данные
Цикл повторяется: юзер дозаполняет или меняет поля (или даже грузит другой файл), мы снова всё проверяем, и так пока все наши проверки не пройдет, а для фронта ImportTask не станет ready
Когда у importTask status=ready, фронт вызывает метод execute, который по полностью заполненному importTask создает миграшку, и отвечает ok + ID созданной миграшки
Метод execute внутри бека создает MigrationTask - нужного типа для type=excel и entity=product - и передает ей входящий набор данных целиком
Созданная MigrationTaskImportExcelProduct (например) забирает уже полностью готовые данные, и создает Subtask нужного типа, сколько их там понадобится. Одна создает Products, вторая создает оприходование, третья заполняет кастомные поля товаров например, четвертая баркоды итп.
Где здесь узкие места
процесс конфигурации задачи может быть достаточно просторным (цикл вызовов /configure и дозаполнения данных). Может вообще хз когда завершиться. Я выбрал файл, вот предпросмотр. Я указываю что вот этот столбец - это остатки, сохраняюсь.
Бек смотрит "ага, у нас используются остатки - ставим в requirements что-то типа [stocks]".
Фронт это получает, выводит компонент с формой про оприходование.
Юзер на него смотрит, чот сложно, убирает выбор поля остатков для столбца.
Мы снова получаем данные configure - ага, остатки больше не хотят, в requirements stocks убираем (может там что-то еще осталось другое). Прогоняем остальные проверки. Итп.
Юзер внезапно понял что вообще мудак, и загрузил другой файл. Ловим другой файл, обновляем importTask, снова проверки, снова ответ.
Когда-то весь пакет данных станет ready.
фронту придется иметь набор подключаемых компонент для различных requirements. Мы не можем с бека сказать "вот такие контролы нам тут нарисуй", это придется понимать фронту (и у них блоки под остатки, под прочее = статически уже есть в коде, но включаются фронтом на странице в зависимости от ответа)
таски мигратора. Именно им придется читать файлик и собственно проводить операции над данными. Файлик на этом этапе валяется у нас на харде, условия обработки - в миграшке
читалка Excel. Тут надо найти подходящую, Леха нашел https://github.com/openspout/openspout , читает она норм. Читать придется и на предпросмотре (importTask type=excel), и на обработке (миграшки).
обработчики для requirements. Как можно заметить, они очень узко-специализированные, и их несколько.
базовые данные конфига. Можем сделать отдельный requirements:[product] например, для type=product.
И там уже с фронта прилетит набор опций: создавать/только обновлять товары, искать по id/по sku, итд.
Список данных нам на этом этапе - пофиг. Нам надо только собрать их по списку, убедиться что всё нужное заполнено, записать это в конфиг, и потом передать в миграшку - пускай уже она там разбирается, что конкретно наконфигурили.
Для importTask
id, created, account_id, modified, всякие стандартные поля
type, entity - что грузим и из чего (type=excel, entity=product)
source: имя загруженного файла, который уже у нас на диске
status: статус задачи на импорт, мне кажется [need_source, need_config, ready, executed] как минимум
requirements[], чтобы фронту однозначно показывать, какие блоки формы выводить
fields[] со списком всех возможных полей, чтобы фронт знал что выводить в формах (их можно тоже обработчиком заполнять из product - см ниже про configure)
fields_map[] со списком столбец_юзера = наше_поле. Выбраны для импорта могут быть далеко не все столбцы!
preview: json кеш прочитанных N строк со столбцами из файла, чтобы на диск не бегать
config: json набор произвольных данных ключ-значение
validation[] набор строк, которые мы заполним, когда проверим данные своими
migration_class чтобы понимать, какую миграшку создавать (по type=excel и entity=product)
migration_id чтобы понимать, что мы этому конфигу на execute завели миграцию, и вон она там по id живет
Цикл проверки configure, еще разок
вытаскиваем все подходящие обработчики по type=excel & entity=product
гоняем им всем метод check у нас на беке
каждый обработчик выполняет свою проверку, может читать всё что есть в importTask, и писать свои результаты проверки - выставлять/снимать requirements, дописывать в validation[] результаты проверки ("укажи контрагента, сцк!"), смотреть в fields[] (что у нас например остатки хотят)
если source нет, задача в статус need_source
если validation[] не пустой, задача в статус need_config
а если всего хватает, то status=ready, ждем финального execute когда юзер нажмет кнопку
Что надо сейчас
собрать эту машинерию на примере type=excel и entity=product. Остальные пока не трогаем, начнем с малого
поддержать как минимум один requirement, когда нам указывают работу с остатками
сделать загрузку upload, цикл configure, доведение importTask до ready
написать класс таска миграции (пока тупо один, excel+product), и к нему сабтаски: залив товаров, обработка остатков
чтобы не ждать фронт, проверить api-вызовами, что наша часть работает: importTask создается, к ней можно залить файл, к ней можно получить предпросмотр, к ней можно указать данные конфига, ответ попросит requirements:[stocks], и если доуказать все данные - importTask станет ready
по заполненной importTask status=ready запустить execute, создастся и запустится миграшка с сабтасками
Пока на детали самой миграшки и сабтасок можно плотно не заморачиваться (они могут быть вообще пустые). Потом задачами допилим.
Сейчас главное механизму с importTask собрать и проверить.
Эту задачу можно и нужно бить на сабтаски, и даже отдельные коммиты. Вот один метод, вот второй, вот добавили обработчики, вот фиксы, вот проверили requirements, вот миграшка, вот execute итд итп. Слона лучше жрать по частям.
В заказе есть исполнитель
При переводе заказа из архивного в актуальный, текущий исполнитель будет снят с задачи.
Выберите тип сделки
С безопасной сделкой вы всегда сможете вернуть средства, если что-то пойдет не так. С простой сделкой вы самостоятельно договариваетесь с исполнителем об оплате и берете на себя решение конфликтов.