forked from mirror/Part-DB.Part-DB-server
Compare commits
1 commit
master
...
stura_impo
Author | SHA1 | Date | |
---|---|---|---|
|
407d78b151 |
4 changed files with 375 additions and 1 deletions
|
@ -22,6 +22,7 @@
|
|||
"florianv/swap-bundle": "dev-master",
|
||||
"friendsofsymfony/ckeditor-bundle": "^2.0",
|
||||
"gregwar/captcha-bundle": "^2.1.0",
|
||||
"league/csv": "^9.6",
|
||||
"league/html-to-markdown": "^5.0.1",
|
||||
"liip/imagine-bundle": "^2.2",
|
||||
"nelmio/security-bundle": "^2.9",
|
||||
|
|
86
composer.lock
generated
86
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "8baea752d676ffb53b92c4cda2e2150e",
|
||||
"content-hash": "667c4b8cd1e3beb7ad1dab32ecd9e874",
|
||||
"packages": [
|
||||
{
|
||||
"name": "beberlei/assert",
|
||||
|
@ -2707,6 +2707,90 @@
|
|||
],
|
||||
"time": "2021-09-28T19:18:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/csv",
|
||||
"version": "9.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/csv.git",
|
||||
"reference": "f28da6e483bf979bac10e2add384c90ae9983e4e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/csv/zipball/f28da6e483bf979bac10e2add384c90ae9983e4e",
|
||||
"reference": "f28da6e483bf979bac10e2add384c90ae9983e4e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=7.2.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"ext-dom": "*",
|
||||
"friendsofphp/php-cs-fixer": "^2.16",
|
||||
"phpstan/phpstan": "^0.12.0",
|
||||
"phpstan/phpstan-phpunit": "^0.12.0",
|
||||
"phpstan/phpstan-strict-rules": "^0.12.0",
|
||||
"phpunit/phpunit": "^8.5"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "Required to use the XMLConverter and or the HTMLConverter classes",
|
||||
"ext-iconv": "Needed to ease transcoding CSV using iconv stream filters"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "9.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\Csv\\": "src"
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ignace Nyamagana Butera",
|
||||
"email": "nyamsprod@gmail.com",
|
||||
"homepage": "https://github.com/nyamsprod/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "CSV data manipulation made easy in PHP",
|
||||
"homepage": "http://csv.thephpleague.com",
|
||||
"keywords": [
|
||||
"convert",
|
||||
"csv",
|
||||
"export",
|
||||
"filter",
|
||||
"import",
|
||||
"read",
|
||||
"transform",
|
||||
"write"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://csv.thephpleague.com",
|
||||
"issues": "https://github.com/thephpleague/csv/issues",
|
||||
"rss": "https://github.com/thephpleague/csv/releases.atom",
|
||||
"source": "https://github.com/thephpleague/csv"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sponsors/nyamsprod",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-12-10T19:40:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/html-to-markdown",
|
||||
"version": "5.0.1",
|
||||
|
|
286
src/Command/SturaImportCommand.php
Normal file
286
src/Command/SturaImportCommand.php
Normal file
|
@ -0,0 +1,286 @@
|
|||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use App\Entity\PriceInformations\Pricedetail;
|
||||
use Brick\Math\BigDecimal;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use League\Csv\CharsetConverter;
|
||||
use League\Csv\Reader;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class SturaImportCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'app:stura-import';
|
||||
protected static $defaultDescription = 'Add a short description for your command';
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $dry_run;
|
||||
|
||||
const NORMALIZE_MAP = [
|
||||
'Tag der Buchung' => 'booking_date',
|
||||
'Beleg-Nr.' => 'reference_id',
|
||||
'Anzahl' => 'amount',
|
||||
'Bezeichnung' => 'name',
|
||||
'Lieferant/Empfänger' => 'supplier',
|
||||
'Stückpreis in Euro' => 'price',
|
||||
'Standort' => 'location',
|
||||
'Anmerkungen' => 'comment',
|
||||
'Zugang' => 'incoming_date',
|
||||
'Abgang' => 'outcoming_date',
|
||||
];
|
||||
|
||||
public function __construct(string $name = null, EntityManagerInterface $entityManager)
|
||||
{
|
||||
parent::__construct($name);
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addArgument('input', InputArgument::REQUIRED, 'The input electoral register as CSV')
|
||||
->addOption('dry', null, InputOption::VALUE_NONE, 'Dry run (Dont write changes to databse)')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$filename = $input->getArgument('input');
|
||||
$dry = $input->getOption('dry');
|
||||
$this->dry_run = $dry;
|
||||
|
||||
$csv = Reader::createFromPath($filename, 'r');
|
||||
$csv->setDelimiter(';');
|
||||
$csv->setHeaderOffset(0);
|
||||
|
||||
$stream = (new CharsetConverter())->inputEncoding('iso-8859-15')->convert($csv);
|
||||
|
||||
$io->info('Use file ' . $csv->getPathname());
|
||||
$io->info(sprintf('File contains %d entries', $csv->count()));
|
||||
|
||||
$progressBar = new ProgressBar($output, $csv->count());
|
||||
$progressBar->start();
|
||||
|
||||
foreach ($stream as $entry)
|
||||
{
|
||||
$data = $this->normalizeData($entry);
|
||||
|
||||
//Skip empty or not existing things
|
||||
if(empty($data['name']) || $data['amount'] === '0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$existing_part = $this->tryToFindExistingPart($data);
|
||||
$io->info(sprintf('Try to find "%s" from %s.', $data['name'], $data['location']));
|
||||
if ($existing_part !== null) {
|
||||
$io->info(sprintf('Found existing part. ID: %d', $existing_part->getID()));
|
||||
$this->updateExistingPart($existing_part, $data);
|
||||
} else {
|
||||
$io->info('Part not found. Create a new one.');
|
||||
$new_part = $this->createPartFromData($data);
|
||||
$this->entityManager->persist($new_part);
|
||||
}
|
||||
|
||||
$progressBar->advance();
|
||||
|
||||
}
|
||||
|
||||
$progressBar->finish();
|
||||
|
||||
if (!$dry) {
|
||||
$this->entityManager->flush();
|
||||
$io->success('Successfully wrote changes to database!');
|
||||
} else {
|
||||
$io->warning('Dry run mode activated. Changes were not written to DB.');
|
||||
}
|
||||
|
||||
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
protected function normalizeData(array $data): array
|
||||
{
|
||||
$return = [];
|
||||
foreach ($data as $key => $value) {
|
||||
if (isset(self::NORMALIZE_MAP[$key])) {
|
||||
$return[self::NORMALIZE_MAP[$key]] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
protected function tryToFindExistingPart(array $data): ?Part
|
||||
{
|
||||
//Always return null
|
||||
return null;
|
||||
|
||||
//Find location
|
||||
$repo = $this->entityManager->getRepository(Storelocation::class);
|
||||
$location = $repo->findOneBy(['name' => $data['location']]);
|
||||
//Return early if no location was created yet
|
||||
if ($location === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$qb = new QueryBuilder($this->entityManager);
|
||||
|
||||
$qb->select('part')
|
||||
->from(Part::class, 'part')
|
||||
->leftJoin('part.partLots', 'lots')
|
||||
//->leftJoin('part.orderdetails', 'orderdetails')
|
||||
//->leftJoin('orderdetails.pricedetails', 'pricedetails')
|
||||
//->andWhere('pricedetails.price')
|
||||
->where('lots.storage_location = ?1')
|
||||
->andWhere('part.name = ?2')
|
||||
->setParameter(1, $location)
|
||||
->setParameter(2, $data['name'])
|
||||
;
|
||||
|
||||
$result = $qb->getQuery()->getResult();
|
||||
if (empty($result)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$price_str = $this->getPriceAsFormattedString($data['amount']);
|
||||
//Check price to ensure it is the same part
|
||||
foreach ($result as $part) {
|
||||
/** @var Part $part */
|
||||
|
||||
if (!empty($price_str)) {
|
||||
if (!$part->getOrderdetails()[0]->getPricedetails()[0]->getPrice() !== null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Assume that part is always not existing
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function updateExistingPart(Part $existing_part, array $data): void
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected function getPriceAsFormattedString(string $amount): string
|
||||
{
|
||||
$price_str = trim($amount, " \n\r\t\v\0€");
|
||||
//Use decimal point instead of comma
|
||||
$price_str = trim(str_replace(',', '.', $price_str));
|
||||
|
||||
return $price_str;
|
||||
}
|
||||
|
||||
protected function createPartFromData(array $data): Part
|
||||
{
|
||||
$part = new Part();
|
||||
$part->setName($data['name']);
|
||||
$part_lot = new PartLot();
|
||||
$part_lot->setAmount((float) $data['amount']);
|
||||
$part_lot->setStorageLocation($this->getStorageLocation($data['location']));
|
||||
$part->addPartLot($part_lot);
|
||||
|
||||
$part->setCategory($this->getDummyCategory());
|
||||
|
||||
//Add price information
|
||||
$price_str = $this->getPriceAsFormattedString($data['amount']);
|
||||
if (!empty($price_str)) {
|
||||
|
||||
$orderdetail = new Orderdetail();
|
||||
$orderdetail->setSupplier($this->getDummySupplier());
|
||||
|
||||
$orderdetail->addPricedetail(
|
||||
(new Pricedetail())
|
||||
->setMinDiscountQuantity(1)
|
||||
->setPrice(BigDecimal::of((float) $price_str))
|
||||
);
|
||||
|
||||
$part->addOrderdetail($orderdetail);
|
||||
}
|
||||
|
||||
//Add comment
|
||||
$comment = '';
|
||||
if (!empty($data['comment'])) {
|
||||
$comment .= $data['comment'] . "\n\n";
|
||||
}
|
||||
if (!empty($data['booking_date'])) {
|
||||
$comment .= 'Buchungsdatum: ' . $data['booking_date'] . "\n\n";
|
||||
}
|
||||
if (!empty($data['incoming_date'])) {
|
||||
$comment .= 'Zugang: ' . $data['incoming_date'] . "\n\n";
|
||||
}
|
||||
if (!empty($data['outcoming_date_date'])) {
|
||||
$comment .= 'Ausgang: ' . $data['outcoming_date_date'] . "\n\n";
|
||||
}
|
||||
if (!empty($data['reference_id'])) {
|
||||
$comment .= 'Beleg-Nr.: ' . $data['reference_id'] . "\n\n";
|
||||
}
|
||||
$part->setComment($comment);
|
||||
|
||||
return $part;
|
||||
}
|
||||
|
||||
protected function getStorageLocation(string $name)
|
||||
{
|
||||
$repo = $this->entityManager->getRepository(Storelocation::class);
|
||||
$existing = $repo->findOneBy(['name' => $name]);
|
||||
if ($existing) {
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$new = (new Storelocation())->setName($name);
|
||||
$this->entityManager->persist($new);
|
||||
if(!$this->dry_run) {
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
protected function getDummyCategory()
|
||||
{
|
||||
return $this->getDummyNamedEntity('Unsortiert', Category::class);
|
||||
}
|
||||
|
||||
protected function getDummySupplier()
|
||||
{
|
||||
return $this->getDummyNamedEntity('Buchwert', Supplier::class);
|
||||
}
|
||||
|
||||
protected function getDummyNamedEntity(string $name, string $class): AbstractNamedDBElement
|
||||
{
|
||||
$repo = $this->entityManager->getRepository($class);
|
||||
$existing = $repo->findOneBy(['name' => $name]);
|
||||
|
||||
if($existing) {
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$new = (new $class)->setName($name);
|
||||
$this->entityManager->persist($new);
|
||||
if(!$this->dry_run) {
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
}
|
|
@ -199,6 +199,9 @@
|
|||
"lcobucci/jwt": {
|
||||
"version": "3.3.1"
|
||||
},
|
||||
"league/csv": {
|
||||
"version": "9.6.2"
|
||||
},
|
||||
"league/html-to-markdown": {
|
||||
"version": "4.8.2"
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue