From b4c610f9be11d3dfe50122614b157b20c304c123 Mon Sep 17 00:00:00 2001 From: Nadim Salloum Date: Fri, 9 Jul 2021 21:04:29 +0300 Subject: [PATCH] change export --- app/Exports/BuyContractsSheet.php | 44 --- app/Exports/ContractsExport.php | 26 -- app/Exports/ContractsSheet.php | 69 ---- app/Exports/Report.php | 91 +++++ app/Exports/SellContractsSheet.php | 51 --- app/Http/Controllers/ReportController.php | 6 +- app/Models/Car.php | 2 +- app/Models/Contact.php | 6 + app/Models/Contract.php | 10 + public/css/app.css | 12 +- public/js/app.js | 343 +++++++++++++------ resources/js/Components/Documents/Upload.vue | 1 - 12 files changed, 341 insertions(+), 320 deletions(-) delete mode 100644 app/Exports/BuyContractsSheet.php delete mode 100644 app/Exports/ContractsExport.php delete mode 100644 app/Exports/ContractsSheet.php create mode 100644 app/Exports/Report.php delete mode 100644 app/Exports/SellContractsSheet.php diff --git a/app/Exports/BuyContractsSheet.php b/app/Exports/BuyContractsSheet.php deleted file mode 100644 index ddce3b5..0000000 --- a/app/Exports/BuyContractsSheet.php +++ /dev/null @@ -1,44 +0,0 @@ -whereYear('date', '=', $this->year)->orderBy('date', 'asc'); - } - - public function headings(): array - { - return [ - ['Ankaufsverträge ' . $this->year], - [ - 'Datum', - 'Auto', - 'Stammnummer', - 'Verkäufer', - 'Betrag', - ] - ]; - } - - public function map($contract): array - { - return [ - $contract->date_formatted, - $contract->car->name, - $contract->car->stammnummer, - $contract->contact->full_title, - $contract->price, - ]; - } - - public function title(): string - { - return 'Ankaufsverträge'; - } -} diff --git a/app/Exports/ContractsExport.php b/app/Exports/ContractsExport.php deleted file mode 100644 index 29a5cba..0000000 --- a/app/Exports/ContractsExport.php +++ /dev/null @@ -1,26 +0,0 @@ -year = $year; - } - - public function sheets(): array - { - $sheets = []; - - $sheets[] = new ContractsSheet($this->year); - $sheets[] = new BuyContractsSheet($this->year); - $sheets[] = new SellContractsSheet($this->year); - - return $sheets; - } -} diff --git a/app/Exports/ContractsSheet.php b/app/Exports/ContractsSheet.php deleted file mode 100644 index 976b854..0000000 --- a/app/Exports/ContractsSheet.php +++ /dev/null @@ -1,69 +0,0 @@ -year = $year; - } - - public function query() - { - return Contract::whereYear('date', '=', $this->year)->orderBy('date', 'asc'); - } - - public function headings(): array - { - return [ - ['Alle Verträge ' . $this->year], - [ - 'Datum', - 'Vertragsart', - 'Auto', - 'Stammnummer', - 'Kontakt', - 'Betrag', - ] - ]; - } - - public function map($contract): array - { - return [ - $contract->date_formatted, - $contract->type_formatted, - $contract->car->name, - $contract->car->stammnummer, - $contract->contact->full_title, - $contract->price, - ]; - } - - public function title(): string - { - return 'Alle Verträge'; - } - - public function styles(Worksheet $sheet) - { - return [ - 1 => ['font' => ['bold' => true]], - 2 => ['font' => ['bold' => true]], - ]; - } -} diff --git a/app/Exports/Report.php b/app/Exports/Report.php new file mode 100644 index 0000000..4b9b234 --- /dev/null +++ b/app/Exports/Report.php @@ -0,0 +1,91 @@ +year = $year; + } + + public function headings(): array + { + return [ + 'Bezeichnung', + 'Stammnummer', + 'Einkaufsdatum', + 'Einkaufpreis', + 'Verkäufer', + 'Verkaufsdatum', + 'Verkaufspreis', + 'Käufer', + 'Lagerbestand', + ]; + } + + public function collection() + { + $cars = Contract::with('car')->soldByYear($this->year)->get()->pluck('car'); + $cars = $cars->merge(Car::unsoldOnly()->get())->unique()->map(function ($car) { + $bcontract = $car->latestBuyContract(); + $scontract = $car->latestSellContract(); + if (!$car->isSold()) { + $scontract = null; + } + + return [ + 'title' => $car->name_with_year, + 'stammnummer' => $car->stammnummer, + 'buy_date' => $bcontract ? Date::dateTimeToExcel(Carbon::parse($bcontract->date)) : null, + 'buy_price' => $bcontract ? $bcontract->price_for_excel : null, + 'seller' => $bcontract ? $bcontract->contact->full_title_with_address : null, + 'sell_date' => $scontract ? Date::dateTimeToExcel(Carbon::parse($scontract->date)) : null, + 'sell_price' => $scontract ? $scontract->price_for_excel : null, + 'buyer' => $scontract ? $scontract->contact->full_title_with_address : null, + 'lager' => !$scontract && $bcontract ? $bcontract->price_for_excel : null, + ]; + })->sortBy('buy_date'); + + return $cars; + } + + public function styles(Worksheet $sheet) + { + return [ + 1 => ['font' => ['bold' => true]], + ]; + } + + public function columnFormats(): array + { + return [ + 'C' => NumberFormat::FORMAT_DATE_DDMMYYYY, + 'D' => NumberFormat::FORMAT_NUMBER_00, + 'F' => NumberFormat::FORMAT_DATE_DDMMYYYY, + 'G' => NumberFormat::FORMAT_NUMBER_00, + 'I' => NumberFormat::FORMAT_NUMBER_00, + ]; + } + + public function title(): string + { + return 'Wagenhandelbuch ' . $this->year; + } +} diff --git a/app/Exports/SellContractsSheet.php b/app/Exports/SellContractsSheet.php deleted file mode 100644 index 30982bd..0000000 --- a/app/Exports/SellContractsSheet.php +++ /dev/null @@ -1,51 +0,0 @@ -whereYear('date', '=', $this->year)->orderBy('date', 'asc'); - } - - public function headings(): array - { - return [ - ['Verkaufsverträge ' . $this->year], - [ - 'Datum', - 'Auto', - 'Stammnummer', - 'Käufer', - 'Versicherung', - 'Betrag', - 'Eingezahlt', - 'Noch offen' - ] - ]; - } - - public function map($contract): array - { - return [ - $contract->date_formatted, - $contract->car->name, - $contract->car->stammnummer, - $contract->contact->full_title, - $contract->insurance_type_formatted, - $contract->price, - $contract->paid, - $contract->left_to_pay, - ]; - } - - public function title(): string - { - return 'Verkaufsverträge'; - } -} diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index d3cac25..d8eb864 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -3,10 +3,8 @@ namespace App\Http\Controllers; use Inertia\Inertia; -use App\Models\Contract; use Illuminate\Http\Request; -use App\Exports\ContractsExport; -use Illuminate\Support\Facades\Redirect; +use App\Exports\Report; use Maatwebsite\Excel\Facades\Excel as Excel; class ReportController extends Controller @@ -19,6 +17,6 @@ class ReportController extends Controller public function print(Request $request) { $year = (int)$request->get('year'); - return Excel::download(new ContractsExport($year), 'Jahresbericht-' . $year .'.xlsx'); + return Excel::download(new Report($year), 'Wagenhandelbuch-' . $year .'.xlsx'); } } diff --git a/app/Models/Car.php b/app/Models/Car.php index 5b90d5e..317d0c6 100644 --- a/app/Models/Car.php +++ b/app/Models/Car.php @@ -101,7 +101,7 @@ class Car extends Model public function isSold() { - return $this->buyContracts()->count() >= 1 && $this->buyContracts()->count() <= $this->sellContracts()->count(); + return $this->buyContracts()->count() <= $this->sellContracts()->count(); } public function contracts() diff --git a/app/Models/Contact.php b/app/Models/Contact.php index ab929d0..3d87bb4 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -63,6 +63,12 @@ class Contact extends Model return implode(', ', array_filter([$this->company, $this->name])); } + + public function getFullTitleWithAddressAttribute() + { + return implode(', ', array_filter([$this->full_title, $this->address, $this->full_city])); + } + public function getFullCityAttribute() { return $this->zip . ' ' . $this->city; diff --git a/app/Models/Contract.php b/app/Models/Contract.php index 5ce5968..67d9eac 100644 --- a/app/Models/Contract.php +++ b/app/Models/Contract.php @@ -40,6 +40,11 @@ class Contract extends Model return Money::CHF($price); } + public function getPriceForExcelAttribute() + { + return $this->price->format(null, null, 0); + } + public function getPaidAttribute() { @@ -129,6 +134,11 @@ class Contract extends Model $query->sellContracts()->thisYear(); } + public function scopeSoldByYear($query, $year) + { + $query->sellContracts()->whereYear('date', $year); + } + public function scopeBoughtThisYear($query) { $query->buyContracts()->thisYear(); diff --git a/public/css/app.css b/public/css/app.css index 81a4ecb..3c717e2 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -1,4 +1,4 @@ -/*! tailwindcss v2.2.2 | MIT License | https://tailwindcss.com */ +/*! tailwindcss v2.2.4 | MIT License | https://tailwindcss.com */ /*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */ @@ -332,16 +332,6 @@ button { background-image: none; } -/** - * Work around a Firefox/IE bug where the transparent `button` background - * results in a loss of the default `button` focus styles. - */ - -button:focus { - outline: 1px dotted; - outline: 5px auto -webkit-focus-ring-color; -} - fieldset { margin: 0; padding: 0; diff --git a/public/js/app.js b/public/js/app.js index 5e011dc..f0e269c 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -502,18 +502,59 @@ function isCoreComponent(tag) { } const nonIdentifierRE = /^\d|[^\$\w]/; const isSimpleIdentifier = (name) => !nonIdentifierRE.test(name); -const memberExpRE = /^[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*(?:\s*\.\s*[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*|\[(.+)\])*$/; +const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/; +const validIdentCharRE = /[\.\w$\xA0-\uFFFF]/; +const whitespaceRE = /\s+[.[]\s*|\s*[.[]\s+/g; +/** + * Simple lexer to check if an expression is a member expression. This is + * lax and only checks validity at the root level (i.e. does not validate exps + * inside square brackets), but it's ok since these are only used on template + * expressions and false positives are invalid expressions in the first place. + */ const isMemberExpression = (path) => { - if (!path) - return false; - const matched = memberExpRE.exec(path.trim()); - if (!matched) - return false; - if (!matched[1]) - return true; - if (!/[\[\]]/.test(matched[1])) - return true; - return isMemberExpression(matched[1].trim()); + // remove whitespaces around . or [ first + path = path.trim().replace(whitespaceRE, s => s.trim()); + let state = 0 /* inMemberExp */; + let prevState = 0 /* inMemberExp */; + let currentOpenBracketCount = 0; + let currentStringType = null; + for (let i = 0; i < path.length; i++) { + const char = path.charAt(i); + switch (state) { + case 0 /* inMemberExp */: + if (char === '[') { + prevState = state; + state = 1 /* inBrackets */; + currentOpenBracketCount++; + } + else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) { + return false; + } + break; + case 1 /* inBrackets */: + if (char === `'` || char === `"` || char === '`') { + prevState = state; + state = 2 /* inString */; + currentStringType = char; + } + else if (char === `[`) { + currentOpenBracketCount++; + } + else if (char === `]`) { + if (!--currentOpenBracketCount) { + state = prevState; + } + } + break; + case 2 /* inString */: + if (char === currentStringType) { + state = prevState; + currentStringType = null; + } + break; + } + } + return !currentOpenBracketCount; }; function getInnerRange(loc, offset, length) { const source = loc.source.substr(offset, length); @@ -1194,41 +1235,17 @@ function parseTag(context, type, parent) { } } let tagType = 0 /* ELEMENT */; - const options = context.options; - if (!context.inVPre && !options.isCustomElement(tag)) { - const hasVIs = props.some(p => { - if (p.name !== 'is') - return; - // v-is="xxx" (TODO: deprecate) - if (p.type === 7 /* DIRECTIVE */) { - return true; - } - // is="vue:xxx" - if (p.value && p.value.content.startsWith('vue:')) { - return true; - } - // in compat mode, any is usage is considered a component - if (checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) { - return true; - } - }); - if (options.isNativeTag && !hasVIs) { - if (!options.isNativeTag(tag)) - tagType = 1 /* COMPONENT */; - } - else if (hasVIs || - isCoreComponent(tag) || - (options.isBuiltInComponent && options.isBuiltInComponent(tag)) || - /^[A-Z]/.test(tag) || - tag === 'component') { - tagType = 1 /* COMPONENT */; - } + if (!context.inVPre) { if (tag === 'slot') { tagType = 2 /* SLOT */; } - else if (tag === 'template' && - props.some(p => p.type === 7 /* DIRECTIVE */ && isSpecialTemplateDirective(p.name))) { - tagType = 3 /* TEMPLATE */; + else if (tag === 'template') { + if (props.some(p => p.type === 7 /* DIRECTIVE */ && isSpecialTemplateDirective(p.name))) { + tagType = 3 /* TEMPLATE */; + } + } + else if (isComponent(tag, props, context)) { + tagType = 1 /* COMPONENT */; } } return { @@ -1243,6 +1260,49 @@ function parseTag(context, type, parent) { codegenNode: undefined // to be created during transform phase }; } +function isComponent(tag, props, context) { + const options = context.options; + if (options.isCustomElement(tag)) { + return false; + } + if (tag === 'component' || + /^[A-Z]/.test(tag) || + isCoreComponent(tag) || + (options.isBuiltInComponent && options.isBuiltInComponent(tag)) || + (options.isNativeTag && !options.isNativeTag(tag))) { + return true; + } + // at this point the tag should be a native tag, but check for potential "is" + // casting + for (let i = 0; i < props.length; i++) { + const p = props[i]; + if (p.type === 6 /* ATTRIBUTE */) { + if (p.name === 'is' && p.value) { + if (p.value.content.startsWith('vue:')) { + return true; + } + else if (checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) { + return true; + } + } + } + else { + // directive + // v-is (TODO Deprecate) + if (p.name === 'is') { + return true; + } + else if ( + // :is on plain element - only treat as component in compat mode + p.name === 'bind' && + isBindKey(p.arg, 'is') && + true && + checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) { + return true; + } + } + } +} function parseAttributes(context, type) { const props = []; const attributeNames = new Set(); @@ -3564,16 +3624,10 @@ function resolveComponentType(node, context, ssr = false) { let { tag } = node; // 1. dynamic component const isExplicitDynamic = isComponentTag(tag); - const isProp = findProp(node, 'is') || (!isExplicitDynamic && findDir(node, 'is')); + const isProp = findProp(node, 'is'); if (isProp) { - if (!isExplicitDynamic && isProp.type === 6 /* ATTRIBUTE */) { - //