April 2026 waren Wirtschaft Feedback

This commit is contained in:
Kevin Adametz 2026-04-10 17:14:38 +02:00
parent 02f2a4c23e
commit 9ce711d6b2
167 changed files with 25278 additions and 8518 deletions

27
.cursor/mcp.json Normal file
View file

@ -0,0 +1,27 @@
{
"mcpServers": {
"laravel-boost": {
"command": "php",
"args": [
"artisan",
"boost:mcp"
]
},
"context7": {
"command": "npx",
"args": [
"-y",
"@upstash/context7-mcp",
"--api-key",
"ctx7sk-119cd4ab-8983-4229-8702-e84c59c34fc9"
]
},
"sequential-thinking": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-sequential-thinking"
]
}
}
}

View file

@ -0,0 +1,248 @@
---
alwaysApply: true
---
<laravel-boost-guidelines>
=== foundation rules ===
# Laravel Boost Guidelines
The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications.
## Foundational Context
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
- php - 8.4.1
- laravel/framework (LARAVEL) - v11
- laravel/prompts (PROMPTS) - v0
- laravel/pint (PINT) - v1
- pestphp/pest (PEST) - v2
## Conventions
- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming.
- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
- Check for existing components to reuse before writing a new one.
## Verification Scripts
- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important.
## Application Structure & Architecture
- Stick to existing directory structure - don't create new base folders without approval.
- Do not change the application's dependencies without approval.
## Frontend Bundling
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them.
## Replies
- Be concise in your explanations - focus on what's important rather than explaining obvious details.
## Documentation Files
- You must only create documentation files if explicitly requested by the user.
=== boost rules ===
## Laravel Boost
- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them.
## Artisan
- Use the `list-artisan-commands` tool when you need to call an Artisan command to double check the available parameters.
## URLs
- Whenever you share a project URL with the user you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain / IP, and port.
## Tinker / Debugging
- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly.
- Use the `database-query` tool when you only need to read from the database.
## Reading Browser Logs With the `browser-logs` Tool
- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost.
- Only recent browser logs will be useful - ignore old logs.
## Searching Documentation (Critically Important)
- Boost comes with a powerful `search-docs` tool you should use before any other approaches. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation specific for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages.
- The 'search-docs' tool is perfect for all Laravel related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc.
- You must use this tool to search for Laravel-ecosystem documentation before falling back to other approaches.
- Search the documentation before making code changes to ensure we are taking the correct approach.
- Use multiple, broad, simple, topic based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`.
- Do not add package names to queries - package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`.
### Available Search Syntax
- You can and should pass multiple queries at once. The most relevant results will be returned first.
1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'
2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit"
3. Quoted Phrases (Exact Position) - query="infinite scroll" - Words must be adjacent and in that order
4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit"
5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms
=== php rules ===
## PHP
- Always use curly braces for control structures, even if it has one line.
### Constructors
- Use PHP 8 constructor property promotion in `__construct()`.
- <code-snippet>public function __construct(public GitHub $github) { }</code-snippet>
- Do not allow empty `__construct()` methods with zero parameters.
### Type Declarations
- Always use explicit return type declarations for methods and functions.
- Use appropriate PHP type hints for method parameters.
<code-snippet name="Explicit Return Types and Method Params" lang="php">
protected function isAccessible(User $user, ?string $path = null): bool
{
...
}
</code-snippet>
## Comments
- Prefer PHPDoc blocks over comments. Never use comments within the code itself unless there is something _very_ complex going on.
## PHPDoc Blocks
- Add useful array shape type definitions for arrays when appropriate.
## Enums
- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`.
=== laravel/core rules ===
## Do Things the Laravel Way
- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
- If you're creating a generic PHP class, use `artisan make:class`.
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
### Database
- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins.
- Use Eloquent models and relationships before suggesting raw database queries
- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them.
- Generate code that prevents N+1 query problems by using eager loading.
- Use Laravel's query builder for very complex database operations.
### Model Creation
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`.
### APIs & Eloquent Resources
- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
### Controllers & Validation
- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages.
- Check sibling Form Requests to see if the application uses array or string based validation rules.
### Queues
- Use queued jobs for time-consuming operations with the `ShouldQueue` interface.
### Authentication & Authorization
- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).
### URL Generation
- When generating links to other pages, prefer named routes and the `route()` function.
### Configuration
- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`.
### Testing
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
- When creating tests, make use of `php artisan make:test [options] <name>` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
### Vite Error
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`.
=== laravel/v11 rules ===
## Laravel 11
- Use the `search-docs` tool to get version specific documentation.
- This project upgraded from Laravel 10 without migrating to the new streamlined Laravel 11 file structure.
- This is **perfectly fine** and recommended by Laravel. Follow the existing structure from Laravel 10. We do not to need migrate to the Laravel 11 structure unless the user explicitly requests that.
### Laravel 10 Structure
- Middleware typically live in `app/Http/Middleware/` and service providers in `app/Providers/`.
- There is no `bootstrap/app.php` application configuration in a Laravel 10 structure:
- Middleware registration is in `app/Http/Kernel.php`
- Exception handling is in `app/Exceptions/Handler.php`
- Console commands and schedule registration is in `app/Console/Kernel.php`
- Rate limits likely exist in `RouteServiceProvider` or `app/Http/Kernel.php`
### Database
- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost.
- Laravel 11 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`.
### Models
- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models.
### New Artisan Commands
- List Artisan commands using Boost's MCP tool, if available. New commands available in Laravel 11:
- `php artisan make:enum`
- `php artisan make:class`
- `php artisan make:interface`
=== pint/core rules ===
## Laravel Pint Code Formatter
- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues.
=== pest/core rules ===
## Pest
### Testing
- If you need to verify a feature is working, write or update a Unit / Feature test.
### Pest Tests
- All tests must be written using Pest. Use `php artisan make:test --pest <name>`.
- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application.
- Tests should test all of the happy paths, failure paths, and weird paths.
- Tests live in the `tests/Feature` and `tests/Unit` directories.
- Pest tests look and behave like this:
<code-snippet name="Basic Pest Test Example" lang="php">
it('is true', function () {
expect(true)->toBeTrue();
});
</code-snippet>
### Running Tests
- Run the minimal number of tests using an appropriate filter before finalizing code edits.
- To run all tests: `php artisan test`.
- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`.
- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file).
- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing.
### Pest Assertions
- When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.:
<code-snippet name="Pest Example Asserting postJson Response" lang="php">
it('returns all', function () {
$response = $this->postJson('/api/docs', []);
$response->assertSuccessful();
});
</code-snippet>
### Mocking
- Mocking can be very helpful when appropriate.
- When mocking, you can use the `Pest\Laravel\mock` Pest function, but always import it via `use function Pest\Laravel\mock;` before using it. Alternatively, you can use `$this->mock()` if existing tests do.
- You can also create partial mocks using the same import or self method.
### Datasets
- Use datasets in Pest to simplify tests which have a lot of duplicated data. This is often the case when testing validation rules, so consider going with this solution when writing tests for validation rules.
<code-snippet name="Pest Dataset Example" lang="php">
it('has emails', function (string $email) {
expect($email)->not->toBeEmpty();
})->with([
'james' => 'james@laravel.com',
'taylor' => 'taylor@laravel.com',
]);
</code-snippet>
</laravel-boost-guidelines>

View file

@ -0,0 +1,44 @@
{
"name": "Partner Grüne Seele (Dev Container)",
"dockerComposeFile": [
"../docker-compose.yml"
],
"service": "laravel.test",
"workspaceFolder": "/var/www/html",
"remoteUser": "sail",
"features": {},
"customizations": {
"vscode": {
"extensions": [
"bmewburn.vscode-intelephense-client",
"onecentlin.laravel-blade",
"shufo.vscode-blade-formatter",
"bradlc.vscode-tailwindcss",
"Anthropic.claude-code",
"onecentlin.laravel-extension-pack"
]
}
},
// WICHTIG: Nur noch den Haupt-Container starten!
"runServices": [
"laravel.test"
],
"containerEnv": {
"WWWUSER": "501",
"WWWGROUP": "20",
"LARAVEL_SAIL": "1"
},
"mounts": [
"source=${localWorkspaceFolder},target=/var/www/html,type=bind,consistency=cached"
],
// WICHTIG: Nur noch den Vite-Port weiterleiten
"forwardPorts": [
5179
],
"portsAttributes": {
"5179": {
"label": "Vite Dev Server",
"onAutoForward": "notify"
}
}
}

31
.env
View file

@ -27,15 +27,15 @@ LOG_CHANNEL=stack
LOG_LEVEL=debug LOG_LEVEL=debug
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=192.168.1.8 DB_HOST=mysql
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=grueneseele DB_DATABASE=partner_gruene_seele
DB_USERNAME=kadmin DB_USERNAME=root
DB_PASSWORD=KT32vQ7ix DB_PASSWORD=password
#DB_CONNECTION=mysql #DB_CONNECTION=mysql
#DB_HOST=localhost #DB_HOST=mysql
#DB_PORT=3306 #DB_PORT=3306
#DB_DATABASE=web28_db4 #DB_DATABASE=web28_db4
#DB_USERNAME=web28_4 #DB_USERNAME=web28_4
@ -51,16 +51,21 @@ MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1 REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6384
#REDIS_PORT=6379
MAIL_DRIVER=smtp MAIL_DRIVER=smtp
MAIL_HOST=w017e534.kasserver.com MAIL_HOST=mailpit
MAIL_PORT=587 MAIL_PORT=1029
MAIL_USERNAME=m0496c96
MAIL_PASSWORD=mZtVp7WQcs6DC3hf #MAIL_DRIVER=smtp
MAIL_ENCRYPTION=null #MAIL_HOST=w017e534.kasserver.com
MAIL_FROM_ADDRESS=dev@adametz.media #MAIL_PORT=587
MAIL_FROM_NAME="DEV Grüne Seele" #MAIL_USERNAME=m0496c96
#MAIL_PASSWORD=mZtVp7WQcs6DC3hf
#MAIL_ENCRYPTION=null
#MAIL_FROM_ADDRESS=dev@adametz.media
#MAIL_FROM_NAME="DEV Grüne Seele"
#MAIL_MAILER=smtp #MAIL_MAILER=smtp
#MAIL_HOST=s182.goserver.host #MAIL_HOST=s182.goserver.host

27
.mcp.json Normal file
View file

@ -0,0 +1,27 @@
{
"mcpServers": {
"laravel-boost": {
"command": "php",
"args": [
"artisan",
"boost:mcp"
]
},
"context7": {
"command": "npx",
"args": [
"-y",
"@upstash/context7-mcp",
"--api-key",
"ctx7sk-119cd4ab-8983-4229-8702-e84c59c34fc9"
]
},
"sequential-thinking": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-sequential-thinking"
]
}
}
}

File diff suppressed because it is too large Load diff

85
CLAUDE.md Normal file
View file

@ -0,0 +1,85 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
German-language MLM/direct-sales e-commerce platform for organic/natural products ("Gruene Seele" / Green Soul). Partners/distributors get personal whitelabel shops, earn commissions through a multi-level hierarchy, and manage orders, invoices, and promotions.
**Stack:** PHP 8.4, Laravel 11 (using Laravel 10 directory structure), Bootstrap 4, jQuery, Laravel Mix (webpack), MySQL, Laravel Passport (API auth).
## Common Commands
```bash
# Tests (Pest v2, uses SQLite in-memory)
php artisan test # run all tests
php artisan test tests/Feature/ExampleTest.php # run single file
php artisan test --filter=testName # filter by name
# Code formatting (Laravel Pint)
vendor/bin/pint --dirty # format changed files only
vendor/bin/pint # format all files
composer format # alias for pint
# Frontend (Laravel Mix, NOT Vite)
npm run dev # development build
npm run prod # production build
npm run watch # watch mode
# Artisan - always pass --no-interaction
php artisan make:model Name --no-interaction
php artisan make:test --pest Name --no-interaction
```
## Architecture
### Multi-Domain Routing (`routes/web.php`)
Three separate domain groups, each with distinct middleware:
- **Main domain** (`config('app.domain')`) - admin panel + user dashboard
- **Promo domain** (`config('app.promo_domain')`) - public promotion/microsite pages
- **Shop domain** (`config('app.shop_domain')`) - public whitelabel shop
### Admin Access Levels (middleware in `app/Http/Middleware/`)
- `CopyReader` - admin >= 1 (product/content management)
- `Admin` - admin >= 7 (sales, customers, promotions)
- `SuperAdmin` - admin >= 8 (users, shipping, settings)
- `SysAdmin` - admin >= 9 (system tools, imports)
### Key Service Layer
- **`app/Services/Yard.php`** + `app/Services/Yard/` - Custom shopping cart (extends forked Gloudemans Cart in `packages/digital-bird/shoppingcart/`). Handles shipping, tax, margins, commissions.
- **`app/Services/Invoice.php`** - Invoice and cancellation invoice PDF generation (uses DomPDF)
- **`app/Services/PaymentReminderService.php`** - Payment reminder logic with status progression
- **`app/Services/Credit.php`** - User credit/balance management
- **`app/Services/Stats/`** - Sales statistics
### Repository Pattern
Business logic uses repositories in `app/Repositories/` (e.g., `CheckoutRepository`, `InvoiceRepository`, `CustomerRepository`). Controllers delegate to repositories and services.
### Local Packages
`packages/digital-bird/shoppingcart/` - Forked `gloudemans/shoppingcart`, autoloaded via composer PSR-4 as `Gloudemans\Shoppingcart\`.
### Cron Jobs (`app/Console/Kernel.php`)
- `payments:accounts` - Checks user account expiry, sends reminders (statuses 31/33/34/35), deactivates expired accounts
- `payments:reminders` - Sends payment reminders for open invoices
### API (`routes/api.php`)
No versioning. Passport-authenticated endpoints for WordPress integration (`/api/wp/*`) and auth (`/api/auth/*`).
### Global Helpers
`app/helpers.php` (autoloaded) - URL helpers, formatting delegates to `App\Services\Util`.
### PDF Generation
- `app/Libraries/InvoicePDF.php`, `ContractPDF.php` - FPDF/FPDI based
- `app/Services/Invoice.php` - DomPDF based (Blade templates in `resources/views/pdf/`)
## Important Conventions
- This project uses **Laravel 10 directory structure** on Laravel 11. Do NOT migrate to Laravel 11 structure.
- Middleware registration: `app/Http/Kernel.php`
- Exception handling: `app/Exceptions/Handler.php`
- Schedule: `app/Console/Kernel.php`
- Default auth guard is `user` (not `web`), configured in `config/auth.php`
- User model is `App\User` (not `App\Models\User`)
- Views are Blade templates with Bootstrap 4 + jQuery DataTables
- Always run `vendor/bin/pint --dirty` before finalizing changes
- Countries supported: DE, FR, CH, NL (with reverse charge VAT handling)

File diff suppressed because it is too large Load diff

2267
_ide_helper_models.php Normal file

File diff suppressed because it is too large Load diff

View file

@ -33,16 +33,20 @@ class PaymentsReminders extends Command
* @var string * @var string
*/ */
protected $signature = 'payments:reminders'; protected $signature = 'payments:reminders';
protected $description = 'Run Payments Reminders'; protected $description = 'Run Payments Reminders';
private $timeStart; private $timeStart;
private $dev = false; private $dev = false;
private $paymentReminderService; private $paymentReminderService;
private $stats = [ private $stats = [
'total_processed' => 0, 'total_processed' => 0,
'reminders_sent' => 0, 'reminders_sent' => 0,
'errors' => 0, 'errors' => 0,
'skipped' => 0 'skipped' => 0,
]; ];
public function __construct(PaymentReminderService $paymentReminderService) public function __construct(PaymentReminderService $paymentReminderService)
@ -68,7 +72,7 @@ class PaymentsReminders extends Command
$executionTime = round(microtime(true) - $this->timeStart, 2); $executionTime = round(microtime(true) - $this->timeStart, 2);
$this->info("\n=== PAYMENT REMINDERS ABGESCHLOSSEN ==="); $this->info("\n=== PAYMENT REMINDERS ABGESCHLOSSEN ===");
$this->info("Ausführungszeit: {$executionTime} Sekunden"); $this->info("Ausführungszeit: {$executionTime} Sekunden");
$this->info("Statistiken:"); $this->info('Statistiken:');
$this->info(" - Gesamt verarbeitet: {$this->stats['total_processed']}"); $this->info(" - Gesamt verarbeitet: {$this->stats['total_processed']}");
$this->info(" - Erinnerungen gesendet: {$this->stats['reminders_sent']}"); $this->info(" - Erinnerungen gesendet: {$this->stats['reminders_sent']}");
$this->info(" - Fehler: {$this->stats['errors']}"); $this->info(" - Fehler: {$this->stats['errors']}");
@ -76,15 +80,17 @@ class PaymentsReminders extends Command
\Log::info('PaymentsReminders Command completed successfully', [ \Log::info('PaymentsReminders Command completed successfully', [
'execution_time' => $executionTime, 'execution_time' => $executionTime,
'stats' => $this->stats 'stats' => $this->stats,
]); ]);
return 0; return 0;
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::error('PaymentsReminders Command failed', [ \Log::error('PaymentsReminders Command failed', [
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'trace' => $e->getTraceAsString() 'trace' => $e->getTraceAsString(),
]); ]);
$this->error('Command failed: '.$e->getMessage()); $this->error('Command failed: '.$e->getMessage());
return 1; return 1;
} }
} }
@ -98,17 +104,18 @@ class PaymentsReminders extends Command
// Hole alle aktiven PaymentReminder und gruppiere sie nach clearingtype // Hole alle aktiven PaymentReminder und gruppiere sie nach clearingtype
$payment_reminders = PaymentReminder::where('active', true)->get(); $payment_reminders = PaymentReminder::where('active', true)->get();
$this->info("Gefundene aktive PaymentReminder: " . $payment_reminders->count()); $this->info('Gefundene aktive PaymentReminder: '.$payment_reminders->count());
if ($payment_reminders->isEmpty()) { if ($payment_reminders->isEmpty()) {
$this->warn("Keine aktiven PaymentReminder gefunden!"); $this->warn('Keine aktiven PaymentReminder gefunden!');
return; return;
} }
// Finde für jeden clearingtype das kleinste Intervall (in Tagen) // Finde für jeden clearingtype das kleinste Intervall (in Tagen)
$intervals = $this->paymentReminderService->getActiveIntervals(); $intervals = $this->paymentReminderService->getActiveIntervals();
$this->info("Gefundene clearingtypes mit kleinsten Intervallen:"); $this->info('Gefundene clearingtypes mit kleinsten Intervallen:');
foreach ($intervals as $clearingtype => $interval) { foreach ($intervals as $clearingtype => $interval) {
$this->line(" - {$clearingtype}: {$interval} Tage"); $this->line(" - {$clearingtype}: {$interval} Tage");
} }
@ -118,7 +125,7 @@ class PaymentsReminders extends Command
$this->info("\n--- Verarbeite clearingtype: {$clearingtype} mit Intervall: {$interval} Tage ---"); $this->info("\n--- Verarbeite clearingtype: {$clearingtype} mit Intervall: {$interval} Tage ---");
$date = Carbon::now()->subDays($interval); $date = Carbon::now()->subDays($interval);
$this->line("Suche Zahlungen vor: " . $date->format('d.m.Y H:i:s')); $this->line('Suche Zahlungen vor: '.$date->format('d.m.Y H:i:s'));
// Hole nur die neueste ShoppingPayment pro shopping_order_id // Hole nur die neueste ShoppingPayment pro shopping_order_id
$shopping_payments = $this->paymentReminderService->getOpenPaymentsForClearingType($clearingtype, $interval); $shopping_payments = $this->paymentReminderService->getOpenPaymentsForClearingType($clearingtype, $interval);
@ -127,10 +134,13 @@ class PaymentsReminders extends Command
if ($shopping_payments->isEmpty()) { if ($shopping_payments->isEmpty()) {
$this->line("Keine Zahlungen für {$clearingtype} gefunden."); $this->line("Keine Zahlungen für {$clearingtype} gefunden.");
continue; continue;
} }
// Verarbeite jede Zahlung // Verarbeite jede Zahlung
$this->line('--- START processPayment VERARBEITUNG');
foreach ($shopping_payments as $shopping_payment) { foreach ($shopping_payments as $shopping_payment) {
$this->processPayment($shopping_payment, $clearingtype); $this->processPayment($shopping_payment, $clearingtype);
} }
@ -151,18 +161,17 @@ class PaymentsReminders extends Command
if ($this->shouldSendReminder($shopping_payment, $clearingtype)) { if ($this->shouldSendReminder($shopping_payment, $clearingtype)) {
$this->sendReminderForPayment($shopping_payment); $this->sendReminderForPayment($shopping_payment);
} else { } else {
$this->line(" ⏭️ Übersprungen - Keine Erinnerung fällig"); $this->line('Übersprungen - Keine Erinnerung fällig');
$this->stats['skipped']++; $this->stats['skipped']++;
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error("Fehler bei Order ID {$shopping_payment->shopping_order_id}: " . $e->getMessage()); $this->error("Fehler bei Order ID {$shopping_payment->shopping_order_id}: ".$e->getMessage());
$this->stats['errors']++; $this->stats['errors']++;
\Log::error('Error processing payment reminder', [ \Log::error('Error processing payment reminder', [
'order_id' => $shopping_payment->shopping_order_id, 'order_id' => $shopping_payment->shopping_order_id,
'payment_id' => $shopping_payment->id, 'payment_id' => $shopping_payment->id,
'error' => $e->getMessage() 'error' => $e->getMessage(),
]); ]);
} }
} }
@ -179,33 +188,52 @@ class PaymentsReminders extends Command
// Hole alle aktiven Erinnerungen für diesen Clearingtype // Hole alle aktiven Erinnerungen für diesen Clearingtype
$payment_reminders = PaymentReminder::where('active', true) $payment_reminders = PaymentReminder::where('active', true)
->where('clearingtype', $clearingtype) ->where('clearingtype', $clearingtype)
->orderBy('interval', 'asc') ->orderBy('interval', 'asc') // von kein nach gross
->get(); ->get();
if ($payment_reminders->isEmpty()) { if ($payment_reminders->isEmpty()) {
$this->line('shouldSendReminder - keine PaymentReminders');
return false; return false;
} }
// Wenn alle Erinnerungen bereits gesendet wurden // Wenn alle Erinnerungen bereits gesendet wurden
if ($shopping_payment->reminder >= $payment_reminders->count()) { if ($shopping_payment->reminder >= $payment_reminders->count()) {
$this->line('shouldSendReminder - alle Erinnerungen wurden bereits gesendet');
return false; return false;
} }
// Hole die nächste Erinnerung $next_reminder = isset($payment_reminders[$shopping_payment->reminder]) ? $payment_reminders[$shopping_payment->reminder] : null;
$next_reminder = $payment_reminders[$shopping_payment->reminder]; if ($next_reminder == null) {
$next_reminder = isset($payment_reminders[0]) ? $payment_reminders[0] : null;
if ($next_reminder == null) {
$this->line('shouldSendReminder - keine Erinnerung gefunden');
return false;
}
}
$this->line("shouldSendReminder - nächste Erinnerung: {$next_reminder->interval} Tage");
// Wenn noch keine Erinnerung gesendet wurde, prüfe das erste Intervall // Wenn noch keine Erinnerung gesendet wurde, prüfe das erste Intervall
if ($shopping_payment->reminder == 0) { if ($shopping_payment->reminder == null || $shopping_payment->reminder == 0) {
$daysSinceOrder = $shopping_payment->created_at->diffInDays(now()); $daysSinceOrder = $shopping_payment->created_at->diffInDays(now());
$this->line("shouldSendReminder - no reminder send first intervall - Tage seit Bestellung: {$daysSinceOrder} : Next intervall: {$next_reminder->interval}");
return $daysSinceOrder >= $next_reminder->interval; return $daysSinceOrder >= $next_reminder->interval;
} }
// Hole die nächste Erinnerung
// Wenn bereits Erinnerungen gesendet wurden, prüfe das nächste Intervall // Wenn bereits Erinnerungen gesendet wurden, prüfe das nächste Intervall
if ($shopping_payment->reminder_date) { if ($shopping_payment->reminder_date) {
$current_reminder = $payment_reminders[$shopping_payment->reminder - 1]; $current_reminder = $payment_reminders[$shopping_payment->reminder - 1];
$this->line("shouldSendReminder - letzte Erinnerung: {$current_reminder->interval} Tage");
$interval_difference = $next_reminder->interval - $current_reminder->interval; $interval_difference = $next_reminder->interval - $current_reminder->interval;
$next_reminder_date = Carbon::parse($shopping_payment->reminder_date)->addDays($interval_difference); $next_reminder_date = Carbon::parse($shopping_payment->reminder_date)->addDays($interval_difference);
$this->line("shouldSendReminder - next reminder date: {$next_reminder_date->format('d.m.Y H:i:s')}");
return now()->gte($next_reminder_date); return now()->gte($next_reminder_date);
} }
@ -218,12 +246,12 @@ class PaymentsReminders extends Command
private function sendReminderForPayment($shopping_payment) private function sendReminderForPayment($shopping_payment)
{ {
try { try {
$this->line(" 📧 Sende Erinnerung..."); $this->line('Sende Erinnerung...');
$result = $this->paymentReminderService->sendReminder($shopping_payment); $result = $this->paymentReminderService->sendReminder($shopping_payment);
if ($result) { if ($result) {
$this->line(" ✅ Erinnerung erfolgreich gesendet"); $this->line('Erinnerung erfolgreich gesendet');
$this->stats['reminders_sent']++; $this->stats['reminders_sent']++;
// Log für Cron-Job // Log für Cron-Job
@ -231,21 +259,20 @@ class PaymentsReminders extends Command
'order_id' => $shopping_payment->shopping_order_id, 'order_id' => $shopping_payment->shopping_order_id,
'payment_id' => $shopping_payment->id, 'payment_id' => $shopping_payment->id,
'reminder_count' => $shopping_payment->reminder + 1, 'reminder_count' => $shopping_payment->reminder + 1,
'email' => $shopping_payment->shopping_order->shopping_user->billing_email ?? 'N/A' 'email' => $shopping_payment->shopping_order->shopping_user->billing_email ?? 'N/A',
]); ]);
} else { } else {
$this->line(" ⚠️ Keine Erinnerung gesendet (keine weitere Erinnerung verfügbar)"); $this->line('Keine Erinnerung gesendet (keine weitere Erinnerung verfügbar)');
$this->stats['skipped']++; $this->stats['skipped']++;
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error(" ❌ Fehler beim Senden der Erinnerung: " . $e->getMessage()); $this->error('Fehler beim Senden der Erinnerung: '.$e->getMessage());
$this->stats['errors']++; $this->stats['errors']++;
\Log::error('Error sending payment reminder', [ \Log::error('Error sending payment reminder', [
'order_id' => $shopping_payment->shopping_order_id, 'order_id' => $shopping_payment->shopping_order_id,
'payment_id' => $shopping_payment->id, 'payment_id' => $shopping_payment->id,
'error' => $e->getMessage() 'error' => $e->getMessage(),
]); ]);
} }
} }

View file

@ -19,19 +19,19 @@ class Kernel extends ConsoleKernel
/** /**
* Define the application's command schedule. * Define the application's command schedule.
* *
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void * @return void
*/ */
protected function schedule(Schedule $schedule) protected function schedule(Schedule $schedule)
{ {
/*$schedule->command('payments:accounts') if (config('app.debug') == false) {
$schedule->command('payments:accounts')
->sendOutputTo(storage_path('logs/cron.log')) ->sendOutputTo(storage_path('logs/cron.log'))
->appendOutputTo(storage_path('logs/cron-history.log')) ->appendOutputTo(storage_path('logs/cron-history.log'))
->emailOutputOnFailure(config('app.exception_mail')) ->emailOutputOnFailure(config('app.exception_mail'))
->onFailure(function () { ->onFailure(function () {
\Log::error('Payments:accounts command failed'); \Log::error('Payments:accounts command failed');
}); });
*/ }
$schedule->command('payments:reminders') $schedule->command('payments:reminders')
->sendOutputTo(storage_path('logs/cron.log')) ->sendOutputTo(storage_path('logs/cron.log'))

View file

@ -1,21 +1,17 @@
<?php <?php
namespace App\Cron; namespace App\Cron;
use App\User;
use App\Models\UserBusiness;
use App\Services\HTMLHelper;
use App\Models\UserCreditItem;
use App\Repositories\CreditRepository;
use App\Mail\MailCustomMessage; use App\Mail\MailCustomMessage;
use App\Mail\MailVerifyAccount; use App\Mail\MailVerifyAccount;
use App\Models\UserHistory; use App\Models\UserHistory;
use App\Models\UserMessage; use App\Models\UserMessage;
use App\User;
use Carbon; use Carbon;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
class UserCheckPaymentsAccounts class UserCheckPaymentsAccounts
{ {
/*RULES /*RULES
reminders reminders
> 29 renewal_days > set next_m_level to m_level > 29 renewal_days > set next_m_level to m_level
@ -27,7 +23,8 @@ class UserCheckPaymentsAccounts
> 0 deaktiv = reminder_deaktiv //status 35 > 0 deaktiv = reminder_deaktiv //status 35
*/ */
public static function userReminderPayments(User $user, $dev){ public static function userReminderPayments(User $user, $dev)
{
// 35 reminder_deaktiv // 35 reminder_deaktiv
if (! $user->isActiveAccount()) { // payment_account gt now if (! $user->isActiveAccount()) { // payment_account gt now
return self::checkIsReminderSend($user, 35, $dev); return self::checkIsReminderSend($user, 35, $dev);
@ -44,11 +41,12 @@ class UserCheckPaymentsAccounts
if ($user->daysActiveAccount() > config('main.remind_sec_days')) { if ($user->daysActiveAccount() > config('main.remind_sec_days')) {
return self::checkIsReminderSend($user, 31, $dev); return self::checkIsReminderSend($user, 31, $dev);
} }
return 0; return 0;
} }
private static function checkIsReminderSend(User $user, $status, $dev)
private static function checkIsReminderSend(User $user, $status, $dev){ {
$isSend = UserHistory::whereUserId($user->id) $isSend = UserHistory::whereUserId($user->id)
->whereAction('reminder_payments') ->whereAction('reminder_payments')
@ -65,11 +63,12 @@ class UserCheckPaymentsAccounts
$referenz = self::sendReminderMail($user, $status); $referenz = self::sendReminderMail($user, $status);
} }
UserHistory::create(['user_id' => $user->id, 'action' => 'reminder_payments', 'referenz' => $referenz, 'identifier' => $user->payment_account, 'status' => $status]); UserHistory::create(['user_id' => $user->id, 'action' => 'reminder_payments', 'referenz' => $referenz, 'identifier' => $user->payment_account, 'status' => $status]);
return $status;
return $status;
} }
private function sendReminderMail(User $user, $status){ private static function sendReminderMail(User $user, $status)
{
$days = $user->daysActiveAccount(); $days = $user->daysActiveAccount();
if ($days < 0) { if ($days < 0) {
@ -89,11 +88,11 @@ class UserCheckPaymentsAccounts
$message_last = __('reminder.copy_last_'.$status, ['days' => $days, 'datetime' => $datetime, 'price' => $price, 'pay_date' => $pay_date]); $message_last = __('reminder.copy_last_'.$status, ['days' => $days, 'datetime' => $datetime, 'price' => $price, 'pay_date' => $pay_date]);
$button = __('reminder.button_'.$status); $button = __('reminder.button_'.$status);
$message = preg_replace("/[\n\r]/","",$message); $message = preg_replace("/[\n\r]/", '', $message);
$message_last = preg_replace("/[\n\r]/","",$message_last); $message_last = preg_replace("/[\n\r]/", '', $message_last);
$data = [ $data = [
'subject' => __('reminder.subject')." | ID: ".$status, 'subject' => __('reminder.subject').' | ID: '.$status,
'message' => $message, 'message' => $message,
'message_last' => $message_last, 'message_last' => $message_last,
'url' => route('user_membership'), 'url' => route('user_membership'),
@ -106,7 +105,7 @@ class UserCheckPaymentsAccounts
'send_user_id' => $sender->id, 'send_user_id' => $sender->id,
'email' => $user->email, 'email' => $user->email,
'subject' => $data['subject'], 'subject' => $data['subject'],
'message' => $data['message']." ".$data['message_last'], 'message' => $data['message'].' '.$data['message_last'],
]); ]);
try { try {
if ($status >= 34) { if ($status >= 34) {
@ -114,24 +113,23 @@ class UserCheckPaymentsAccounts
} else { } else {
// Mail::to($user->email)->send(new MailCustomMessage($user, $data, $sender, false)); // Mail::to($user->email)->send(new MailCustomMessage($user, $data, $sender, false));
} }
} } catch (\Exception $e) {
catch(\Exception $e){
\Log::channel('cron')->error('Mail Error: '.$e->getMessage()); \Log::channel('cron')->error('Mail Error: '.$e->getMessage());
// Never reached // Never reached
$customer_mail->fail = true; $customer_mail->fail = true;
$customer_mail->error = $e->getMessage(); $customer_mail->error = $e->getMessage();
$customer_mail->save(); $customer_mail->save();
return 0; return 0;
} }
$customer_mail->send = true; $customer_mail->send = true;
$customer_mail->sent_at = now(); $customer_mail->sent_at = now();
$customer_mail->save(); $customer_mail->save();
return 1; return 1;
} }
} }
/*public function checkConfirmation() /*public function checkConfirmation()
{ {
User Register sind in der DB UserRegister, erst bei bestätigung wird es in die User DB übertragen User Register sind in der DB UserRegister, erst bei bestätigung wird es in die User DB übertragen
@ -158,4 +156,3 @@ class UserCheckPaymentsAccounts
} }
return "TOSK"; return "TOSK";
}*/ }*/

View file

@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Admin\Inventory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Inventory\StoreLocationRequest;
use App\Http\Requests\Inventory\UpdateLocationRequest;
use App\Models\Location;
class LocationController extends Controller
{
public function index()
{
return view('admin.inventory.locations.index', [
'values' => Location::query()->orderBy('name')->get(),
]);
}
public function create()
{
return view('admin.inventory.locations.form', [
'model' => new Location(['active' => true]),
]);
}
public function store(StoreLocationRequest $request)
{
Location::create($request->validated());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.locations.index');
}
public function edit(Location $location)
{
return view('admin.inventory.locations.form', [
'model' => $location,
]);
}
public function update(UpdateLocationRequest $request, Location $location)
{
$location->update($request->validated());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.locations.index');
}
public function destroy(Location $location)
{
$location->delete();
\Session::flash('alert-success', __('Eintrag gelöscht'));
return redirect()->route('admin.inventory.locations.index');
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Admin\Inventory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Inventory\StoreMaterialQualityRequest;
use App\Http\Requests\Inventory\UpdateMaterialQualityRequest;
use App\Models\MaterialQuality;
class MaterialQualityController extends Controller
{
public function index()
{
return view('admin.inventory.material-qualities.index', [
'values' => MaterialQuality::query()->orderBy('pos')->orderBy('name')->get(),
]);
}
public function create()
{
return view('admin.inventory.material-qualities.form', [
'model' => new MaterialQuality(['pos' => 0]),
]);
}
public function store(StoreMaterialQualityRequest $request)
{
MaterialQuality::create($request->validated());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.material-qualities.index');
}
public function edit(MaterialQuality $materialQuality)
{
return view('admin.inventory.material-qualities.form', [
'model' => $materialQuality,
]);
}
public function update(UpdateMaterialQualityRequest $request, MaterialQuality $materialQuality)
{
$materialQuality->update($request->validated());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.material-qualities.index');
}
public function destroy(MaterialQuality $materialQuality)
{
$materialQuality->delete();
\Session::flash('alert-success', __('Eintrag gelöscht'));
return redirect()->route('admin.inventory.material-qualities.index');
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace App\Http\Controllers\Admin\Inventory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Inventory\StorePackagingItemRequest;
use App\Http\Requests\Inventory\UpdatePackagingItemRequest;
use App\Models\PackagingItem;
use App\Models\PackagingMaterial;
use App\Models\Supplier;
use App\Repositories\PackagingItemRepository;
use Illuminate\Http\Request;
class PackagingItemController extends Controller
{
public function __construct(
protected PackagingItemRepository $packagingItemRepository
) {}
public function index(Request $request)
{
$category = $request->get('category', 'packaging');
$isShipping = $category === 'shipping';
$query = PackagingItem::query()
->with(['packagingMaterial', 'supplier'])
->orderBy('name');
if ($isShipping) {
$query->whereIn('category', ['shipping', 'label', 'shipping_office']);
} else {
$query->where('category', 'packaging');
}
return view('admin.inventory.packaging-items.index', [
'values' => $query->get(),
'category' => $category,
'pageTitle' => $isShipping ? __('Versandverpackung') : __('Produktverpackung'),
]);
}
public function create(Request $request)
{
$category = $request->get('category', 'packaging');
return view('admin.inventory.packaging-items.form', [
'model' => new PackagingItem(['active' => true, 'category' => $category === 'shipping' ? 'shipping' : 'packaging', 'weight_grams' => 0]),
'packagingMaterials' => PackagingMaterial::query()->orderBy('pos')->orderBy('name')->get(),
'suppliers' => Supplier::query()->orderBy('name')->get(),
'category' => $category,
]);
}
public function store(StorePackagingItemRequest $request)
{
$item = $this->packagingItemRepository->create($request->validated());
\Session::flash('alert-save', '1');
$category = in_array($item->category, ['shipping', 'label', 'shipping_office']) ? 'shipping' : 'packaging';
return redirect()->route('admin.inventory.packaging-items.index', ['category' => $category]);
}
public function edit(PackagingItem $packagingItem)
{
$category = in_array($packagingItem->category, ['shipping', 'label', 'shipping_office']) ? 'shipping' : 'packaging';
return view('admin.inventory.packaging-items.form', [
'model' => $packagingItem,
'packagingMaterials' => PackagingMaterial::query()->orderBy('pos')->orderBy('name')->get(),
'suppliers' => Supplier::query()->orderBy('name')->get(),
'category' => $category,
]);
}
public function update(UpdatePackagingItemRequest $request, PackagingItem $packagingItem)
{
$this->packagingItemRepository->update($packagingItem, $request->validated());
\Session::flash('alert-save', '1');
$category = in_array($packagingItem->category, ['shipping', 'label', 'shipping_office']) ? 'shipping' : 'packaging';
return redirect()->route('admin.inventory.packaging-items.index', ['category' => $category]);
}
public function destroy(PackagingItem $packagingItem)
{
$category = in_array($packagingItem->category, ['shipping', 'label', 'shipping_office']) ? 'shipping' : 'packaging';
$packagingItem->delete();
\Session::flash('alert-success', __('Eintrag gelöscht'));
return redirect()->route('admin.inventory.packaging-items.index', ['category' => $category]);
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Admin\Inventory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Inventory\StorePackagingMaterialRequest;
use App\Http\Requests\Inventory\UpdatePackagingMaterialRequest;
use App\Models\PackagingMaterial;
class PackagingMaterialController extends Controller
{
public function index()
{
return view('admin.inventory.packaging-materials.index', [
'values' => PackagingMaterial::query()->orderBy('pos')->orderBy('name')->get(),
]);
}
public function create()
{
return view('admin.inventory.packaging-materials.form', [
'model' => new PackagingMaterial(['pos' => 0]),
]);
}
public function store(StorePackagingMaterialRequest $request)
{
PackagingMaterial::create($request->validated());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.packaging-materials.index');
}
public function edit(PackagingMaterial $packagingMaterial)
{
return view('admin.inventory.packaging-materials.form', [
'model' => $packagingMaterial,
]);
}
public function update(UpdatePackagingMaterialRequest $request, PackagingMaterial $packagingMaterial)
{
$packagingMaterial->update($request->validated());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.packaging-materials.index');
}
public function destroy(PackagingMaterial $packagingMaterial)
{
$packagingMaterial->delete();
\Session::flash('alert-success', __('Eintrag gelöscht'));
return redirect()->route('admin.inventory.packaging-materials.index');
}
}

View file

@ -0,0 +1,169 @@
<?php
namespace App\Http\Controllers\Admin\Inventory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Inventory\StoreProductionRequest;
use App\Models\Location;
use App\Models\Product;
use App\Models\Production;
use App\Repositories\ProductionRepository;
use App\Services\ProductionService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
class ProductionController extends Controller
{
public function __construct(
protected ProductionRepository $productionRepository,
protected ProductionService $productionService
) {}
public function index(): View
{
return view('admin.inventory.productions.index', [
'values' => $this->productionRepository->listForIndex(),
]);
}
public function create(): View
{
$defaultLocationId = Location::query()->where('name', 'like', '%öln%')->value('id')
?? Location::query()->where('active', true)->first()?->id;
return view('admin.inventory.productions.create', [
'products' => Product::query()->where('active', 1)->orderBy('name')->get(['id', 'name']),
'locations' => Location::query()->where('active', true)->orderBy('name')->get(),
'defaultLocationId' => $defaultLocationId,
'model' => null,
]);
}
public function store(StoreProductionRequest $request): RedirectResponse
{
$payload = $request->validatedPayload();
try {
$production = $this->productionService->store(
[
'product_id' => $payload['product_id'],
'location_id' => $payload['location_id'],
'produced_at' => $payload['produced_at'],
'quantity' => $payload['quantity'],
'notes' => $payload['notes'],
],
$payload['ingredient_lines'],
(int) $request->user()->id
);
} catch (ValidationException $e) {
return redirect()->back()->withInput()->withErrors($e->errors());
}
if ($production->mhd_warning) {
\Session::flash('alert-warning', __('Hinweis: Mindestens eine Rohstoff-Charge hat ein kürzeres MHD als das geplante Produkt-MHD.'));
}
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.productions.show', $production);
}
public function show(Production $production): View
{
$production->load([
'product',
'location',
'producedByUser.account',
'productionIngredients.ingredient',
'productionIngredients.stockEntry',
'productionPackagings.packagingItem.packagingMaterial',
]);
return view('admin.inventory.productions.show', [
'model' => $production,
]);
}
public function edit(Production $production): View
{
$production->load([
'product',
'location',
'productionIngredients.ingredient',
'productionIngredients.stockEntry',
]);
$defaultLocationId = $production->location_id;
return view('admin.inventory.productions.edit', [
'model' => $production,
'products' => Product::query()->where('active', 1)
->orWhere('id', $production->product_id)
->orderBy('name')->get(['id', 'name']),
'locations' => Location::query()->where('active', true)->orderBy('name')->get(),
'defaultLocationId' => $defaultLocationId,
]);
}
public function update(StoreProductionRequest $request, Production $production): RedirectResponse
{
$payload = $request->validatedPayload();
try {
$production = $this->productionService->updateProduction(
$production,
[
'product_id' => $payload['product_id'],
'location_id' => $payload['location_id'],
'produced_at' => $payload['produced_at'],
'quantity' => $payload['quantity'],
'notes' => $payload['notes'],
],
$payload['ingredient_lines'],
(int) $request->user()->id
);
} catch (ValidationException $e) {
return redirect()->back()->withInput()->withErrors($e->errors());
}
if ($production->mhd_warning) {
\Session::flash('alert-warning', __('Hinweis: Mindestens eine Rohstoff-Charge hat ein kürzeres MHD als das geplante Produkt-MHD.'));
}
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.productions.show', $production);
}
public function copy(Production $production): View
{
$production->load([
'product',
'productionIngredients.ingredient',
'productionIngredients.stockEntry',
]);
$defaultLocationId = $production->location_id;
return view('admin.inventory.productions.create', [
'model' => $production,
'products' => Product::query()->where('active', 1)->orderBy('name')->get(['id', 'name']),
'locations' => Location::query()->where('active', true)->orderBy('name')->get(),
'defaultLocationId' => $defaultLocationId,
]);
}
public function recipeJson(Request $request, Product $product): JsonResponse
{
$locationId = (int) $request->query('location_id', 0);
$quantity = (int) $request->query('quantity', 1);
if ($locationId < 1) {
return response()->json(['message' => __('location_id erforderlich')], 422);
}
return response()->json(
$this->productionService->buildRecipePayload($product, $locationId, max(1, $quantity))
);
}
}

View file

@ -0,0 +1,234 @@
<?php
namespace App\Http\Controllers\Admin\Inventory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Inventory\ReceiveStockEntryRequest;
use App\Http\Requests\Inventory\StoreStockEntryRequest;
use App\Http\Requests\Inventory\UpdateStockEntryRequest;
use App\Models\Ingredient;
use App\Models\Location;
use App\Models\MaterialQuality;
use App\Models\PackagingItem;
use App\Models\StockEntry;
use App\Models\Supplier;
use App\Repositories\StockEntryRepository;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class StockEntryController extends Controller
{
public function __construct(
protected StockEntryRepository $stockEntryRepository
) {}
public function index(): View
{
return view('admin.inventory.stock-entries.index', array_merge($this->formSharedData(), [
'values' => $this->stockEntryRepository->listForIndex(),
]));
}
public function create(): View|RedirectResponse
{
if (! auth()->user()->isAdmin()) {
return redirect()->route('home');
}
return view('admin.inventory.stock-entries.create', array_merge($this->formSharedData(), [
'model' => new StockEntry([
'ordered_at' => now()->toDateString(),
'entry_type' => 'ingredient',
]),
]));
}
public function store(StoreStockEntryRequest $request): RedirectResponse
{
if (! $request->user()->isAdmin()) {
return redirect()->route('home');
}
$data = $request->validatedPayload();
$data['ordered_by'] = (int) auth()->id();
$this->stockEntryRepository->create($data);
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.stock-entries.index');
}
public function show(StockEntry $stockEntry): View
{
$stockEntry->load([
'ingredient',
'packagingItem.packagingMaterial',
'supplier',
'location',
'quality',
'orderedByUser.account',
'receivedByUser.account',
]);
return view('admin.inventory.stock-entries.show', array_merge($this->formSharedData(), [
'model' => $stockEntry,
'materialQualities' => MaterialQuality::query()->orderBy('pos')->orderBy('name')->get(),
]));
}
public function edit(StockEntry $stockEntry): View|RedirectResponse
{
if (! auth()->user()->isAdmin()) {
return redirect()->route('home');
}
if (! $stockEntry->isPending()) {
\Session::flash('alert-error', __('Nur offene Bestellungen können bearbeitet werden.'));
return redirect()->route('admin.inventory.stock-entries.show', $stockEntry);
}
$stockEntry->load(['ingredient', 'packagingItem']);
return view('admin.inventory.stock-entries.edit', array_merge($this->formSharedData(), [
'model' => $stockEntry,
]));
}
public function update(UpdateStockEntryRequest $request, StockEntry $stockEntry): RedirectResponse
{
if (! $request->user()->isAdmin()) {
return redirect()->route('home');
}
if (! $stockEntry->isPending()) {
\Session::flash('alert-error', __('Nur offene Bestellungen können bearbeitet werden.'));
return redirect()->route('admin.inventory.stock-entries.show', $stockEntry);
}
$this->stockEntryRepository->update($stockEntry, $request->validatedPayload());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.stock-entries.index');
}
public function destroy(StockEntry $stockEntry): RedirectResponse
{
if (! auth()->user()->isAdmin()) {
return redirect()->route('home');
}
if (! $stockEntry->isPending()) {
\Session::flash('alert-error', __('Nur offene Bestellungen können gelöscht werden.'));
return redirect()->route('admin.inventory.stock-entries.index');
}
$stockEntry->delete();
\Session::flash('alert-success', __('Eintrag gelöscht'));
return redirect()->route('admin.inventory.stock-entries.index');
}
public function receive(ReceiveStockEntryRequest $request, StockEntry $stockEntry): RedirectResponse
{
if (! $stockEntry->isPending()) {
\Session::flash('alert-error', __('Eintrag nicht mehr offen.'));
return redirect()->route('admin.inventory.stock-entries.show', $stockEntry);
}
$data = $request->validated();
if ($stockEntry->entry_type !== 'ingredient') {
$data['quality_id'] = null;
$data['best_before'] = null;
}
$this->stockEntryRepository->receive($stockEntry, $data);
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.stock-entries.show', $stockEntry->fresh());
}
public function searchIngredients(Request $request): JsonResponse
{
$term = trim((string) $request->query('q', ''));
if (mb_strlen($term) < 1) {
return response()->json(['results' => []]);
}
$rows = Ingredient::query()
->where('active', true)
->where(function ($query) use ($term) {
$query->where('name', 'like', '%'.$term.'%')
->orWhere('inci', 'like', '%'.$term.'%');
})
->orderBy('name')
->limit(30)
->get(['id', 'name', 'inci']);
$results = $rows->map(function (Ingredient $i) {
$text = $i->name;
if ($i->inci) {
$text .= ' ('.$i->inci.')';
}
return ['id' => $i->id, 'text' => $text];
})->values()->all();
return response()->json(['results' => $results]);
}
public function searchPackagingItems(Request $request): JsonResponse
{
$term = trim((string) $request->query('q', ''));
$entryType = $request->query('entry_type');
$categoryMap = [
'packaging' => 'packaging',
'label' => 'label',
'shipping_office' => 'shipping_office',
];
$query = PackagingItem::query()
->where('active', true);
if ($entryType && isset($categoryMap[$entryType])) {
$query->where('category', $categoryMap[$entryType]);
}
if (mb_strlen($term) >= 1) {
$query->where('name', 'like', '%'.$term.'%');
}
$rows = $query->orderBy('name')->limit(30)->get(['id', 'name']);
$results = $rows->map(fn (PackagingItem $p) => [
'id' => $p->id,
'text' => $p->name,
])->values()->all();
return response()->json(['results' => $results]);
}
/**
* @return array<string, mixed>
*/
protected function formSharedData(): array
{
return [
'suppliers' => Supplier::query()->where('active', true)->orderBy('name')->get(),
'locations' => Location::query()->where('active', true)->orderBy('name')->get(),
'entryTypeLabels' => [
'ingredient' => __('Rohstoff'),
'packaging' => __('Verpackung'),
'label' => __('Etikett'),
'shipping_office' => __('Versand & Büro'),
],
];
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Admin\Inventory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Inventory\StoreSupplierCategoryRequest;
use App\Http\Requests\Inventory\UpdateSupplierCategoryRequest;
use App\Models\SupplierCategory;
class SupplierCategoryController extends Controller
{
public function index()
{
return view('admin.inventory.supplier-categories.index', [
'values' => SupplierCategory::query()->orderBy('pos')->orderBy('name')->get(),
]);
}
public function create()
{
return view('admin.inventory.supplier-categories.form', [
'model' => new SupplierCategory(['pos' => 0]),
]);
}
public function store(StoreSupplierCategoryRequest $request)
{
SupplierCategory::create($request->validated());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.supplier-categories.index');
}
public function edit(SupplierCategory $supplierCategory)
{
return view('admin.inventory.supplier-categories.form', [
'model' => $supplierCategory,
]);
}
public function update(UpdateSupplierCategoryRequest $request, SupplierCategory $supplierCategory)
{
$supplierCategory->update($request->validated());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.supplier-categories.index');
}
public function destroy(SupplierCategory $supplierCategory)
{
$supplierCategory->delete();
\Session::flash('alert-success', __('Eintrag gelöscht'));
return redirect()->route('admin.inventory.supplier-categories.index');
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\Admin\Inventory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Inventory\StoreSupplierRequest;
use App\Http\Requests\Inventory\UpdateSupplierRequest;
use App\Models\Country;
use App\Models\Supplier;
use App\Models\SupplierCategory;
use App\Repositories\SupplierRepository;
class SupplierController extends Controller
{
public function __construct(
protected SupplierRepository $supplierRepository
) {}
public function index()
{
return view('admin.inventory.suppliers.index', [
'values' => Supplier::query()->with(['country', 'supplierCategories'])->orderBy('name')->get(),
]);
}
public function create()
{
$defaultCountryId = Country::where('code', 'DE')->value('id');
return view('admin.inventory.suppliers.form', [
'model' => new Supplier(['active' => true, 'country_id' => $defaultCountryId]),
'countries' => Country::query()->orderBy('de')->get(),
'supplierCategories' => SupplierCategory::query()->orderBy('pos')->orderBy('name')->get(),
]);
}
public function store(StoreSupplierRequest $request)
{
$this->supplierRepository->create($request->validated());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.suppliers.index');
}
public function edit(Supplier $supplier)
{
$supplier->load('supplierCategories');
return view('admin.inventory.suppliers.form', [
'model' => $supplier,
'countries' => Country::query()->orderBy('de')->get(),
'supplierCategories' => SupplierCategory::query()->orderBy('pos')->orderBy('name')->get(),
]);
}
public function update(UpdateSupplierRequest $request, Supplier $supplier)
{
$this->supplierRepository->update($supplier, $request->validated());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.suppliers.index');
}
public function destroy(Supplier $supplier)
{
$supplier->delete();
\Session::flash('alert-success', __('Eintrag gelöscht'));
return redirect()->route('admin.inventory.suppliers.index');
}
}

View file

@ -2,11 +2,15 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Storage; use App\Models\File;
use Response; use App\Models\ShoppingOrder;
use App\Models\UserCredit;
use App\Services\Credit; use App\Services\Credit;
use App\Services\Invoice; use App\Services\Invoice;
use App\Services\PDFMerger;
use Auth; use Auth;
use Response;
use Storage;
class FileController extends Controller class FileController extends Controller
{ {
@ -15,11 +19,10 @@ class FileController extends Controller
* *
* @return void * @return void
*/ */
public function __construct() public function __construct() {}
{
}
private function isPermission($user_id){ private function isPermission($user_id)
{
if (Auth::user()->isAdmin() || $user_id == Auth::user()->id) { if (Auth::user()->isAdmin() || $user_id == Auth::user()->id) {
return true; return true;
@ -30,11 +33,11 @@ class FileController extends Controller
public function show($id = null, $disk = null, $do = 'file') public function show($id = null, $disk = null, $do = 'file')
{ {
$path = ""; $path = '';
$filename = ""; $filename = '';
if ($disk === 'user') { if ($disk === 'user') {
$file = \App\Models\File::findOrFail($id); $file = File::findOrFail($id);
$this->isPermission($file->user_id); $this->isPermission($file->user_id);
$path = Storage::disk($disk)->path($file->dir.$file->filename); $path = Storage::disk($disk)->path($file->dir.$file->filename);
if (file_exists($path)) { if (file_exists($path)) {
@ -43,22 +46,28 @@ class FileController extends Controller
} }
if ($disk === 'invoice') { if ($disk === 'invoice') {
$shopping_order = \App\Models\ShoppingOrder::findOrFail($id); $shopping_order = ShoppingOrder::findOrFail($id);
$this->isPermission($shopping_order->auth_user_id); $this->isPermission($shopping_order->auth_user_id);
$filename = Invoice::getFilename($shopping_order); $filename = Invoice::getFilename($shopping_order);
$path = Invoice::getDownloadPath($shopping_order); $path = Invoice::getDownloadPath($shopping_order);
} }
if ($disk === 'delivery') { if ($disk === 'delivery') {
$shopping_order = \App\Models\ShoppingOrder::findOrFail($id); $shopping_order = ShoppingOrder::findOrFail($id);
$this->isPermission($shopping_order->auth_user_id); $this->isPermission($shopping_order->auth_user_id);
$filename = Invoice::getDeliveryFilename($shopping_order); $filename = Invoice::getDeliveryFilename($shopping_order);
$path = Invoice::getDownloadPathDelivery($shopping_order); $path = Invoice::getDownloadPathDelivery($shopping_order);
} }
if ($disk === 'cancellation') {
$shopping_order = ShoppingOrder::findOrFail($id);
$this->isPermission($shopping_order->auth_user_id);
$filename = Invoice::getCancellationFilename($shopping_order);
$path = Invoice::getCancellationDownloadPath($shopping_order);
}
if ($disk === 'invoice_delivery') { if ($disk === 'invoice_delivery') {
$shopping_order = \App\Models\ShoppingOrder::findOrFail($id); $shopping_order = ShoppingOrder::findOrFail($id);
$this->isPermission($shopping_order->auth_user_id); $this->isPermission($shopping_order->auth_user_id);
$ifilename = Invoice::getFilename($shopping_order); $ifilename = Invoice::getFilename($shopping_order);
@ -66,7 +75,7 @@ class FileController extends Controller
$dfilename = Invoice::getDeliveryFilename($shopping_order); $dfilename = Invoice::getDeliveryFilename($shopping_order);
$dpath = Invoice::getDownloadPathDelivery($shopping_order, true); $dpath = Invoice::getDownloadPathDelivery($shopping_order, true);
$oMerger = new \App\Services\PDFMerger(); $oMerger = new PDFMerger;
$oMerger->init(); $oMerger->init();
$oMerger->addPDF($ipath); $oMerger->addPDF($ipath);
@ -75,13 +84,14 @@ class FileController extends Controller
$oMerger->setFileName($filename); $oMerger->setFileName($filename);
$oMerger->merge(); $oMerger->merge();
$file = $oMerger->output(); $file = $oMerger->output();
return Response::make($file, 200) return Response::make($file, 200)
->header("Content-Type", 'application/pdf') ->header('Content-Type', 'application/pdf')
->header('Content-disposition', 'attachment; filename="'.$filename.'"'); ->header('Content-disposition', 'attachment; filename="'.$filename.'"');
} }
if ($disk === 'credit') { if ($disk === 'credit') {
$UserCredit = \App\Models\UserCredit::findOrFail($id); $UserCredit = UserCredit::findOrFail($id);
$this->isPermission($UserCredit->auth_user_id); $this->isPermission($UserCredit->auth_user_id);
$filename = Credit::getFilename($UserCredit); $filename = Credit::getFilename($UserCredit);
$path = Credit::getDownloadPath($UserCredit); $path = Credit::getDownloadPath($UserCredit);
@ -96,7 +106,7 @@ class FileController extends Controller
if ($do === 'download') { if ($do === 'download') {
return Response::make($file, 200) return Response::make($file, 200)
->header("Content-Type", $mime) ->header('Content-Type', $mime)
->header('Content-disposition', 'attachment; filename="'.$filename.'"'); ->header('Content-disposition', 'attachment; filename="'.$filename.'"');
/* $full_path = Invoice::getDownloadPath($shopping_order, true); /* $full_path = Invoice::getDownloadPath($shopping_order, true);
$he $he
@ -106,30 +116,29 @@ class FileController extends Controller
} }
if ($do === 'stream') { if ($do === 'stream') {
return Response::make($file, 200) return Response::make($file, 200)
->header("Content-Type", $mime) ->header('Content-Type', $mime)
->header('Content-disposition', 'filename="'.$filename.'"'); ->header('Content-disposition', 'filename="'.$filename.'"');
} }
if ($do === 'file') { if ($do === 'file') {
return Response::make($file, 200) return Response::make($file, 200)
->header("Content-Type", $mime) ->header('Content-Type', $mime)
->header('Content-disposition', 'filename="'.$filename.'"'); ->header('Content-disposition', 'filename="'.$filename.'"');
} }
if ($do === 'image') { if ($do === 'image') {
return Response::make($file, 200) return Response::make($file, 200)
->header("Content-Type", $mime); ->header('Content-Type', $mime);
} }
if ($do === 'pdf') { if ($do === 'pdf') {
$path = storage_path().'/app/public/'.$path; $path = storage_path().'/app/public/'.$path;
$headers = array( $headers = [
'Content-Type:'.$mime, 'Content-Type:'.$mime,
// 'Content-Length: ' . $file->size // 'Content-Length: ' . $file->size
// 'Content-Disposition: ' . $stream . '; filename=' . $file->original_name // 'Content-Disposition: ' . $stream . '; filename=' . $file->original_name
); ];
return Response::download($path, $filename, $headers); return Response::download($path, $filename, $headers);
} }
} }
} }

View file

@ -2,18 +2,12 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Category;
use App\Models\Ingredient; use App\Models\Ingredient;
use App\Models\IqImage;
use App\Models\ProductCategory;
use App\Models\ProductIngredient; use App\Models\ProductIngredient;
use Request; use Request;
class IngredientController extends Controller class IngredientController extends Controller
{ {
public function __construct() public function __construct()
{ {
$this->middleware('copyreader'); $this->middleware('copyreader');
@ -25,13 +19,14 @@ class IngredientController extends Controller
$data = [ $data = [
'values' => Ingredient::all(), 'values' => Ingredient::all(),
]; ];
return view('admin.ingredient.index', $data); return view('admin.ingredient.index', $data);
} }
public function edit($id) public function edit($id)
{ {
if($id === "new"){ if ($id === 'new') {
$model = new Ingredient(); $model = new Ingredient;
$model->active = true; $model->active = true;
} else { } else {
$model = Ingredient::findOrFail($id); $model = Ingredient::findOrFail($id);
@ -41,6 +36,7 @@ class IngredientController extends Controller
// 'trans' => array_keys(config('localization.supportedLocales')), // 'trans' => array_keys(config('localization.supportedLocales')),
]; ];
return view('admin.ingredient.edit', $data); return view('admin.ingredient.edit', $data);
} }
@ -49,7 +45,18 @@ class IngredientController extends Controller
$data = Request::all(); $data = Request::all();
$data['active'] = isset($data['active']) ? true : false; $data['active'] = isset($data['active']) ? true : false;
if($data['id'] === "new"){ if (isset($data['default_factor']) && $data['default_factor'] !== '') {
$data['default_factor'] = reFormatNumber($data['default_factor']) ?: 1.10;
}
if (isset($data['min_stock_alert']) && $data['min_stock_alert'] === '') {
$data['min_stock_alert'] = null;
} elseif (isset($data['min_stock_alert']) && $data['min_stock_alert'] !== null) {
$data['min_stock_alert'] = reFormatNumber($data['min_stock_alert']);
}
if (empty($data['material_quality_id'])) {
$data['material_quality_id'] = null;
}
if ($data['id'] === 'new') {
$model = Ingredient::create($data); $model = Ingredient::create($data);
} else { } else {
$model = Ingredient::find($data['id']); $model = Ingredient::find($data['id']);
@ -57,22 +64,23 @@ class IngredientController extends Controller
} }
\Session()->flash('alert-save', '1'); \Session()->flash('alert-save', '1');
return redirect(route('admin_product_ingredients'));
return redirect(route('admin_product_ingredients'));
} }
public function delete($id){ public function delete($id)
{
if (ProductIngredient::where('ingredient_id', $id)->count()) { if (ProductIngredient::where('ingredient_id', $id)->count()) {
\Session()->flash('alert-error', 'Eintrag wird als Produkt-Inhaltsstoff verwendet'); \Session()->flash('alert-error', 'Eintrag wird als Produkt-Inhaltsstoff verwendet');
return redirect(route('admin_product_ingredients')); return redirect(route('admin_product_ingredients'));
} }
$model = Ingredient::findOrFail($id); $model = Ingredient::findOrFail($id);
$model->delete(); $model->delete();
\Session()->flash('alert-success', 'Eintrag gelöscht'); \Session()->flash('alert-success', 'Eintrag gelöscht');
return redirect(route('admin_product_ingredients')); return redirect(route('admin_product_ingredients'));
} }
} }

View file

@ -3,6 +3,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Country; use App\Models\Country;
use App\Models\Ingredient;
use App\Models\PackagingItem;
use App\Models\Product; use App\Models\Product;
use App\Models\ProductImage; use App\Models\ProductImage;
use App\Models\ProductIngredient; use App\Models\ProductIngredient;
@ -10,8 +12,6 @@ use App\Repositories\ProductRepository;
use Request; use Request;
use Validator; use Validator;
class ProductController extends Controller class ProductController extends Controller
{ {
protected $productRepo; protected $productRepo;
@ -27,32 +27,37 @@ class ProductController extends Controller
if (Request::get('show_active_products')) { if (Request::get('show_active_products')) {
set_user_attr('show_active_products', Request::get('show_active_products')); set_user_attr('show_active_products', Request::get('show_active_products'));
} }
if(get_user_attr('show_active_products') === "true"){ if (get_user_attr('show_active_products') === 'true') {
$values = Product::where('active', true)->orderBy('pos', 'DESC')->orderBy('id', 'DESC')->get(); $values = Product::where('active', true)->orderBy('pos', 'DESC')->orderBy('id', 'DESC')->get();
} else { } else {
$values = Product::orderBy('pos', 'DESC')->orderBy('id', 'DESC')->get(); $values = Product::orderBy('pos', 'DESC')->orderBy('id', 'DESC')->get();
} }
$data = [ $data = [
'values' => $values 'values' => $values,
]; ];
return view('admin.product.index', $data); return view('admin.product.index', $data);
} }
public function edit($id) public function edit($id)
{ {
if($id === "new"){ if ($id === 'new') {
$model = new Product(); $model = new Product;
$model->active = true; $model->active = true;
} else { } else {
$model = Product::findOrFail($id); $model = Product::findOrFail($id);
$model->load(['packagings.packagingMaterial']);
} }
$country_for_prices = Country::where('own_eur', '=', true)->orWhere('currency', '=', true)->get(); $country_for_prices = Country::where('own_eur', '=', true)->orWhere('currency', '=', true)->get();
$data = [ $data = [
'product' => $model, 'product' => $model,
'country_for_prices' => $country_for_prices, 'country_for_prices' => $country_for_prices,
'ingredient_catalog' => Ingredient::query()->where('active', true)->with('materialQuality')->orderBy('name')->get(['id', 'name', 'inci', 'effect', 'default_factor', 'material_quality_id']),
'packaging_catalog' => PackagingItem::query()->where('active', true)->with('packagingMaterial')->orderBy('name')->get(),
]; ];
return view('admin.product.edit', $data); return view('admin.product.edit', $data);
} }
@ -61,14 +66,14 @@ class ProductController extends Controller
$data = Request::all(); $data = Request::all();
$rules = array( $rules = [
'name' => 'required', 'name' => 'required',
); ];
/*if(isset($data['number']) && $data['number'] != ""){ /*if(isset($data['number']) && $data['number'] != ""){
$rules['number'] = 'int'; $rules['number'] = 'int';
}*/ }*/
if (isset($data['wp_number'])) { if (isset($data['wp_number'])) {
if($data['id'] !== "new"){ if ($data['id'] !== 'new') {
$model = Product::findOrFail($data['id']); $model = Product::findOrFail($data['id']);
$rules['wp_number'] = 'unique:products,wp_number,'.$model->id; $rules['wp_number'] = 'unique:products,wp_number,'.$model->id;
} else { } else {
@ -78,17 +83,19 @@ class ProductController extends Controller
} }
$validator = Validator::make(Request::all(), $rules); $validator = Validator::make(Request::all(), $rules);
if($data['id'] === "new"){ if ($data['id'] === 'new') {
$model = new Product(); $model = new Product;
} else { } else {
$model = Product::findOrFail($data['id']); $model = Product::findOrFail($data['id']);
$model->load(['packagings.packagingMaterial']);
} }
$country_for_prices = Country::where('own_eur', '=', true)->orWhere('currency', '=', true)->get(); $country_for_prices = Country::where('own_eur', '=', true)->orWhere('currency', '=', true)->get();
$data = [ $data = [
'product' => $model, 'product' => $model,
'country_for_prices' => $country_for_prices, 'country_for_prices' => $country_for_prices,
'ingredient_catalog' => Ingredient::query()->where('active', true)->with('materialQuality')->orderBy('name')->get(['id', 'name', 'inci', 'effect', 'default_factor', 'material_quality_id']),
'packaging_catalog' => PackagingItem::query()->where('active', true)->with('packagingMaterial')->orderBy('name')->get(),
]; ];
if ($validator->fails()) { if ($validator->fails()) {
@ -98,25 +105,31 @@ class ProductController extends Controller
} else { } else {
$product = $this->productRepo->update(Request::all()); $product = $this->productRepo->update(Request::all());
\Session()->flash('alert-save', true); \Session()->flash('alert-save', true);
return redirect(route('admin_product_edit', [$product->id])); return redirect(route('admin_product_edit', [$product->id]));
} }
\Session()->flash('alert-save', '1'); \Session()->flash('alert-save', '1');
return redirect(route('admin_product_show')); return redirect(route('admin_product_show'));
} }
public function copy($id){ public function copy($id)
{
$model = Product::findOrFail($id); $model = Product::findOrFail($id);
$product = $this->productRepo->copy($model); $product = $this->productRepo->copy($model);
\Session()->flash('alert-success', 'Eintrag kopiert'); \Session()->flash('alert-success', 'Eintrag kopiert');
return redirect(route('admin_product_show')); return redirect(route('admin_product_show'));
} }
public function delete($id, $do = 'product', $did = null){ public function delete($id, $do = 'product', $did = null)
{
if ($do === 'product') { if ($do === 'product') {
$model = Product::findOrFail($id); $model = Product::findOrFail($id);
$model->delete(); $model->delete();
\Session()->flash('alert-success', 'Eintrag gelöscht'); \Session()->flash('alert-success', 'Eintrag gelöscht');
return redirect(route('admin_product_show')); return redirect(route('admin_product_show'));
} }
@ -126,6 +139,7 @@ class ProductController extends Controller
if ($ProductIngredient) { if ($ProductIngredient) {
$ProductIngredient->delete(); $ProductIngredient->delete();
\Session()->flash('alert-success', 'Eintrag gelöscht'); \Session()->flash('alert-success', 'Eintrag gelöscht');
return redirect(route('admin_product_edit', [$model->id])); return redirect(route('admin_product_edit', [$model->id]));
} }
@ -134,22 +148,27 @@ class ProductController extends Controller
} }
// Upload FILE ----------------------------------------------------------------------------------------------------------------------- // Upload FILE -----------------------------------------------------------------------------------------------------------------------
public function imageUpload(){ public function imageUpload()
{
if (Request::has('product_id')) { if (Request::has('product_id')) {
$product = Product::findOrFail(Request::get('product_id')); $product = Product::findOrFail(Request::get('product_id'));
return \App\Services\ProductImage::imageUpload('product', $product, Request::get('upload_type')); return \App\Services\ProductImage::imageUpload('product', $product, Request::get('upload_type'));
} }
} }
public function imageDelete($product_image_id, $product_id){ public function imageDelete($product_image_id, $product_id)
{
$product = Product::findOrFail($product_id); $product = Product::findOrFail($product_id);
return \App\Services\ProductImage::imageDelete('product', $product, $product_image_id); return \App\Services\ProductImage::imageDelete('product', $product, $product_image_id);
} }
public function imageAttribute($product_id, $attr, $val = false){ public function imageAttribute($product_id, $attr, $val = false)
{
if (is_numeric($val) && $val < 0) { if (is_numeric($val) && $val < 0) {
$val = 0; $val = 0;
@ -160,9 +179,9 @@ class ProductController extends Controller
$product_image->{$attr} = $val; $product_image->{$attr} = $val;
$product_image->save(); $product_image->save();
\Session()->flash('alert-success', "Wert gespeichert"); \Session()->flash('alert-success', 'Wert gespeichert');
return redirect()->back(); return redirect()->back();
} }
} }

View file

@ -2,32 +2,32 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Request;
use App\Models\Setting; use App\Models\Setting;
use App\Services\Payment;
use App\Models\ShoppingUser;
use App\Models\ShoppingOrder; use App\Models\ShoppingOrder;
use App\Models\UserPayCredit; use App\Models\ShoppingUser;
use App\Models\ShoppingPayment;
use App\Models\PaymentTransaction;
use App\Services\CustomerPriority;
use App\Repositories\InvoiceRepository; use App\Repositories\InvoiceRepository;
use App\Services\CustomerPriority;
use App\Services\Invoice;
use App\Services\Payment;
use App\Services\PaymentService; use App\Services\PaymentService;
use Request;
class SalesController extends Controller class SalesController extends Controller
{ {
public function __construct()
public function __construct(){ {
$this->middleware('admin'); $this->middleware('admin');
} }
public function index(){ public function index()
{
if (Request::get('reset') === 'filter') { if (Request::get('reset') === 'filter') {
set_user_attr('filter_txaction', null); set_user_attr('filter_txaction', null);
set_user_attr('filter_member_id', null); set_user_attr('filter_member_id', null);
set_user_attr('filter_art', null); set_user_attr('filter_art', null);
set_user_attr('filter_shipped', null); set_user_attr('filter_shipped', null);
return redirect(route('admin_sales')); return redirect(route('admin_sales'));
} }
// set Filter! // set Filter!
@ -35,10 +35,12 @@ class SalesController extends Controller
$data = [ $data = [
'filter_members' => $filter_members, 'filter_members' => $filter_members,
]; ];
return view('admin.sales.index', $data); return view('admin.sales.index', $data);
} }
public function detail($id){ public function detail($id)
{
$ShoppingOrder = ShoppingOrder::find($id); $ShoppingOrder = ShoppingOrder::find($id);
if ($ShoppingOrder->shipped == 0) { if ($ShoppingOrder->shipped == 0) {
@ -54,28 +56,32 @@ class SalesController extends Controller
return view('admin.sales.detail', $data); return view('admin.sales.detail', $data);
} }
public function detailStore($id){ public function detailStore($id)
{
$data = Request::all(); $data = Request::all();
$change_member_error = false; $change_member_error = false;
if ($data['action'] === 'shopping-order-change-member') { if ($data['action'] === 'shopping-order-change-member') {
if (! isset($data['change_member_key']) || $data['change_member_key'] !== config('main.edit_data_pass')) { if (! isset($data['change_member_key']) || $data['change_member_key'] !== config('main.edit_data_pass')) {
$change_member_error = "Das Passwort ist falsch."; $change_member_error = 'Das Passwort ist falsch.';
} else { } else {
// change // change
$shopping_order = ShoppingOrder::findOrFail($data['id']); $shopping_order = ShoppingOrder::findOrFail($data['id']);
CustomerPriority::newMemberForOrder($shopping_order, $data['change_member_id'], $data['customer_set_member_for']); CustomerPriority::newMemberForOrder($shopping_order, $data['change_member_id'], $data['customer_set_member_for']);
\Session()->flash('alert-save', true); \Session()->flash('alert-save', true);
return redirect(route('admin_sales_detail', [$shopping_order->id])); return redirect(route('admin_sales_detail', [$shopping_order->id]));
} }
} }
if ($data['action'] === 'shopping-user-is-like-member') { if ($data['action'] === 'shopping-user-is-like-member') {
if (! isset($data['change_member_key']) || $data['change_member_key'] !== config('main.edit_data_pass')) { if (! isset($data['change_member_key']) || $data['change_member_key'] !== config('main.edit_data_pass')) {
\Session()->flash('alert-error', 'Das Passwort ist falsch.'); \Session()->flash('alert-error', 'Das Passwort ist falsch.');
return redirect($data['back']); return redirect($data['back']);
} else { } else {
if (! isset($data['is_like_shopping_user_id'])) { if (! isset($data['is_like_shopping_user_id'])) {
\Session()->flash('alert-error', 'Keine Änderung ausgewählt'); \Session()->flash('alert-error', 'Keine Änderung ausgewählt');
return redirect($data['back']); return redirect($data['back']);
} }
$shopping_user = ShoppingUser::findOrFail($data['id']); $shopping_user = ShoppingUser::findOrFail($data['id']);
@ -85,6 +91,7 @@ class SalesController extends Controller
// Mail send in setIsLike // Mail send in setIsLike
CustomerPriority::setIsLike($shopping_user, $set_like_shopping_user, $send_member_mail, $change_shopping_user); CustomerPriority::setIsLike($shopping_user, $set_like_shopping_user, $send_member_mail, $change_shopping_user);
\Session()->flash('alert-save', true); \Session()->flash('alert-save', true);
return redirect($data['back']); return redirect($data['back']);
} }
} }
@ -95,54 +102,57 @@ class SalesController extends Controller
'isAdmin' => true, 'isAdmin' => true,
'isView' => $ShoppingOrder->auth_user_id ? 'sales_user' : 'sales_customer', 'isView' => $ShoppingOrder->auth_user_id ? 'sales_user' : 'sales_customer',
]; ];
return view('admin.sales.detail', $data); return view('admin.sales.detail', $data);
} }
public function datatable(){ public function datatable()
{
$query = ShoppingOrder::with('shopping_user', 'shopping_payments')->select('shopping_orders.*'); $query = ShoppingOrder::with('shopping_user', 'shopping_payments')->select('shopping_orders.*');
set_user_attr('filter_txaction', Request::get('filter_txaction')); set_user_attr('filter_txaction', Request::get('filter_txaction'));
if(Request::get('filter_txaction') != ""){ if (Request::get('filter_txaction') != '') {
if (Request::get('filter_txaction') === 'NULL') { if (Request::get('filter_txaction') === 'NULL') {
$query->where('txaction', '=', NULL); $query->where('txaction', '=', null);
} else { } else {
$query->where('txaction', '=', Request::get('filter_txaction')); $query->where('txaction', '=', Request::get('filter_txaction'));
} }
} }
set_user_attr('filter_member_id', Request::get('filter_member_id')); set_user_attr('filter_member_id', Request::get('filter_member_id'));
if(Request::get('filter_member_id') != ""){ if (Request::get('filter_member_id') != '') {
$query->where('member_id', '=', Request::get('filter_member_id')); $query->where('member_id', '=', Request::get('filter_member_id'));
} }
set_user_attr('filter_art', Request::get('filter_art')); set_user_attr('filter_art', Request::get('filter_art'));
if(Request::get('filter_art') != ""){ if (Request::get('filter_art') != '') {
if (Request::get('filter_art') === 'user_order') { if (Request::get('filter_art') === 'user_order') {
$query->where('shopping_orders.auth_user_id', '!=', NULL)->where('payment_for', '!=', 6); $query->where('shopping_orders.auth_user_id', '!=', null)->where('payment_for', '!=', 6);
} elseif (Request::get('filter_art') === 'customer_order') { } elseif (Request::get('filter_art') === 'customer_order') {
$query->where('shopping_orders.auth_user_id', NULL); $query->where('shopping_orders.auth_user_id', null);
} elseif (Request::get('filter_art') === 'user_for_customer') { } elseif (Request::get('filter_art') === 'user_for_customer') {
$query->where('shopping_user_id', '!=', NULL)->where('payment_for', '=', 6); $query->where('shopping_user_id', '!=', null)->where('payment_for', '=', 6);
} }
// $query->where('payment_for', '=', Request::get('filter_art')); // $query->where('payment_for', '=', Request::get('filter_art'));
} }
set_user_attr('filter_shipped', Request::get('filter_shipped')); set_user_attr('filter_shipped', Request::get('filter_shipped'));
if(Request::get('filter_shipped') != ""){ if (Request::get('filter_shipped') != '') {
$query->where('shipped', '=', Request::get('filter_shipped')); $query->where('shipped', '=', Request::get('filter_shipped'));
} }
return \DataTables::eloquent($query) return \DataTables::eloquent($query)
->addColumn('id', function (ShoppingOrder $ShoppingOrder) { ->addColumn('id', function (ShoppingOrder $ShoppingOrder) {
return '<a href="'.route('admin_sales_detail', [$ShoppingOrder->id]).'" class="btn icon-btn btn-sm btn-primary"><span class="fa fa-edit"></span></a>'; return '<a href="'.route('admin_sales_detail', [$ShoppingOrder->id]).'" class="btn icon-btn btn-sm btn-primary"><span class="fa fa-edit"></span></a>';
}) })
->addColumn('created_at', function (ShoppingOrder $ShoppingOrder) { ->addColumn('created_at', function (ShoppingOrder $ShoppingOrder) {
return $ShoppingOrder->created_at->format("d.m.Y"); return $ShoppingOrder->created_at->format('d.m.Y');
}) })
->addColumn('txaction', function (ShoppingOrder $ShoppingOrder) { ->addColumn('txaction', function (ShoppingOrder $ShoppingOrder) {
return Payment::getShoppingOrderBadge($ShoppingOrder); return Payment::getShoppingOrderBadge($ShoppingOrder);
}) })
->addColumn('total_shipping', function (ShoppingOrder $ShoppingOrder) { ->addColumn('total_shipping', function (ShoppingOrder $ShoppingOrder) {
return $ShoppingOrder->getFormattedTotalShipping().""; return $ShoppingOrder->getFormattedTotalShipping().' €';
}) })
->addColumn('payment', function (ShoppingOrder $ShoppingOrder) { ->addColumn('payment', function (ShoppingOrder $ShoppingOrder) {
return $ShoppingOrder->getLastShoppingPayment('getPaymentType'); return $ShoppingOrder->getLastShoppingPayment('getPaymentType');
@ -168,6 +178,7 @@ class SalesController extends Controller
data-modal="modal-xl" data-modal="modal-xl"
data-route="'.route('modal_load').'"><span class="fa fa-edit"></span> Vertriebspartner zuordnen</button>'; data-route="'.route('modal_load').'"><span class="fa fa-edit"></span> Vertriebspartner zuordnen</button>';
} }
return ''; return '';
}) })
@ -400,7 +411,8 @@ class SalesController extends Controller
->make(true); ->make(true);
}*/ }*/
public function store(){ public function store()
{
$data = Request::all(); $data = Request::all();
if (! isset($data['id'])) { if (! isset($data['id'])) {
abort(404); abort(404);
@ -463,10 +475,8 @@ class SalesController extends Controller
} }
} }
} }
/* txaction ändern /* txaction ändern
änderung der txaction von der Bestellung, Status Zahlung, offen, bezahlt, keine zahlung */ änderung der txaction von der Bestellung, Status Zahlung, offen, bezahlt, keine zahlung */
if ($data['action'] === 'store_txaction' && isset($data['txaction']) && isset($data['payment_id'])) { if ($data['action'] === 'store_txaction' && isset($data['txaction']) && isset($data['payment_id'])) {
@ -477,11 +487,12 @@ class SalesController extends Controller
if (isset($data['back'])) { if (isset($data['back'])) {
return redirect($data['back']); return redirect($data['back']);
} }
return back(); return back();
} }
public function invoice()
public function invoice(){ {
$data = Request::all(); $data = Request::all();
if (! isset($data['id'])) { if (! isset($data['id'])) {
abort(404); abort(404);
@ -490,26 +501,68 @@ class SalesController extends Controller
if ($data['action'] === 'create_invoice') { if ($data['action'] === 'create_invoice') {
$shopping_order = ShoppingOrder::findOrFail($data['id']); $shopping_order = ShoppingOrder::findOrFail($data['id']);
$invoice_repo = new InvoiceRepository($shopping_order); $invoice_repo = new InvoiceRepository($shopping_order);
if(\App\Services\Invoice::isInvoice($shopping_order)){ if (Invoice::isInvoice($shopping_order)) {
$user_invoice = $invoice_repo->update($data); $user_invoice = $invoice_repo->update($data);
} else { } else {
$user_invoice = $invoice_repo->create($data); $user_invoice = $invoice_repo->create($data);
} }
return redirect(route('admin_sales_detail', [$shopping_order->id])); return redirect(route('admin_sales_detail', [$shopping_order->id]));
} }
} }
} }
public function sendLogisticMail($id){ public function invoiceCancellation()
{
$data = Request::all();
if (! isset($data['id'])) {
abort(404);
}
if (isset($data['action'])) {
if ($data['action'] === 'create_cancellation_invoice') {
$shopping_order = ShoppingOrder::findOrFail($data['id']);
// Prüfen ob bereits eine Rechnung existiert
if (! Invoice::isInvoice($shopping_order)) {
\Session()->flash('alert-error', 'Es existiert keine Rechnung, die storniert werden kann.');
return redirect(route('admin_sales_detail', [$shopping_order->id]));
}
// Prüfen ob bereits eine Stornorechnung existiert
if (Invoice::isCancellationInvoice($shopping_order)) {
\Session()->flash('alert-error', 'Es existiert bereits eine Stornorechnung für diese Bestellung.');
return redirect(route('admin_sales_detail', [$shopping_order->id]));
}
$invoice_repo = new InvoiceRepository($shopping_order);
$cancellation_invoice = $invoice_repo->createCancellation($data);
if ($cancellation_invoice) {
\Session()->flash('alert-success', 'Stornorechnung wurde erfolgreich erstellt.');
} else {
\Session()->flash('alert-error', 'Fehler beim Erstellen der Stornorechnung.');
}
return redirect(route('admin_sales_detail', [$shopping_order->id]));
}
}
return redirect(route('admin_sales'));
}
public function sendLogisticMail($id)
{
$shopping_order = ShoppingOrder::findOrFail($id); $shopping_order = ShoppingOrder::findOrFail($id);
if(\App\Services\Invoice::isInvoice($shopping_order)){ if (Invoice::isInvoice($shopping_order)) {
\App\Services\Invoice::sendLogisticMail($shopping_order); Invoice::sendLogisticMail($shopping_order);
\Session()->flash('alert-success', "Rechnung / Lieferschein wurde an den Versand gesendet."); \Session()->flash('alert-success', 'Rechnung / Lieferschein wurde an den Versand gesendet.');
} else { } else {
\Session()->flash('alert-error', "Keine Rechnung vorhanden."); \Session()->flash('alert-error', 'Keine Rechnung vorhanden.');
}
return redirect(route('admin_sales_detail', [$shopping_order->id]));
} }
return redirect(route('admin_sales_detail', [$shopping_order->id]));
}
} }

View file

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class ReceiveStockEntryRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, mixed>>
*/
public function rules(): array
{
return [
'received_at' => ['required', 'date'],
'received_quantity' => ['required', 'numeric', 'min:0.000001'],
'batch_number' => ['nullable', 'string', 'max:100'],
'best_before' => ['nullable', 'date'],
'quality_id' => ['nullable', 'integer', 'exists:material_qualities,id'],
];
}
protected function prepareForValidation(): void
{
$this->merge([
'received_quantity' => reFormatNumber($this->input('received_quantity')),
]);
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class StoreLocationRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, string>>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'active' => ['sometimes', 'boolean'],
];
}
protected function prepareForValidation(): void
{
$this->merge([
'active' => $this->boolean('active'),
]);
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class StoreMaterialQualityRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, string>>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'pos' => ['nullable', 'integer', 'min:0', 'max:255'],
];
}
protected function prepareForValidation(): void
{
if ($this->has('pos') && $this->input('pos') === '') {
$this->merge(['pos' => 0]);
}
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StorePackagingItemRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, mixed>>
*/
public function rules(): array
{
return [
'packaging_material_id' => ['required', 'integer', 'exists:packaging_materials,id'],
'supplier_id' => ['nullable', 'integer', 'exists:suppliers,id'],
'name' => ['required', 'string', 'max:255'],
'category' => ['required', Rule::in(['packaging', 'shipping', 'label', 'shipping_office'])],
'weight_grams' => ['nullable', 'numeric', 'min:0'],
'min_stock_alert' => ['nullable', 'integer', 'min:0'],
'url' => ['nullable', 'url', 'max:500'],
'product_id' => ['nullable', 'integer', 'exists:products,id'],
'active' => ['sometimes', 'boolean'],
];
}
protected function prepareForValidation(): void
{
$this->merge([
'active' => $this->boolean('active'),
]);
foreach (['supplier_id', 'product_id', 'min_stock_alert'] as $key) {
if ($this->input($key) === '' || $this->input($key) === null) {
$this->merge([$key => null]);
}
}
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class StorePackagingMaterialRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, string>>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'pos' => ['nullable', 'integer', 'min:0', 'max:255'],
];
}
protected function prepareForValidation(): void
{
if ($this->has('pos') && $this->input('pos') === '') {
$this->merge(['pos' => 0]);
}
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class StoreProductionRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, mixed|string>>
*/
public function rules(): array
{
return [
'product_id' => ['required', 'integer', 'exists:products,id'],
'location_id' => ['required', 'integer', 'exists:locations,id'],
'produced_at' => ['required', 'date'],
'quantity' => ['required', 'integer', 'min:1'],
'notes' => ['nullable', 'string', 'max:2000'],
'ingredient_lines' => ['required', 'array', 'min:1'],
'ingredient_lines.*.ingredient_id' => ['required', 'integer', 'exists:ingredients,id'],
'ingredient_lines.*.stock_entry_id' => ['required', 'integer', 'exists:stock_entries,id'],
'ingredient_lines.*.quantity_used' => ['required', 'string'],
];
}
/**
* @return array<string, mixed>
*/
public function validatedPayload(): array
{
$data = $this->validated();
return [
'product_id' => (int) $data['product_id'],
'location_id' => (int) $data['location_id'],
'produced_at' => $data['produced_at'],
'quantity' => (int) $data['quantity'],
'notes' => $data['notes'] ?? null,
'ingredient_lines' => array_map(function (array $line): array {
return [
'ingredient_id' => (int) $line['ingredient_id'],
'stock_entry_id' => (int) $line['stock_entry_id'],
'quantity_used' => $line['quantity_used'],
];
}, $data['ingredient_lines']),
];
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Validator;
class StoreStockEntryRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, mixed>>
*/
public function rules(): array
{
return [
'entry_type' => ['required', Rule::in(['ingredient', 'packaging', 'label', 'shipping_office'])],
'ingredient_id' => ['nullable', 'integer', 'exists:ingredients,id'],
'packaging_item_id' => ['nullable', 'integer', 'exists:packaging_items,id'],
'supplier_id' => ['required', 'integer', 'exists:suppliers,id'],
'location_id' => ['required', 'integer', 'exists:locations,id'],
'ordered_at' => ['required', 'date'],
'ordered_quantity' => ['required', 'numeric', 'min:0.000001'],
'price_per_kg' => ['nullable', 'numeric', 'min:0'],
'price_total' => ['nullable', 'numeric', 'min:0'],
];
}
protected function prepareForValidation(): void
{
$this->merge([
'ordered_quantity' => reFormatNumber($this->input('ordered_quantity')),
'price_per_kg' => $this->filled('price_per_kg') ? reFormatNumber($this->input('price_per_kg')) : null,
'price_total' => $this->filled('price_total') ? reFormatNumber($this->input('price_total')) : null,
]);
}
public function withValidator(Validator $validator): void
{
$validator->after(function (Validator $validator): void {
$type = $this->input('entry_type');
if ($type === 'ingredient') {
if (empty($this->input('ingredient_id'))) {
$validator->errors()->add('ingredient_id', __('Bitte einen Inhaltsstoff wählen.'));
}
} elseif (! empty($type)) {
if (empty($this->input('packaging_item_id'))) {
$validator->errors()->add('packaging_item_id', __('Bitte einen Verpackungsartikel wählen.'));
}
}
});
}
/**
* @return array<string, mixed>
*/
public function validatedPayload(): array
{
$data = $this->validated();
if (($data['entry_type'] ?? '') === 'ingredient') {
$data['packaging_item_id'] = null;
} else {
$data['ingredient_id'] = null;
}
return $data;
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class StoreSupplierCategoryRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, string>>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'pos' => ['nullable', 'integer', 'min:0', 'max:255'],
];
}
protected function prepareForValidation(): void
{
if ($this->has('pos') && $this->input('pos') === '') {
$this->merge(['pos' => 0]);
}
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class StoreSupplierRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, mixed>>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'url' => ['nullable', 'string', 'max:2048'],
'contact_person' => ['nullable', 'string', 'max:255'],
'email' => ['nullable', 'email', 'max:255'],
'phone' => ['nullable', 'string', 'max:100'],
'country_id' => ['required', 'integer', 'exists:countries,id'],
'notes' => ['nullable', 'string'],
'active' => ['sometimes', 'boolean'],
'supplier_category_ids' => ['nullable', 'array'],
'supplier_category_ids.*' => ['integer', 'exists:supplier_categories,id'],
];
}
protected function prepareForValidation(): void
{
$this->merge([
'active' => $this->boolean('active'),
]);
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class UpdateLocationRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, string>>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'active' => ['sometimes', 'boolean'],
];
}
protected function prepareForValidation(): void
{
$this->merge([
'active' => $this->boolean('active'),
]);
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class UpdateMaterialQualityRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, string>>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'pos' => ['nullable', 'integer', 'min:0', 'max:255'],
];
}
protected function prepareForValidation(): void
{
if ($this->has('pos') && $this->input('pos') === '') {
$this->merge(['pos' => 0]);
}
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class UpdatePackagingItemRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, mixed>>
*/
public function rules(): array
{
return [
'packaging_material_id' => ['required', 'integer', 'exists:packaging_materials,id'],
'supplier_id' => ['nullable', 'integer', 'exists:suppliers,id'],
'name' => ['required', 'string', 'max:255'],
'category' => ['required', Rule::in(['packaging', 'shipping', 'label', 'shipping_office'])],
'weight_grams' => ['nullable', 'numeric', 'min:0'],
'min_stock_alert' => ['nullable', 'integer', 'min:0'],
'url' => ['nullable', 'url', 'max:500'],
'product_id' => ['nullable', 'integer', 'exists:products,id'],
'active' => ['sometimes', 'boolean'],
];
}
protected function prepareForValidation(): void
{
$this->merge([
'active' => $this->boolean('active'),
]);
foreach (['supplier_id', 'product_id', 'min_stock_alert'] as $key) {
if ($this->input($key) === '' || $this->input($key) === null) {
$this->merge([$key => null]);
}
}
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePackagingMaterialRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, string>>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'pos' => ['nullable', 'integer', 'min:0', 'max:255'],
];
}
protected function prepareForValidation(): void
{
if ($this->has('pos') && $this->input('pos') === '') {
$this->merge(['pos' => 0]);
}
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Validator;
class UpdateStockEntryRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, mixed>>
*/
public function rules(): array
{
return [
'entry_type' => ['required', Rule::in(['ingredient', 'packaging', 'label', 'shipping_office'])],
'ingredient_id' => ['nullable', 'integer', 'exists:ingredients,id'],
'packaging_item_id' => ['nullable', 'integer', 'exists:packaging_items,id'],
'supplier_id' => ['required', 'integer', 'exists:suppliers,id'],
'location_id' => ['required', 'integer', 'exists:locations,id'],
'ordered_at' => ['required', 'date'],
'ordered_quantity' => ['required', 'numeric', 'min:0.000001'],
'price_per_kg' => ['nullable', 'numeric', 'min:0'],
'price_total' => ['nullable', 'numeric', 'min:0'],
];
}
protected function prepareForValidation(): void
{
$this->merge([
'ordered_quantity' => reFormatNumber($this->input('ordered_quantity')),
'price_per_kg' => $this->filled('price_per_kg') ? reFormatNumber($this->input('price_per_kg')) : null,
'price_total' => $this->filled('price_total') ? reFormatNumber($this->input('price_total')) : null,
]);
}
public function withValidator(Validator $validator): void
{
$validator->after(function (Validator $validator): void {
$type = $this->input('entry_type');
if ($type === 'ingredient') {
if (empty($this->input('ingredient_id'))) {
$validator->errors()->add('ingredient_id', __('Bitte einen Inhaltsstoff wählen.'));
}
} elseif (! empty($type)) {
if (empty($this->input('packaging_item_id'))) {
$validator->errors()->add('packaging_item_id', __('Bitte einen Verpackungsartikel wählen.'));
}
}
});
}
/**
* @return array<string, mixed>
*/
public function validatedPayload(): array
{
$data = $this->validated();
if (($data['entry_type'] ?? '') === 'ingredient') {
$data['packaging_item_id'] = null;
} else {
$data['ingredient_id'] = null;
}
return $data;
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class UpdateSupplierCategoryRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, string>>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'pos' => ['nullable', 'integer', 'min:0', 'max:255'],
];
}
protected function prepareForValidation(): void
{
if ($this->has('pos') && $this->input('pos') === '') {
$this->merge(['pos' => 0]);
}
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace App\Http\Requests\Inventory;
use Illuminate\Foundation\Http\FormRequest;
class UpdateSupplierRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, array<int, mixed>>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'url' => ['nullable', 'string', 'max:2048'],
'contact_person' => ['nullable', 'string', 'max:255'],
'email' => ['nullable', 'email', 'max:255'],
'phone' => ['nullable', 'string', 'max:100'],
'country_id' => ['required', 'integer', 'exists:countries,id'],
'notes' => ['nullable', 'string'],
'active' => ['sometimes', 'boolean'],
'supplier_category_ids' => ['nullable', 'array'],
'supplier_category_ids.*' => ['integer', 'exists:supplier_categories,id'],
];
}
protected function prepareForValidation(): void
{
$this->merge([
'active' => $this->boolean('active'),
]);
}
}

View file

@ -1,14 +1,13 @@
<?php <?php
namespace App\Mail; namespace App\Mail;
use App\User;
use App\Services\Invoice;
use App\Models\ShoppingOrder; use App\Models\ShoppingOrder;
use App\Services\Invoice;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Storage; use Storage;
use Illuminate\Contracts\Queue\ShouldQueue;
class MailLogistic extends Mailable class MailLogistic extends Mailable
{ {
@ -18,15 +17,16 @@ class MailLogistic extends Mailable
public $subject; public $subject;
public function __construct(ShoppingOrder $shopping_order) public function __construct(ShoppingOrder $shopping_order)
{ {
$this->shopping_order = $shopping_order; $this->shopping_order = $shopping_order;
$name = $shopping_order->shopping_user->billing_firstname.' '.$shopping_order->shopping_user->billing_lastname; $name = $shopping_order->shopping_user->billing_firstname.' '.$shopping_order->shopping_user->billing_lastname;
$company = $shopping_order->shopping_user->billing_company ?? ''; $company = $shopping_order->shopping_user->billing_company ?? '';
$hasWhiteLabel = $shopping_order->user_white_label || $shopping_order->hasWhitelabelProducts();
$this->subject = 'Partner'; $this->subject = 'Partner';
if($shopping_order->user_white_label){ if ($hasWhiteLabel) {
// Bei allen, die ein eigenes Logo haben // Bei allen, die ein eigenes Logo haben
$this->subject .= ' (mit Logo)'; $this->subject .= ' (mit Logo)';
} else { } else {
@ -57,7 +57,6 @@ class MailLogistic extends Mailable
$file = Storage::disk('public')->path($path); $file = Storage::disk('public')->path($path);
$mime = Storage::disk('public')->mimeType($path); $mime = Storage::disk('public')->mimeType($path);
$mail = $this->view('emails.logistic')->with([ $mail = $this->view('emails.logistic')->with([
'title' => $title, 'title' => $title,
'copy1line' => $copy1line, 'copy1line' => $copy1line,
@ -66,8 +65,9 @@ class MailLogistic extends Mailable
'mime' => $mime, 'mime' => $mime,
]); ]);
//Wenn das Logo gesetzt ist und die Rechnungsadresse und Lieferadresse unterschiedlich sind, dann wird der Lieferschein angehängt // Wenn mindestens ein White-Label-Produkt enthalten ist, wird der Lieferschein (Etikett-Infos) mit angehängt
if($this->shopping_order->user_white_label && !$this->shopping_order->shopping_user->same_as_billing){ $hasWhiteLabel = $this->shopping_order->user_white_label || $this->shopping_order->hasWhitelabelProducts();
if ($hasWhiteLabel) {
$filenameDelivery = Invoice::getDeliveryFilename($this->shopping_order); $filenameDelivery = Invoice::getDeliveryFilename($this->shopping_order);
$pathDelivery = Invoice::getDownloadPathDelivery($this->shopping_order); $pathDelivery = Invoice::getDownloadPathDelivery($this->shopping_order);
if (! Storage::disk('public')->exists($pathDelivery)) { if (! Storage::disk('public')->exists($pathDelivery)) {

View file

@ -9,6 +9,7 @@ namespace App\Models;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/** /**
* Class Ingredient * Class Ingredient
@ -25,10 +26,10 @@ use Illuminate\Database\Eloquent\Model;
* @property Carbon $created_at * @property Carbon $created_at
* @property Carbon $updated_at * @property Carbon $updated_at
* @property Collection|Product[] $products * @property Collection|Product[] $products
* @package App\Models * @property-read Collection|ProductIngredient[] $product_ingredients
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductIngredient[] $product_ingredients
* @property-read int|null $product_ingredients_count * @property-read int|null $product_ingredients_count
* @property-read int|null $products_count * @property-read int|null $products_count
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient newQuery() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient query() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient query()
@ -43,6 +44,7 @@ use Illuminate\Database\Eloquent\Model;
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient whereTransInci($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient whereTransInci($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient whereTransName($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient whereTransName($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient whereUpdatedAt($value)
*
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class Ingredient extends Model class Ingredient extends Model
@ -51,7 +53,9 @@ class Ingredient extends Model
protected $casts = [ protected $casts = [
'active' => 'bool', 'active' => 'bool',
'pos' => 'int' 'pos' => 'int',
'default_factor' => 'decimal:2',
'min_stock_alert' => 'decimal:2',
]; ];
protected $fillable = [ protected $fillable = [
@ -62,19 +66,30 @@ class Ingredient extends Model
'effect', 'effect',
'trans_effect', 'trans_effect',
'active', 'active',
'pos' 'pos',
'default_factor',
'min_stock_alert',
'material_quality_id',
]; ];
/**
* @return BelongsTo<MaterialQuality, $this>
*/
public function materialQuality(): BelongsTo
{
return $this->belongsTo(MaterialQuality::class);
}
public function products() public function products()
{ {
return $this->belongsToMany(Product::class, 'product_ingredients') return $this->belongsToMany(Product::class, 'product_ingredients')
->withPivot('id') ->withPivot('id', 'pos', 'gram', 'factor')
->withTimestamps(); ->withTimestamps()
->orderByPivot('pos');
} }
public function product_ingredients() public function product_ingredients()
{ {
return $this->hasMany(ProductIngredient::class, 'product_ingredients', 'id'); return $this->hasMany(ProductIngredient::class, 'ingredient_id');
} }
} }

28
app/Models/Location.php Normal file
View file

@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Database\Factories\LocationFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Location extends Model
{
/** @use HasFactory<LocationFactory> */
use HasFactory;
protected $fillable = [
'name',
'active',
];
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'active' => 'boolean',
];
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Database\Factories\MaterialQualityFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class MaterialQuality extends Model
{
/** @use HasFactory<MaterialQualityFactory> */
use HasFactory;
protected $fillable = [
'name',
'pos',
];
}

View file

@ -0,0 +1,75 @@
<?php
namespace App\Models;
use Database\Factories\PackagingItemFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class PackagingItem extends Model
{
/** @use HasFactory<PackagingItemFactory> */
use HasFactory, SoftDeletes;
protected $fillable = [
'packaging_material_id',
'supplier_id',
'name',
'category',
'weight_grams',
'min_stock_alert',
'url',
'product_id',
'active',
];
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'active' => 'boolean',
'weight_grams' => 'decimal:2',
];
}
/**
* @return BelongsTo<PackagingMaterial, $this>
*/
public function packagingMaterial(): BelongsTo
{
return $this->belongsTo(PackagingMaterial::class);
}
/**
* @return BelongsTo<Supplier, $this>
*/
public function supplier(): BelongsTo
{
return $this->belongsTo(Supplier::class);
}
/**
* @return BelongsTo<Product, $this>
*/
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
/**
* Produkte, die diesen Verpackungsartikel in der Stückliste führen (BOM).
*
* @return BelongsToMany<Product, $this>
*/
public function products(): BelongsToMany
{
return $this->belongsToMany(Product::class, 'product_packagings')
->withPivot('quantity', 'pos')
->withTimestamps();
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Database\Factories\PackagingMaterialFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class PackagingMaterial extends Model
{
/** @use HasFactory<PackagingMaterialFactory> */
use HasFactory;
protected $fillable = [
'name',
'pos',
];
/**
* @return HasMany<PackagingItem, $this>
*/
public function packagingItems(): HasMany
{
return $this->hasMany(PackagingItem::class);
}
}

View file

@ -20,8 +20,24 @@ use Illuminate\Database\Eloquent\Model;
* @property bool $active * @property bool $active
* @property Carbon|null $created_at * @property Carbon|null $created_at
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
* @property string|null $subject
* @property string|null $clearingtype
* *
* @package App\Models * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder whereAction($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder whereActive($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder whereClearingtype($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder whereInterval($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder whereMessage($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder whereSubject($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder whereTitle($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentReminder whereUpdatedAt($value)
*
* @mixin \Eloquent
*/ */
class PaymentReminder extends Model class PaymentReminder extends Model
{ {
@ -29,7 +45,7 @@ class PaymentReminder extends Model
protected $casts = [ protected $casts = [
'interval' => 'int', 'interval' => 'int',
'active' => 'bool' 'active' => 'bool',
]; ];
protected $fillable = [ protected $fillable = [
@ -39,7 +55,7 @@ class PaymentReminder extends Model
'message', 'message',
'action', 'action',
'clearingtype', 'clearingtype',
'active' 'active',
]; ];
protected static $clearingtypes = [ protected static $clearingtypes = [
@ -47,11 +63,13 @@ class PaymentReminder extends Model
'vor' => 'Vorkasse', 'vor' => 'Vorkasse',
]; ];
public function getClearingtype(){ public function getClearingtype()
{
return isset(self::$clearingtypes[$this->clearingtype]) ? self::$clearingtypes[$this->clearingtype] : 'Kein Typ'; return isset(self::$clearingtypes[$this->clearingtype]) ? self::$clearingtypes[$this->clearingtype] : 'Kein Typ';
} }
public static function returnClearingtypes(){ public static function returnClearingtypes()
{
return self::$clearingtypes; return self::$clearingtypes;
} }
} }

View file

@ -5,8 +5,12 @@ namespace App\Models;
use App\Services\Type; use App\Services\Type;
use App\Services\Util; use App\Services\Util;
use Cviebrock\EloquentSluggable\Sluggable; use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
/** /**
* App\Models\Product * App\Models\Product
@ -34,13 +38,14 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property int|null $pos * @property int|null $pos
* @property int $active * @property int $active
* @property int|null $amount * @property int|null $amount
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductAttribute[] $attributes * @property-read Collection|ProductAttribute[] $attributes
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductCategory[] $categories * @property-read Collection|ProductCategory[] $categories
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductImage[] $images * @property-read Collection|ProductImage[] $images
* @property-write mixed $price_vk * @property-write mixed $price_vk
*
* @method static bool|null forceDelete() * @method static bool|null forceDelete()
* @method static \Illuminate\Database\Query\Builder|\App\Models\Product onlyTrashed() * @method static \Illuminate\Database\Query\Builder|\App\Models\Product onlyTrashed()
* @method static bool|null restore() * @method static bool|null restore()
@ -72,78 +77,102 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereUsage($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereUsage($value)
* @method static \Illuminate\Database\Query\Builder|\App\Models\Product withTrashed() * @method static \Illuminate\Database\Query\Builder|\App\Models\Product withTrashed()
* @method static \Illuminate\Database\Query\Builder|\App\Models\Product withoutTrashed() * @method static \Illuminate\Database\Query\Builder|\App\Models\Product withoutTrashed()
*
* @property string|null $slug * @property string|null $slug
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product findSimilarSlugs($attribute, $config, $slug) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product findSimilarSlugs($attribute, $config, $slug)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereSlug($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereSlug($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product newQuery() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product query() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product query()
*
* @property int|null $weight * @property int|null $weight
* @property int|null $show_at * @property int|null $show_at
* @property array|null $action * @property array|null $action
* @property-read int|null $attributes_count * @property-read int|null $attributes_count
* @property-read int|null $categories_count * @property-read int|null $categories_count
* @property-read int|null $images_count * @property-read int|null $images_count
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereAction($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereAction($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereShowAt($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereShowAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereWeight($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereWeight($value)
*
* @property int|null $points * @property int|null $points
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductImage[] $imagesActive * @property-read Collection|ProductImage[] $imagesActive
* @property-read int|null $images_active_count * @property-read int|null $images_active_count
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product wherePoints($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product wherePoints($value)
*
* @property string|null $identifier * @property string|null $identifier
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereIdentifier($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereIdentifier($value)
*
* @property int|null $upgrade_to_id * @property int|null $upgrade_to_id
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereUpgradeToId($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereUpgradeToId($value)
*
* @property int|null $contents_total * @property int|null $contents_total
* @property int|null $unit * @property int|null $unit
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereContentsTotal($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereContentsTotal($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereUnit($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereUnit($value)
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\CountryPrice[] $country_prices *
* @property-read Collection|CountryPrice[] $country_prices
* @property-read int|null $country_prices_count * @property-read int|null $country_prices_count
* @property int|null $wp_number * @property int|null $wp_number
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereWpNumber($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereWpNumber($value)
*
* @property bool|null $single_commission * @property bool|null $single_commission
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereShippingAddon($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereShippingAddon($value)
*
* @property-read int|null $ingredients_count * @property-read int|null $ingredients_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductIngredient[] $product_ingredients * @property-read Collection|ProductIngredient[] $product_ingredients
* @property-read int|null $product_ingredients_count * @property-read int|null $product_ingredients_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Ingredient[] $p_ingredients * @property-read Collection|Ingredient[] $p_ingredients
* @property-read int|null $p_ingredients_count * @property-read int|null $p_ingredients_count
* @property bool $amount_commission * @property bool $amount_commission
* @property string|null $value_commission * @property string|null $value_commission
* @property string|null $partner_commission * @property string|null $partner_commission
*
* @method static \Illuminate\Database\Eloquent\Builder|Product whereAmountCommission($value) * @method static \Illuminate\Database\Eloquent\Builder|Product whereAmountCommission($value)
* @method static \Illuminate\Database\Eloquent\Builder|Product wherePartnerCommission($value) * @method static \Illuminate\Database\Eloquent\Builder|Product wherePartnerCommission($value)
* @method static \Illuminate\Database\Eloquent\Builder|Product whereSingleCommission($value) * @method static \Illuminate\Database\Eloquent\Builder|Product whereSingleCommission($value)
* @method static \Illuminate\Database\Eloquent\Builder|Product whereValueCommission($value) * @method static \Illuminate\Database\Eloquent\Builder|Product whereValueCommission($value)
*
* @property bool|null $shipping_addon * @property bool|null $shipping_addon
* @property bool|null $max_buy * @property bool|null $max_buy
* @property int|null $max_buy_num * @property int|null $max_buy_num
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductBuy[] $product_buys * @property-read Collection|ProductBuy[] $product_buys
* @property-read int|null $product_buys_count * @property-read int|null $product_buys_count
*
* @method static \Illuminate\Database\Eloquent\Builder|Product whereMaxBuy($value) * @method static \Illuminate\Database\Eloquent\Builder|Product whereMaxBuy($value)
* @method static \Illuminate\Database\Eloquent\Builder|Product whereMaxBuyNum($value) * @method static \Illuminate\Database\Eloquent\Builder|Product whereMaxBuyNum($value)
* @method static \Illuminate\Database\Eloquent\Builder|Product withUniqueSlugConstraints(\Illuminate\Database\Eloquent\Model $model, string $attribute, array $config, string $slug) * @method static \Illuminate\Database\Eloquent\Builder|Product withUniqueSlugConstraints(\Illuminate\Database\Eloquent\Model $model, string $attribute, array $config, string $slug)
*
* @property string|null $short_copy * @property string|null $short_copy
* @property array|null $show_on * @property array|null $show_on
*
* @method static \Illuminate\Database\Eloquent\Builder|Product whereShortCopy($value) * @method static \Illuminate\Database\Eloquent\Builder|Product whereShortCopy($value)
* @method static \Illuminate\Database\Eloquent\Builder|Product whereShowOn($value) * @method static \Illuminate\Database\Eloquent\Builder|Product whereShowOn($value)
*
* @property bool $exclude_stats_sales * @property bool $exclude_stats_sales
* @property bool|null $whitelabel * @property bool|null $whitelabel
* @property string|null $whitelabel_name * @property string|null $whitelabel_name
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProductAttribute> $attribute_variants * @property-read Collection<int, ProductAttribute> $attribute_variants
* @property-read int|null $attribute_variants_count * @property-read int|null $attribute_variants_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProductImage> $whitelabel_images * @property-read Collection<int, ProductImage> $whitelabel_images
* @property-read int|null $whitelabel_images_count * @property-read int|null $whitelabel_images_count
*
* @method static \Illuminate\Database\Eloquent\Builder|Product whereExcludeStatsSales($value) * @method static \Illuminate\Database\Eloquent\Builder|Product whereExcludeStatsSales($value)
* @method static \Illuminate\Database\Eloquent\Builder|Product whereWhitelabel($value) * @method static \Illuminate\Database\Eloquent\Builder|Product whereWhitelabel($value)
* @method static \Illuminate\Database\Eloquent\Builder|Product whereWhitelabelName($value) * @method static \Illuminate\Database\Eloquent\Builder|Product whereWhitelabelName($value)
*
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class Product extends Model class Product extends Model
{ {
/*identifiers /*identifiers
show_upgrade # in membership payment can upgrade this package to show order show_upgrade # in membership payment can upgrade this package to show order
show_order # in membership payment show always show_order # in membership payment show always
@ -172,8 +201,8 @@ class Product extends Model
'max_buy_num' => 'int', 'max_buy_num' => 'int',
'whitelabel' => 'bool', 'whitelabel' => 'bool',
]; ];
use Sluggable;
use Sluggable;
use SoftDeletes; use SoftDeletes;
protected $dates = ['deleted_at']; protected $dates = ['deleted_at'];
@ -215,7 +244,9 @@ class Product extends Model
'upgrade_to_id', 'upgrade_to_id',
'shipping_addon', 'shipping_addon',
'max_buy', 'max_buy',
'max_buy_num' 'max_buy_num',
'shelf_life_type',
'shelf_life_months',
]; ];
@ -227,6 +258,7 @@ class Product extends Model
'upgrade_member' => 'Vertriebspartnerupgrade zur Karriere ID', 'upgrade_member' => 'Vertriebspartnerupgrade zur Karriere ID',
// 'proportional_voucher' => 'Anteiliger Gutschein Vertriebspartner', // 'proportional_voucher' => 'Anteiliger Gutschein Vertriebspartner',
]; ];
public $unitTypes = [ public $unitTypes = [
0 => '', 0 => '',
1 => 'ml', 1 => 'ml',
@ -248,33 +280,38 @@ class Product extends Model
{ {
return [ return [
'slug' => [ 'slug' => [
'source' => 'name' 'source' => 'name',
] ],
]; ];
} }
public function attributes(){ public function attributes()
{
return $this->hasMany(ProductAttribute::class, 'product_id', 'id')->where('type_id', '!=', 1); return $this->hasMany(ProductAttribute::class, 'product_id', 'id')->where('type_id', '!=', 1);
} }
public function attribute_variants(){ public function attribute_variants()
{
return $this->hasMany(ProductAttribute::class, 'product_id', 'id')->where('type_id', '=', 1); return $this->hasMany(ProductAttribute::class, 'product_id', 'id')->where('type_id', '=', 1);
} }
public function categories()
public function categories(){ {
return $this->hasMany('App\Models\ProductCategory', 'product_id', 'id'); return $this->hasMany('App\Models\ProductCategory', 'product_id', 'id');
} }
public function images(){ public function images()
{
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'product')->orderBy('pos'); return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'product')->orderBy('pos');
} }
public function imagesActive(){ public function imagesActive()
{
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'product')->where('active', true)->orderBy('pos'); return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'product')->where('active', true)->orderBy('pos');
} }
public function whitelabel_images(){ public function whitelabel_images()
{
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'wllogo')->orderBy('pos'); return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'wllogo')->orderBy('pos');
} }
@ -291,81 +328,130 @@ class Product extends Model
public function p_ingredients() public function p_ingredients()
{ {
return $this->belongsToMany(Ingredient::class, 'product_ingredients') return $this->belongsToMany(Ingredient::class, 'product_ingredients')
->withPivot('id') ->withPivot('id', 'pos', 'gram', 'factor', 'recipe_type')
->withTimestamps(); ->withTimestamps()
->wherePivot('recipe_type', 'product')
->orderByPivot('pos');
}
public function manufacturer_ingredients()
{
return $this->belongsToMany(Ingredient::class, 'product_ingredients')
->withPivot('id', 'pos', 'gram', 'factor', 'recipe_type')
->withTimestamps()
->wherePivot('recipe_type', 'manufacturer')
->orderByPivot('pos');
} }
public function product_ingredients() public function product_ingredients()
{ {
return $this->hasMany(ProductIngredient::class, 'product_ingredients', 'id'); return $this->hasMany(ProductIngredient::class, 'product_id');
} }
public function getShortCopy($clean = false, $len = false){ /**
* Verpackungs-Stückliste (BOM) am Produkt mit Menge und Reihenfolge.
*
* @return BelongsToMany<PackagingItem, $this>
*/
public function packagings()
{
return $this->belongsToMany(PackagingItem::class, 'product_packagings')
->withPivot('quantity', 'pos')
->withTimestamps()
->orderByPivot('pos');
}
/**
* @return HasMany<Production, $this>
*/
public function productions(): HasMany
{
return $this->hasMany(Production::class);
}
public function getShortCopy($clean = false, $len = false)
{
$ret = $this->short_copy ? $this->short_copy : $this->description; $ret = $this->short_copy ? $this->short_copy : $this->description;
if ($len && $clean) { if ($len && $clean) {
return substr_ellipsis($ret, $len, $clean); return substr_ellipsis($ret, $len, $clean);
} }
return $ret; return $ret;
} }
public function getActionName($id = 0){
public function getActionName($id = 0)
{
if (isset($this->actions[$id])) { if (isset($this->actions[$id])) {
return $this->actions[$id]; return $this->actions[$id];
} }
return false; return false;
} }
public function getWhiteLableName($id = 0){ public function getWhiteLableName($id = 0)
{
return $this->whitelabel_name ? $this->whitelabel_name : $this->name; return $this->whitelabel_name ? $this->whitelabel_name : $this->name;
} }
public function _format_number($value){ public function _format_number($value)
return preg_replace("/[^0-9,]/", "", $value); {
return preg_replace('/[^0-9,]/', '', $value);
} }
public function setPriceAttribute( $value ) { public function setPriceAttribute($value)
{
$this->attributes['price'] = $value ? Util::reFormatNumber($value) : null; $this->attributes['price'] = $value ? Util::reFormatNumber($value) : null;
} }
public function setPriceEkAttribute( $value ) {
public function setPriceEkAttribute($value)
{
$this->attributes['price_ek'] = $value ? Util::reFormatNumber($value) : null; $this->attributes['price_ek'] = $value ? Util::reFormatNumber($value) : null;
} }
public function setTaxAttribute( $value ) {
$this->attributes['tax'] = $value != "" ? Util::reFormatNumber($value) : null; public function setTaxAttribute($value)
{
$this->attributes['tax'] = $value != '' ? Util::reFormatNumber($value) : null;
} }
public function setPriceOldAttribute( $value ) {
public function setPriceOldAttribute($value)
{
$this->attributes['price_old'] = $value ? Util::reFormatNumber($value) : null; $this->attributes['price_old'] = $value ? Util::reFormatNumber($value) : null;
} }
public function setValueCommissionAttribute( $value ) { public function setValueCommissionAttribute($value)
{
$this->attributes['value_commission'] = $value ? Util::reFormatNumber($value) : 0; $this->attributes['value_commission'] = $value ? Util::reFormatNumber($value) : 0;
} }
public function setPartnerCommissionAttribute( $value ) { public function setPartnerCommissionAttribute($value)
{
$this->attributes['partner_commission'] = $value ? Util::reFormatNumber($value) : 0; $this->attributes['partner_commission'] = $value ? Util::reFormatNumber($value) : 0;
} }
public function getFormattedPrice() public function getFormattedPrice()
{ {
return isset($this->attributes['price']) ? Util::formatNumber($this->attributes['price']) : ""; return isset($this->attributes['price']) ? Util::formatNumber($this->attributes['price']) : '';
} }
public function getFormattedPriceEk() public function getFormattedPriceEk()
{ {
return isset($this->attributes['price_ek']) ? Util::formatNumber($this->attributes['price_ek']) : ""; return isset($this->attributes['price_ek']) ? Util::formatNumber($this->attributes['price_ek']) : '';
} }
public function getFormattedTax($country = null) public function getFormattedTax($country = null)
{ {
return isset($this->attributes['tax']) ? Util::formatNumber($this->getTaxWith($country), 0) : ""; return isset($this->attributes['tax']) ? Util::formatNumber($this->getTaxWith($country), 0) : '';
} }
public function getFormattedPriceOld() public function getFormattedPriceOld()
{ {
return isset($this->attributes['price_old']) ? Util::formatNumber($this->attributes['price_old']) : ""; return isset($this->attributes['price_old']) ? Util::formatNumber($this->attributes['price_old']) : '';
} }
public function getFormattedValueCommission() public function getFormattedValueCommission()
{ {
return isset($this->attributes['value_commission']) ? Util::formatNumber($this->attributes['value_commission']) : 0; return isset($this->attributes['value_commission']) ? Util::formatNumber($this->attributes['value_commission']) : 0;
@ -376,9 +462,9 @@ class Product extends Model
return isset($this->attributes['partner_commission']) ? Util::formatNumber($this->attributes['partner_commission']) : 0; return isset($this->attributes['partner_commission']) ? Util::formatNumber($this->attributes['partner_commission']) : 0;
} }
/* price by user Factor */ /* price by user Factor */
private function calcPriceUserFactor($price){ private function calcPriceUserFactor($price)
{
/* /*
Nicht in benutzung, die margin errechnet sich im Warenkorb, wegen der Staffelprovision Nicht in benutzung, die margin errechnet sich im Warenkorb, wegen der Staffelprovision
if(\Auth::user() && \Auth::user()->user_level){ if(\Auth::user() && \Auth::user()->user_level){
@ -388,15 +474,19 @@ class Product extends Model
*/ */
return $price; return $price;
} }
/* price net */ /* price net */
private function calcPriceNet($price, $country=null){ private function calcPriceNet($price, $country = null)
{
$tax = $this->getTaxWith($country); $tax = $this->getTaxWith($country);
$tax_rate = ($tax + 100) / 100; $tax_rate = ($tax + 100) / 100;
return $price / $tax_rate; return $price / $tax_rate;
} }
// price calu with // price calu with
public function getPriceWith(Bool $net = true, Bool $ufactor = true, $country = null, $commission=false){ public function getPriceWith(bool $net = true, bool $ufactor = true, $country = null, $commission = false)
{
$price = isset($this->attributes['price']) ? $this->attributes['price'] : null; $price = isset($this->attributes['price']) ? $this->attributes['price'] : null;
/*$cprice = $country ? $this->getCPrice($country) : null; //eigener Preis für Land /*$cprice = $country ? $this->getCPrice($country) : null; //eigener Preis für Land
@ -405,24 +495,26 @@ class Product extends Model
$price = $net ? $this->calcPriceNet($price, $country) : $price; $price = $net ? $this->calcPriceNet($price, $country) : $price;
$price = $ufactor ? $this->calcPriceUserFactor($price) : $price; $price = $ufactor ? $this->calcPriceUserFactor($price) : $price;
$price = $commission ? $this->calcPriceUserCommission($price) : $price; $price = $commission ? $this->calcPriceUserCommission($price) : $price;
return round($price, 2); return round($price, 2);
} }
/* out */ /* out */
public function getFormattedPriceWith(Bool $net = true, Bool $ufactor = true) public function getFormattedPriceWith(bool $net = true, bool $ufactor = true)
{ {
return isset($this->attributes['price']) ? Util::formatNumber($this->getPriceWith($net, $ufactor)) : ""; return isset($this->attributes['price']) ? Util::formatNumber($this->getPriceWith($net, $ufactor)) : '';
} }
public function getTaxWith($country = null){ public function getTaxWith($country = null)
{
$tax = isset($this->attributes['tax']) ? $this->attributes['tax'] : null; $tax = isset($this->attributes['tax']) ? $this->attributes['tax'] : null;
$ctax = $country ? $this->getCTax($country) : null; $ctax = $country ? $this->getCTax($country) : null;
return $ctax !== null ? $ctax : $tax; return $ctax !== null ? $ctax : $tax;
} }
public function getBasePriceFormattedFull()
{
public function getBasePriceFormattedFull(){
if ($price = $this->getBasePrice()) { if ($price = $this->getBasePrice()) {
$unit = $this->attributes['unit']; $unit = $this->attributes['unit'];
// ml g // ml g
@ -434,17 +526,21 @@ class Product extends Model
return Util::formatNumber($price).' € / 1 '.$this->getUnitType(); return Util::formatNumber($price).' € / 1 '.$this->getUnitType();
} }
} }
return "";
return '';
} }
public function getBasePriceFormatted(){ public function getBasePriceFormatted()
{
if ($price = $this->getBasePrice()) { if ($price = $this->getBasePrice()) {
return Util::formatNumber($price); return Util::formatNumber($price);
} }
return "";
return '';
} }
public function getBasePrice(){ public function getBasePrice()
{
if (isset($this->attributes['unit']) && isset($this->attributes['contents_total']) && $this->attributes['contents_total'] != 0) { if (isset($this->attributes['unit']) && isset($this->attributes['contents_total']) && $this->attributes['contents_total'] != 0) {
$unit = $this->attributes['unit']; $unit = $this->attributes['unit'];
// ml g // ml g
@ -456,30 +552,38 @@ class Product extends Model
return $this->attributes['price'] * 1000 / $this->attributes['contents_total']; return $this->attributes['price'] * 1000 / $this->attributes['contents_total'];
} }
} }
return "";
return '';
} }
public function getUnitType(){
public function getUnitType()
{
return isset($this->unitTypes[$this->unit]) ? $this->unitTypes[$this->unit] : '-'; return isset($this->unitTypes[$this->unit]) ? $this->unitTypes[$this->unit] : '-';
} }
public function getShowAtType(){
public function getShowAtType()
{
return isset(Type::$showATs[$this->show_at]) ? Type::$showATs[$this->show_at] : '-'; return isset(Type::$showATs[$this->show_at]) ? Type::$showATs[$this->show_at] : '-';
} }
public function getShowOnTypes($seperator = false){ public function getShowOnTypes($seperator = false)
{
$ret = []; $ret = [];
if ($this->show_on && is_array($this->show_on)) { if ($this->show_on && is_array($this->show_on)) {
foreach ($this->show_on as $show) { foreach ($this->show_on as $show) {
$ret[] = isset(Type::$showONs[$show]) ? Type::$showONs[$show] : '-'; $ret[] = isset(Type::$showONs[$show]) ? Type::$showONs[$show] : '-';
} }
} }
return $seperator ? implode($seperator, $ret) : $ret; return $seperator ? implode($seperator, $ret) : $ret;
} }
public function setPosAttribute($value)
public function setPosAttribute($value){ {
$this->attributes['pos'] = is_numeric($value) ? $value : null; $this->attributes['pos'] = is_numeric($value) ? $value : null;
} }
public function getLang($key) public function getLang($key)
{ {
$lang = \App::getLocale(); $lang = \App::getLocale();
@ -490,6 +594,7 @@ class Product extends Model
if (! $trans || $trans == '') { if (! $trans || $trans == '') {
return $this->{$key}; return $this->{$key};
} }
return $trans; return $trans;
} }
@ -503,46 +608,58 @@ class Product extends Model
public function getTranNames() public function getTranNames()
{ {
$ret = ""; $ret = '';
foreach ((array) $this->trans_name as $value) { foreach ((array) $this->trans_name as $value) {
$ret .= $value.', '; $ret .= $value.', ';
} }
return rtrim($ret, ', '); return rtrim($ret, ', ');
} }
public function getCountryPrice($country_id){ public function getCountryPrice($country_id)
return $this->country_prices->where('country_id', '=', $country_id)->first() ?: new CountryPrice(); {
return $this->country_prices->where('country_id', '=', $country_id)->first() ?: new CountryPrice;
} }
public function getCPrice($country_id){ public function getCPrice($country_id)
{
return $this->getCountryPrice($country_id)->c_price; return $this->getCountryPrice($country_id)->c_price;
} }
public function getCTax($country_id){
public function getCTax($country_id)
{
return $this->getCountryPrice($country_id)->c_tax; return $this->getCountryPrice($country_id)->c_tax;
} }
public function getCPriceOld($country_id){
public function getCPriceOld($country_id)
{
return $this->getCountryPrice($country_id)->c_price_old; return $this->getCountryPrice($country_id)->c_price_old;
} }
public function getCCurrency($country_id){
public function getCCurrency($country_id)
{
return $this->getCountryPrice($country_id)->c_currency; return $this->getCountryPrice($country_id)->c_currency;
} }
public function getRealPrice(Country $country){ public function getRealPrice(Country $country)
{
if ($country->own_eur && $this->getCPrice($country->id)) { if ($country->own_eur && $this->getCPrice($country->id)) {
return $this->getCPrice($country->id); return $this->getCPrice($country->id);
} }
return $this->price; return $this->price;
} }
public function getFormattedPriceCurrencyWith(Bool $net = true, Bool $ufactor = true, Country $country = null, $commission = false){ public function getFormattedPriceCurrencyWith(bool $net = true, bool $ufactor = true, ?Country $country = null, $commission = false)
$ret = ""; {
$ret = '';
if ($country && isset($country->currency) && $country->currency) { if ($country && isset($country->currency) && $country->currency) {
$price = $this->getPriceWith($net, $ufactor, $country, $commission); $price = $this->getPriceWith($net, $ufactor, $country, $commission);
$ret = formatNumber($price * $country->currency_faktor)." ".$country->currency_unit; $ret = formatNumber($price * $country->currency_faktor).' '.$country->currency_unit;
return '<br><span class="small">~'.$ret.'<span>'; return '<br><span class="small">~'.$ret.'<span>';
} }
return "" ;
}
return '';
}
} }

View file

@ -19,7 +19,7 @@ use Illuminate\Database\Eloquent\Model;
* @property Carbon $updated_at * @property Carbon $updated_at
* @property Ingredient $ingredient * @property Ingredient $ingredient
* @property Product $product * @property Product $product
* @package App\Models *
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient newQuery() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient query() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient query()
@ -28,6 +28,7 @@ use Illuminate\Database\Eloquent\Model;
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient whereIngredientId($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient whereIngredientId($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient whereProductId($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient whereProductId($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient whereUpdatedAt($value)
*
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class ProductIngredient extends Model class ProductIngredient extends Model
@ -36,12 +37,19 @@ class ProductIngredient extends Model
protected $casts = [ protected $casts = [
'product_id' => 'int', 'product_id' => 'int',
'ingredient_id' => 'int' 'ingredient_id' => 'int',
'pos' => 'int',
'gram' => 'decimal:3',
'factor' => 'decimal:2',
]; ];
protected $fillable = [ protected $fillable = [
'product_id', 'product_id',
'ingredient_id' 'ingredient_id',
'pos',
'gram',
'factor',
'recipe_type',
]; ];
public function ingredient() public function ingredient()

72
app/Models/Production.php Normal file
View file

@ -0,0 +1,72 @@
<?php
namespace App\Models;
use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Production extends Model
{
protected $fillable = [
'product_id',
'location_id',
'produced_by',
'produced_at',
'quantity',
'notes',
'mhd_warning',
];
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'produced_at' => 'date',
'mhd_warning' => 'boolean',
];
}
/**
* @return BelongsTo<Product, $this>
*/
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
/**
* @return BelongsTo<Location, $this>
*/
public function location(): BelongsTo
{
return $this->belongsTo(Location::class);
}
/**
* @return BelongsTo<User, $this>
*/
public function producedByUser(): BelongsTo
{
return $this->belongsTo(User::class, 'produced_by');
}
/**
* @return HasMany<ProductionIngredient, $this>
*/
public function productionIngredients(): HasMany
{
return $this->hasMany(ProductionIngredient::class);
}
/**
* @return HasMany<ProductionPackaging, $this>
*/
public function productionPackagings(): HasMany
{
return $this->hasMany(ProductionPackaging::class);
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ProductionIngredient extends Model
{
protected $fillable = [
'production_id',
'ingredient_id',
'stock_entry_id',
'quantity_used',
];
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'quantity_used' => 'decimal:2',
];
}
/**
* @return BelongsTo<Production, $this>
*/
public function production(): BelongsTo
{
return $this->belongsTo(Production::class);
}
/**
* @return BelongsTo<Ingredient, $this>
*/
public function ingredient(): BelongsTo
{
return $this->belongsTo(Ingredient::class);
}
/**
* @return BelongsTo<StockEntry, $this>
*/
public function stockEntry(): BelongsTo
{
return $this->belongsTo(StockEntry::class);
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ProductionPackaging extends Model
{
protected $fillable = [
'production_id',
'packaging_item_id',
'quantity_used',
];
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'quantity_used' => 'integer',
];
}
/**
* @return BelongsTo<Production, $this>
*/
public function production(): BelongsTo
{
return $this->belongsTo(Production::class);
}
/**
* @return BelongsTo<PackagingItem, $this>
*/
public function packagingItem(): BelongsTo
{
return $this->belongsTo(PackagingItem::class);
}
}

View file

@ -2,8 +2,11 @@
namespace App\Models; namespace App\Models;
use App\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
/** /**
* App\Models\ShoppingOrder * App\Models\ShoppingOrder
@ -22,16 +25,17 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property int|null $weight * @property int|null $weight
* @property int|null $paid * @property int|null $paid
* @property string|null $txaction * @property string|null $txaction
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property-read \App\User|null $auth_user * @property-read User|null $auth_user
* @property-read \App\Models\Country $country * @property-read Country $country
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ShoppingOrderItem[] $shopping_order_items * @property-read Collection|ShoppingOrderItem[] $shopping_order_items
* @property-read int|null $shopping_order_items_count * @property-read int|null $shopping_order_items_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ShoppingPayment[] $shopping_payments * @property-read Collection|ShoppingPayment[] $shopping_payments
* @property-read int|null $shopping_payments_count * @property-read int|null $shopping_payments_count
* @property-read \App\Models\ShoppingUser $shopping_user * @property-read ShoppingUser $shopping_user
* @property-read \App\Models\UserShop $user_shop * @property-read UserShop $user_shop
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder newQuery() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder query() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder query()
@ -51,62 +55,93 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereUserShopId($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereUserShopId($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereWeight($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereWeight($value)
*
* @property int|null $payment_for * @property int|null $payment_for
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder wherePaymentFor($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder wherePaymentFor($value)
*
* @property int|null $member_id * @property int|null $member_id
* @property string|null $mode * @property string|null $mode
* @property-read \App\User|null $member * @property-read User|null $member
* @property-read \App\Models\UserHistory|null $user_history * @property-read UserHistory|null $user_history
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereMemberId($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereMemberId($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereMode($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereMode($value)
* @property \Illuminate\Support\Carbon|null $deleted_at *
* @property Carbon|null $deleted_at
* @property string|null $user_deleted_at * @property string|null $user_deleted_at
*
* @method static \Illuminate\Database\Query\Builder|\App\Models\ShoppingOrder onlyTrashed() * @method static \Illuminate\Database\Query\Builder|\App\Models\ShoppingOrder onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereDeletedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereUserDeletedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereUserDeletedAt($value)
* @method static \Illuminate\Database\Query\Builder|\App\Models\ShoppingOrder withTrashed() * @method static \Illuminate\Database\Query\Builder|\App\Models\ShoppingOrder withTrashed()
* @method static \Illuminate\Database\Query\Builder|\App\Models\ShoppingOrder withoutTrashed() * @method static \Illuminate\Database\Query\Builder|\App\Models\ShoppingOrder withoutTrashed()
* @property-read \App\Models\ShippingCountry $shipping_country *
* @property-read ShippingCountry $shipping_country
* @property float|null $shipping_net * @property float|null $shipping_net
* @property float|null $subtotal_shipping * @property float|null $subtotal_shipping
* @property int|null $points * @property int|null $points
* @property int|null $shipped * @property int|null $shipped
* @property string|null $tracking * @property string|null $tracking
* @property string|null $wp_invoice_path * @property string|null $wp_invoice_path
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder wherePoints($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder wherePoints($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereShipped($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereShipped($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereShippingNet($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereShippingNet($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereSubtotalWs($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereSubtotalWs($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereTracking($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereTracking($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereWpInvoicePath($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereWpInvoicePath($value)
*
* @property array|null $wp_notice * @property array|null $wp_notice
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereWpNotice($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereWpNotice($value)
*
* @property string|null $subtotal_full * @property string|null $subtotal_full
* @property-read \App\Models\ShoppingOrderMargin|null $shopping_order_margin * @property-read ShoppingOrderMargin|null $shopping_order_margin
* @property-read \App\Models\ShoppingPayment|null $shopping_payment_last * @property-read ShoppingPayment|null $shopping_payment_last
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereSubtotalFull($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereSubtotalFull($value)
*
* @property string|null $discount * @property string|null $discount
* @property string|null $payment_credit * @property string|null $payment_credit
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereDiscount($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereDiscount($value)
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder wherePaymentCredit($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder wherePaymentCredit($value)
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereSubtotalShipping($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereSubtotalShipping($value)
*
* @property string|null $total_without_credit * @property string|null $total_without_credit
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereTotalWithoutCredit($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereTotalWithoutCredit($value)
*
* @property array|null $invoice * @property array|null $invoice
* @property \Illuminate\Support\Carbon|null $shipped_at * @property Carbon|null $shipped_at
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereInvoice($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereInvoice($value)
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereShippedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereShippedAt($value)
*
* @property string|null $invoice_number * @property string|null $invoice_number
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereInvoiceNumber($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereInvoiceNumber($value)
*
* @property int|null $promotion_user_id * @property int|null $promotion_user_id
* @property string|null $shipping_option * @property string|null $shipping_option
* @property-read \App\Models\PromotionUser|null $promotion_user * @property-read PromotionUser|null $promotion_user
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder wherePromotionUserId($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder wherePromotionUserId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereShippingOption($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereShippingOption($value)
*
* @property array|null $delivery * @property array|null $delivery
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereDelivery($value) * @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereDelivery($value)
*
* @property bool $user_white_label * @property bool $user_white_label
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShoppingOrder whereUserWhiteLabel($value) * @method static \Illuminate\Database\Eloquent\Builder<static>|ShoppingOrder whereUserWhiteLabel($value)
*
* @property array<array-key, mixed>|null $cancellation_invoice
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShoppingOrder whereCancellationInvoice($value)
*
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class ShoppingOrder extends Model class ShoppingOrder extends Model
@ -114,6 +149,7 @@ class ShoppingOrder extends Model
protected $table = 'shopping_orders'; protected $table = 'shopping_orders';
use SoftDeletes; use SoftDeletes;
protected $dates = ['deleted_at']; protected $dates = ['deleted_at'];
protected $fillable = [ protected $fillable = [
@ -139,6 +175,7 @@ class ShoppingOrder extends Model
'weight', 'weight',
'paid', 'paid',
'invoice', 'invoice',
'cancellation_invoice',
'delivery', 'delivery',
'user_white_label', 'user_white_label',
'invoice_number', 'invoice_number',
@ -155,6 +192,7 @@ class ShoppingOrder extends Model
protected $casts = [ protected $casts = [
'wp_notice' => 'array', 'wp_notice' => 'array',
'invoice' => 'array', 'invoice' => 'array',
'cancellation_invoice' => 'array',
'delivery' => 'array', 'delivery' => 'array',
'shipped_at' => 'datetime', 'shipped_at' => 'datetime',
'user_white_label' => 'boolean', 'user_white_label' => 'boolean',
@ -167,7 +205,7 @@ class ShoppingOrder extends Model
3 => 'abgeschlossen', 3 => 'abgeschlossen',
5 => 'Wartestellung', 5 => 'Wartestellung',
4 => 'Abholung', 4 => 'Abholung',
10 => 'storniert' 10 => 'storniert',
]; ];
public static $paymentForTypes = [ public static $paymentForTypes = [
@ -182,7 +220,7 @@ class ShoppingOrder extends Model
8 => 'Shop', 8 => 'Shop',
9 => '-', 9 => '-',
10 => 'extern', 10 => 'extern',
11 => '' 11 => '',
]; ];
public static $paymentForColors = [ public static $paymentForColors = [
@ -198,18 +236,17 @@ class ShoppingOrder extends Model
8 => 'info', 8 => 'info',
9 => 'default', 9 => 'default',
10 => 'info', 10 => 'info',
11 => 'default' 11 => 'default',
]; ];
public static $apiShippedTypes = [ public static $apiShippedTypes = [
0 => 'open', // (Fullfilment durch Händler)', 0 => 'open', // (Fullfilment durch Händler)',
1 => 'process', // (Fullfilment: nicht Versand) 1 => 'process', // (Fullfilment: nicht Versand)
2 => 'sent', // (Fullfilment: Versand erfolgt)' 2 => 'sent', // (Fullfilment: Versand erfolgt)'
3 => 'close', // (Fullfilment: Versand erfolgt)', 3 => 'close', // (Fullfilment: Versand erfolgt)',
4 => 'pick_up', // (Fullfilment: Versand erfolgt)', 4 => 'pick_up', // (Fullfilment: Versand erfolgt)',
10 => 'cancel' 10 => 'cancel',
]; ];
public static $shippedColors = [ public static $shippedColors = [
@ -252,6 +289,7 @@ class ShoppingOrder extends Model
{ {
return $this->belongsTo('App\User', 'member_id'); return $this->belongsTo('App\User', 'member_id');
} }
// can null // can null
public function auth_user() public function auth_user()
{ {
@ -268,19 +306,35 @@ class ShoppingOrder extends Model
return $this->hasOne('App\Models\ShoppingOrderMargin', 'shopping_order_id')->latest(); return $this->hasOne('App\Models\ShoppingOrderMargin', 'shopping_order_id')->latest();
} }
public function shopping_order_items(){ public function shopping_order_items()
{
return $this->hasMany('App\Models\ShoppingOrderItem', 'shopping_order_id'); return $this->hasMany('App\Models\ShoppingOrderItem', 'shopping_order_id');
} }
public function shopping_payments(){ /**
* Mindestens eine Bestellzeile mit White-Label-Produkt (Lieferschein mit Etikett-Infos).
*/
public function hasWhitelabelProducts(): bool
{
return $this->shopping_order_items()
->whereHas('product', function ($query): void {
$query->where('whitelabel', true);
})
->exists();
}
public function shopping_payments()
{
return $this->hasMany('App\Models\ShoppingPayment', 'shopping_order_id'); return $this->hasMany('App\Models\ShoppingPayment', 'shopping_order_id');
} }
public function shopping_payment_last(){ public function shopping_payment_last()
{
return $this->hasOne('App\Models\ShoppingPayment', 'shopping_order_id')->latest(); return $this->hasOne('App\Models\ShoppingPayment', 'shopping_order_id')->latest();
} }
public function setUserHistoryValue($values = []){ public function setUserHistoryValue($values = [])
{
if ($user_history = $this->user_history) { if ($user_history = $this->user_history) {
foreach ($values as $key => $val) { foreach ($values as $key => $val) {
$user_history->{$key} = $val; $user_history->{$key} = $val;
@ -289,7 +343,8 @@ class ShoppingOrder extends Model
} }
} }
public function getLastShoppingPayment($key=false){ public function getLastShoppingPayment($key = false)
{
$shopping_payment = $this->shopping_payments->last(); $shopping_payment = $this->shopping_payments->last();
if ($shopping_payment) { if ($shopping_payment) {
if ($key === 'getPaymentType') { if ($key === 'getPaymentType') {
@ -299,26 +354,33 @@ class ShoppingOrder extends Model
return $shopping_payment->reference; return $shopping_payment->reference;
} }
} }
return "";
return '';
} }
public function getShippedType(){ public function getShippedType()
return isset(self::$shippedTypes[$this->shipped]) ? self::$shippedTypes[$this->shipped] : ""; {
return isset(self::$shippedTypes[$this->shipped]) ? self::$shippedTypes[$this->shipped] : '';
} }
public function getAPIShippedType(){ public function getAPIShippedType()
return isset(self::$apiShippedTypes[$this->shipped]) ? self::$apiShippedTypes[$this->shipped] : "free"; {
return isset(self::$apiShippedTypes[$this->shipped]) ? self::$apiShippedTypes[$this->shipped] : 'free';
} }
public function getShippedColor(){ public function getShippedColor()
return isset(self::$shippedColors[$this->shipped]) ? self::$shippedColors[$this->shipped] : "default"; {
return isset(self::$shippedColors[$this->shipped]) ? self::$shippedColors[$this->shipped] : 'default';
} }
public function getPaymentForType(){ public function getPaymentForType()
return isset(self::$paymentForTypes[$this->payment_for]) ? self::$paymentForTypes[$this->payment_for] : ""; {
return isset(self::$paymentForTypes[$this->payment_for]) ? self::$paymentForTypes[$this->payment_for] : '';
} }
public function getPaymentForColor(){
return isset(self::$paymentForColors[$this->payment_for]) ? self::$paymentForColors[$this->payment_for] : ""; public function getPaymentForColor()
{
return isset(self::$paymentForColors[$this->payment_for]) ? self::$paymentForColors[$this->payment_for] : '';
} }
public function getFormattedTotal() public function getFormattedTotal()
@ -360,39 +422,46 @@ class ShoppingOrder extends Model
{ {
return formatNumber($this->attributes['tax']); return formatNumber($this->attributes['tax']);
} }
public function getFormattedTotalWithoutCredit() public function getFormattedTotalWithoutCredit()
{ {
return formatNumber($this->attributes['total_without_credit']); return formatNumber($this->attributes['total_without_credit']);
} }
public function getFormattedPaymentCredit() public function getFormattedPaymentCredit()
{ {
return formatNumber($this->attributes['payment_credit']); return formatNumber($this->attributes['payment_credit']);
} }
public function getFormattedTotalShipping() public function getFormattedTotalShipping()
{ {
return formatNumber($this->attributes['total_shipping']); return formatNumber($this->attributes['total_shipping']);
} }
public function getItemsCount(){ public function getItemsCount()
{
$count = 0; $count = 0;
if ($this->shopping_order_items) { if ($this->shopping_order_items) {
foreach ($this->shopping_order_items as $shopping_order_item) { foreach ($this->shopping_order_items as $shopping_order_item) {
$count += $shopping_order_item->qty; $count += $shopping_order_item->qty;
} }
} }
return $count; return $count;
} }
public function isInvoice(){ public function isInvoice()
{
return $this->user_invoice ? true : false; return $this->user_invoice ? true : false;
} }
public function isPickUp(){ public function isPickUp()
{
return $this->shipping_option === 'pick_up' ? true : false; return $this->shipping_option === 'pick_up' ? true : false;
} }
public function isTax(){ public function isTax()
{
return $this->tax > 0 ? true : false; return $this->tax > 0 ? true : false;
} }
} }

View file

@ -3,7 +3,9 @@
namespace App\Models; namespace App\Models;
use App\Services\Util; use App\Services\Util;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
/** /**
* App\Models\ShoppingPayment * App\Models\ShoppingPayment
@ -18,11 +20,12 @@ use Illuminate\Database\Eloquent\Model;
* @property string $currency * @property string $currency
* @property string|null $status * @property string|null $status
* @property string|null $txaction * @property string|null $txaction
* @property \Illuminate\Support\Carbon|null $created_at * @property Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\PaymentTransaction[] $payment_transactions * @property-read Collection|PaymentTransaction[] $payment_transactions
* @property-read int|null $payment_transactions_count * @property-read int|null $payment_transactions_count
* @property-read \App\Models\ShoppingOrder $shopping_order * @property-read ShoppingOrder $shopping_order
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment newQuery() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment query() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment query()
@ -38,8 +41,17 @@ use Illuminate\Database\Eloquent\Model;
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment whereTxaction($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment whereTxaction($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment whereWallettype($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment whereWallettype($value)
*
* @property string|null $mode * @property string|null $mode
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment whereMode($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment whereMode($value)
*
* @property int|null $reminder
* @property Carbon|null $reminder_date
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShoppingPayment whereReminder($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShoppingPayment whereReminderDate($value)
*
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class ShoppingPayment extends Model class ShoppingPayment extends Model
@ -58,7 +70,7 @@ class ShoppingPayment extends Model
'reminder', 'reminder',
'reminder_date', 'reminder_date',
'txaction', 'txaction',
'mode' 'mode',
]; ];
protected $casts = [ protected $casts = [
@ -66,7 +78,6 @@ class ShoppingPayment extends Model
'reminder_date' => 'datetime', 'reminder_date' => 'datetime',
]; ];
public function shopping_order() public function shopping_order()
{ {
return $this->belongsTo('App\Models\ShoppingOrder', 'shopping_order_id'); return $this->belongsTo('App\Models\ShoppingOrder', 'shopping_order_id');
@ -77,7 +88,8 @@ class ShoppingPayment extends Model
return $this->hasMany('App\Models\PaymentTransaction', 'shopping_payment_id'); return $this->hasMany('App\Models\PaymentTransaction', 'shopping_payment_id');
} }
public function getPaymentType(){ public function getPaymentType()
{
if ($this->clearingtype === 'pp') { if ($this->clearingtype === 'pp') {
return 'PayPal'; return 'PayPal';
@ -100,10 +112,12 @@ class ShoppingPayment extends Model
if ($this->clearingtype === 'non') { if ($this->clearingtype === 'non') {
return 'keine'; return 'keine';
} }
return 'keine'; return 'keine';
} }
public function getPaymentAmount(){ public function getPaymentAmount()
return Util::formatNumber($this->amount/100)." ".$this->currency; {
return Util::formatNumber($this->amount / 100).' '.$this->currency;
} }
} }

118
app/Models/StockEntry.php Normal file
View file

@ -0,0 +1,118 @@
<?php
namespace App\Models;
use App\User;
use Database\Factories\StockEntryFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class StockEntry extends Model
{
/** @use HasFactory<StockEntryFactory> */
use HasFactory;
protected $fillable = [
'entry_type',
'ingredient_id',
'packaging_item_id',
'supplier_id',
'location_id',
'unit',
'ordered_by',
'ordered_at',
'ordered_quantity',
'price_per_kg',
'price_total',
'received_by',
'received_at',
'received_quantity',
'batch_number',
'best_before',
'quality_id',
'status',
];
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'ordered_at' => 'date',
'received_at' => 'date',
'best_before' => 'date',
'ordered_quantity' => 'decimal:2',
'received_quantity' => 'decimal:2',
'price_per_kg' => 'decimal:4',
'price_total' => 'decimal:4',
];
}
/**
* @return BelongsTo<Ingredient, $this>
*/
public function ingredient(): BelongsTo
{
return $this->belongsTo(Ingredient::class);
}
/**
* @return BelongsTo<PackagingItem, $this>
*/
public function packagingItem(): BelongsTo
{
return $this->belongsTo(PackagingItem::class);
}
/**
* @return BelongsTo<Supplier, $this>
*/
public function supplier(): BelongsTo
{
return $this->belongsTo(Supplier::class);
}
/**
* @return BelongsTo<Location, $this>
*/
public function location(): BelongsTo
{
return $this->belongsTo(Location::class);
}
/**
* @return BelongsTo<MaterialQuality, $this>
*/
public function quality(): BelongsTo
{
return $this->belongsTo(MaterialQuality::class, 'quality_id');
}
/**
* @return BelongsTo<User, $this>
*/
public function orderedByUser(): BelongsTo
{
return $this->belongsTo(User::class, 'ordered_by');
}
/**
* @return BelongsTo<User, $this>
*/
public function receivedByUser(): BelongsTo
{
return $this->belongsTo(User::class, 'received_by');
}
public function isPending(): bool
{
return $this->status === 'pending';
}
public function isReceived(): bool
{
return $this->status === 'received';
}
}

67
app/Models/Supplier.php Normal file
View file

@ -0,0 +1,67 @@
<?php
namespace App\Models;
use Database\Factories\SupplierFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Supplier extends Model
{
/** @use HasFactory<SupplierFactory> */
use HasFactory, SoftDeletes;
protected $fillable = [
'name',
'url',
'contact_person',
'email',
'phone',
'country_id',
'notes',
'active',
];
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'active' => 'boolean',
];
}
/**
* @return BelongsTo<Country, $this>
*/
public function country(): BelongsTo
{
return $this->belongsTo(Country::class);
}
/**
* @return BelongsToMany<SupplierCategory, $this>
*/
public function supplierCategories(): BelongsToMany
{
return $this->belongsToMany(
SupplierCategory::class,
'supplier_supplier_category',
'supplier_id',
'supplier_category_id'
)->withTimestamps();
}
/**
* @return HasMany<PackagingItem, $this>
*/
public function packagingItems(): HasMany
{
return $this->hasMany(PackagingItem::class);
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Database\Factories\SupplierCategoryFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class SupplierCategory extends Model
{
/** @use HasFactory<SupplierCategoryFactory> */
use HasFactory;
protected $fillable = [
'name',
'pos',
];
/**
* @return BelongsToMany<Supplier, $this>
*/
public function suppliers(): BelongsToMany
{
return $this->belongsToMany(
Supplier::class,
'supplier_supplier_category',
'supplier_category_id',
'supplier_id'
)->withTimestamps();
}
}

View file

@ -2,7 +2,9 @@
namespace App\Providers; namespace App\Providers;
use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
@ -15,6 +17,7 @@ class AppServiceProvider extends ServiceProvider
public function boot() public function boot()
{ {
Schema::defaultStringLength(191); Schema::defaultStringLength(191);
URL::forceScheme('https');
} }
/** /**
@ -26,7 +29,7 @@ class AppServiceProvider extends ServiceProvider
{ {
if ($this->app->environment() !== 'production') { if ($this->app->environment() !== 'production') {
$this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class); $this->app->register(IdeHelperServiceProvider::class);
} }
// ... // ...
} }

View file

@ -0,0 +1,44 @@
<?php
namespace App\Repositories;
use App\Models\PackagingItem;
class PackagingItemRepository
{
/**
* @param array<string, mixed> $data
*/
public function create(array $data): PackagingItem
{
return PackagingItem::create($this->extractAttributes($data));
}
/**
* @param array<string, mixed> $data
*/
public function update(PackagingItem $packagingItem, array $data): PackagingItem
{
$packagingItem->update($this->extractAttributes($data));
return $packagingItem->fresh();
}
/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
protected function extractAttributes(array $data): array
{
return collect($data)->only([
'packaging_material_id',
'supplier_id',
'name',
'category',
'weight_grams',
'min_stock_alert',
'product_id',
'active',
])->all();
}
}

View file

@ -2,26 +2,23 @@
namespace App\Repositories; namespace App\Repositories;
use App\Models\CountryPrice;
use App\Models\Attribute; use App\Models\Attribute;
use App\Models\CountryPrice;
use App\Models\Ingredient;
use App\Models\Product; use App\Models\Product;
use App\Models\ProductAttribute; use App\Models\ProductAttribute;
use App\Models\ProductCategory; use App\Models\ProductCategory;
use App\Models\ProductImage; use App\Models\ProductImage;
use App\Models\ProductIngredient; use App\Models\ProductIngredient;
use App\Services\Slim;
class ProductRepository extends BaseRepository
class ProductRepository extends BaseRepository { {
public function __construct(Product $model) public function __construct(Product $model)
{ {
$this->model = $model; $this->model = $model;
} }
/** /**
* refresh. * refresh.
*/ */
@ -37,52 +34,204 @@ class ProductRepository extends BaseRepository {
$data['max_buy'] = isset($data['max_buy']) ? 1 : 0; $data['max_buy'] = isset($data['max_buy']) ? 1 : 0;
$data['show_on'] = isset($data['show_on']) ? $data['show_on'] : null; $data['show_on'] = isset($data['show_on']) ? $data['show_on'] : null;
if($data['id'] === "new"){ if (array_key_exists('shelf_life_type', $data)) {
$this->model = Product::create($data); if ($data['shelf_life_type'] === '' || $data['shelf_life_type'] === null) {
$data['shelf_life_type'] = null;
$data['shelf_life_months'] = null;
} elseif ($data['shelf_life_type'] === 'pao') {
$data['shelf_life_months'] = null;
} elseif ($data['shelf_life_type'] === 'fixed' && array_key_exists('shelf_life_months', $data) && $data['shelf_life_months'] !== '' && $data['shelf_life_months'] !== null) {
$data['shelf_life_months'] = (int) $data['shelf_life_months'];
} }
else{ }
if ($data['id'] === 'new') {
$this->model = Product::create($data);
} else {
$this->model = $this->getById($data['id']); $this->model = $this->getById($data['id']);
$this->model->fill($data); $this->model->fill($data);
$this->model->save(); $this->model->save();
} }
$this->updateCategories(isset($data['categories']) ? $data['categories'] : []); $this->updateCategories(isset($data['categories']) ? $data['categories'] : []);
$this->updateAttributes(isset($data['attributes']) ? $data['attributes'] : []); $this->updateAttributes(isset($data['attributes']) ? $data['attributes'] : []);
$this->updateWLVariants(isset($data['whitelabel_variants']) ? $data['whitelabel_variants'] : []); $this->updateWLVariants(isset($data['whitelabel_variants']) ? $data['whitelabel_variants'] : []);
$this->updateWLImageAttributs(isset($data['image_wl_attributes']) ? $data['image_wl_attributes'] : [], isset($data['whitelabel_variants']) ? $data['whitelabel_variants'] : []); $this->updateWLImageAttributs(isset($data['image_wl_attributes']) ? $data['image_wl_attributes'] : [], isset($data['whitelabel_variants']) ? $data['whitelabel_variants'] : []);
$this->updateIngredients(isset($data['product_ingredients']) ? $data['product_ingredients'] : []); $this->updateIngredients($data);
$this->updateManufacturerIngredients($data);
$this->updatePackagings($data);
$this->updateCountryPrices($data); $this->updateCountryPrices($data);
return $this->model; return $this->model;
} }
public function updatePackagings(array $data = []): bool
public function updateIngredients($data = array())
{ {
$ProductIngredient = $this->model->p_ingredients()->pluck('ingredient_id')->toArray(); if (! isset($data['pp_packaging_item_id']) || ! is_array($data['pp_packaging_item_id'])) {
//set attr $this->model->packagings()->detach();
if(is_array($data)){
foreach ($data as $id) {
//not use
if(!in_array($id, $ProductIngredient)){
ProductIngredient::create([
'product_id' => $this->model->id,
'ingredient_id' => $id,
]);
}
}
}
return true; return true;
} }
public function updateCategories($data = array()) $ids = $data['pp_packaging_item_id'];
$quantities = $data['pp_quantity'] ?? [];
$syncData = [];
foreach ($ids as $index => $packagingItemId) {
$pid = (int) $packagingItemId;
if ($pid <= 0) {
continue;
}
$qtyRaw = $quantities[$index] ?? null;
$qty = $this->parseNullableDecimal($qtyRaw);
if ($qty === null || $qty <= 0) {
$qty = 1.0;
}
$syncData[$pid] = [
'quantity' => $qty,
'pos' => (int) $index,
];
}
$this->model->packagings()->sync($syncData);
return true;
}
public function updateIngredients(array $data = []): bool
{
if (! array_key_exists('product_inci_sync_sent', $data)) {
if (isset($data['product_ingredients']) && is_array($data['product_ingredients'])) {
$ids = array_values(array_unique(array_filter(array_map('intval', $data['product_ingredients']), fn (int $id) => $id > 0)));
$defaults = Ingredient::whereIn('id', $ids)->pluck('default_factor', 'id');
ProductIngredient::where('product_id', $this->model->id)
->where('recipe_type', 'product')
->delete();
foreach ($ids as $index => $ingredientId) {
$factor = $defaults[$ingredientId] ?? 1.10;
ProductIngredient::create([
'product_id' => $this->model->id,
'ingredient_id' => $ingredientId,
'pos' => $index,
'gram' => null,
'factor' => $factor,
'recipe_type' => 'product',
]);
}
}
return true;
}
if (isset($data['pi_ingredient_id']) && is_array($data['pi_ingredient_id'])) {
$ids = $data['pi_ingredient_id'];
$grams = $data['pi_gram'] ?? [];
$factors = $data['pi_factor'] ?? [];
ProductIngredient::where('product_id', $this->model->id)
->where('recipe_type', 'product')
->delete();
foreach ($ids as $index => $ingredientId) {
$ingredientId = (int) $ingredientId;
if ($ingredientId <= 0) {
continue;
}
ProductIngredient::create([
'product_id' => $this->model->id,
'ingredient_id' => $ingredientId,
'pos' => $index,
'gram' => $this->parseNullableDecimal($grams[$index] ?? null),
'factor' => $this->parseFactor($factors[$index] ?? null, $ingredientId),
'recipe_type' => 'product',
]);
}
return true;
}
ProductIngredient::where('product_id', $this->model->id)
->where('recipe_type', 'product')
->delete();
return true;
}
public function updateManufacturerIngredients(array $data = []): bool
{
if (! array_key_exists('manufacturer_inci_sync_sent', $data)) {
return true;
}
if (isset($data['mfg_ingredient_id']) && is_array($data['mfg_ingredient_id'])) {
$ids = $data['mfg_ingredient_id'];
$grams = $data['mfg_gram'] ?? [];
$factors = $data['mfg_factor'] ?? [];
ProductIngredient::where('product_id', $this->model->id)
->where('recipe_type', 'manufacturer')
->delete();
foreach ($ids as $index => $ingredientId) {
$ingredientId = (int) $ingredientId;
if ($ingredientId <= 0) {
continue;
}
ProductIngredient::create([
'product_id' => $this->model->id,
'ingredient_id' => $ingredientId,
'pos' => $index,
'gram' => $this->parseNullableDecimal($grams[$index] ?? null),
'factor' => $this->parseFactor($factors[$index] ?? null, $ingredientId),
'recipe_type' => 'manufacturer',
]);
}
return true;
}
ProductIngredient::where('product_id', $this->model->id)
->where('recipe_type', 'manufacturer')
->delete();
return true;
}
private function parseNullableDecimal(mixed $value): ?float
{
if ($value === null || $value === '') {
return null;
}
if (is_numeric($value)) {
return (float) $value;
}
$normalized = reFormatNumber((string) $value);
return $normalized !== null && $normalized !== '' ? (float) $normalized : null;
}
private function parseFactor(mixed $value, int $ingredientId): float
{
if ($value === null || $value === '') {
$default = Ingredient::whereKey($ingredientId)->value('default_factor');
return $default !== null ? (float) $default : 1.10;
}
if (is_numeric($value)) {
return (float) $value;
}
$normalized = reFormatNumber((string) $value);
return $normalized !== null && $normalized !== '' ? (float) $normalized : 1.10;
}
public function updateCategories($data = [])
{ {
foreach ($this->model->categories as $category) { foreach ($this->model->categories as $category) {
if(($pos = array_search($category->category_id, $data)) !== FALSE){ if (($pos = array_search($category->category_id, $data)) !== false) {
unset($data[$pos]); unset($data[$pos]);
} else { } else {
$category->delete(); $category->delete();
@ -97,13 +246,14 @@ class ProductRepository extends BaseRepository {
]); ]);
} }
} }
return true; return true;
} }
public function updateAttributes($data = array()) public function updateAttributes($data = [])
{ {
foreach ($this->model->attributes as $attribute) { foreach ($this->model->attributes as $attribute) {
if(($pos = array_search($attribute->attribute_id, $data)) !== FALSE){ if (($pos = array_search($attribute->attribute_id, $data)) !== false) {
unset($data[$pos]); unset($data[$pos]);
} else { } else {
$attribute->delete(); $attribute->delete();
@ -120,13 +270,14 @@ class ProductRepository extends BaseRepository {
]); ]);
} }
} }
return true; return true;
} }
public function updateWLVariants($data = array()) public function updateWLVariants($data = [])
{ {
foreach ($this->model->attribute_variants as $variant) { foreach ($this->model->attribute_variants as $variant) {
if(($pos = array_search($variant->attribute_id, $data)) !== FALSE){ if (($pos = array_search($variant->attribute_id, $data)) !== false) {
unset($data[$pos]); unset($data[$pos]);
} else { } else {
$variant->delete(); $variant->delete();
@ -143,6 +294,7 @@ class ProductRepository extends BaseRepository {
]); ]);
} }
} }
return true; return true;
} }
@ -158,16 +310,13 @@ class ProductRepository extends BaseRepository {
} }
foreach ($this->model->whitelabel_images as $image) { foreach ($this->model->whitelabel_images as $image) {
$image->update([ $image->update([
'attributes' => isset($attributes[$image->id]) ? $attributes[$image->id] : NULL, 'attributes' => isset($attributes[$image->id]) ? $attributes[$image->id] : null,
]); ]);
} }
return true; return true;
} }
public function updateCountryPrices($data) public function updateCountryPrices($data)
{ {
if (! isset($data['country_prices']) || ! is_array($data['country_prices'])) { if (! isset($data['country_prices']) || ! is_array($data['country_prices'])) {
@ -186,16 +335,13 @@ class ProductRepository extends BaseRepository {
]); ]);
} }
return true; return true;
} }
public function copy($model) public function copy($model)
{ {
$this->model = $model->replicate(); $this->model = $model->replicate();
$this->model->name = "Kopie: ".$this->model->name; $this->model->name = 'Kopie: '.$this->model->name;
$this->model->wp_number = null; $this->model->wp_number = null;
$this->model->save(); $this->model->save();
@ -211,25 +357,44 @@ class ProductRepository extends BaseRepository {
foreach ($model->attributes as $attribute) { foreach ($model->attributes as $attribute) {
ProductAttribute::create([ ProductAttribute::create([
'product_id' => $this->model->id, 'product_id' => $this->model->id,
'type_id' => $this->model->attribute_type_id, 'type_id' => $attribute->type_id,
'attribute_id' => $attribute->attribute_id, 'attribute_id' => $attribute->attribute_id,
]); ]);
} }
//INCS foreach ($model->p_ingredients()->orderByPivot('pos')->get() as $ing) {
$ingredients = $model->p_ingredients()->pluck('ingredient_id')->toArray();
if(is_array($ingredients)){
foreach ($ingredients as $incs_id){
ProductIngredient::create([ ProductIngredient::create([
'product_id' => $this->model->id, 'product_id' => $this->model->id,
'ingredient_id' => $incs_id, 'ingredient_id' => $ing->id,
'pos' => (int) ($ing->pivot->pos ?? 0),
'gram' => $ing->pivot->gram,
'factor' => $ing->pivot->factor !== null ? (float) $ing->pivot->factor : 1.10,
'recipe_type' => 'product',
]); ]);
} }
foreach ($model->manufacturer_ingredients()->orderByPivot('pos')->get() as $ing) {
ProductIngredient::create([
'product_id' => $this->model->id,
'ingredient_id' => $ing->id,
'pos' => (int) ($ing->pivot->pos ?? 0),
'gram' => $ing->pivot->gram,
'factor' => $ing->pivot->factor !== null ? (float) $ing->pivot->factor : 1.10,
'recipe_type' => 'manufacturer',
]);
} }
$packSync = [];
foreach ($model->packagings()->orderByPivot('pos')->get() as $pack) {
$packSync[$pack->id] = [
'quantity' => $pack->pivot->quantity !== null ? (float) $pack->pivot->quantity : 1.0,
'pos' => (int) ($pack->pivot->pos ?? 0),
];
}
$this->model->packagings()->sync($packSync);
// images // images
foreach ($model->images as $image) { foreach ($model->images as $image) {
$name = \App\Services\Slim::sanitizeFileName($image->original_name); $name = Slim::sanitizeFileName($image->original_name);
$name = uniqid().'_'.$name; $name = uniqid().'_'.$name;
// copy // copy
@ -246,7 +411,7 @@ class ProductRepository extends BaseRepository {
'ext' => $image->ext, 'ext' => $image->ext,
'mine' => $image->mine, 'mine' => $image->mine,
'size' => $image->size, 'size' => $image->size,
'attributes' => $image->attributes 'attributes' => $image->attributes,
]); ]);
} }
@ -254,10 +419,5 @@ class ProductRepository extends BaseRepository {
return $this->model; return $this->model;
} }
public function delete() {}
public function delete()
{
}
} }

View file

@ -0,0 +1,21 @@
<?php
namespace App\Repositories;
use App\Models\Production;
use Illuminate\Database\Eloquent\Collection;
class ProductionRepository
{
/**
* @return Collection<int, Production>
*/
public function listForIndex(): Collection
{
return Production::query()
->with(['product', 'location', 'producedByUser'])
->orderByDesc('produced_at')
->orderByDesc('id')
->get();
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace App\Repositories;
use App\Models\StockEntry;
use Illuminate\Database\Eloquent\Collection;
class StockEntryRepository
{
/**
* @param array<string, mixed> $data
*/
public function create(array $data): StockEntry
{
$data['unit'] = ($data['entry_type'] ?? '') === 'ingredient' ? 'gram' : 'piece';
return StockEntry::query()->create($data);
}
/**
* @param array<string, mixed> $data
*/
public function update(StockEntry $stockEntry, array $data): StockEntry
{
if (array_key_exists('entry_type', $data)) {
$data['unit'] = ($data['entry_type'] ?? '') === 'ingredient' ? 'gram' : 'piece';
}
$stockEntry->update($data);
return $stockEntry->fresh();
}
/**
* @param array<string, mixed> $data
*/
public function receive(StockEntry $stockEntry, array $data): StockEntry
{
$data['status'] = 'received';
$data['received_by'] = auth()->id();
$stockEntry->update($data);
return $stockEntry->fresh();
}
/**
* @return Collection<int, StockEntry>
*/
public function getByStatus(string $status): Collection
{
return StockEntry::query()
->where('status', $status)
->orderByDesc('ordered_at')
->get();
}
/**
* @return Collection<int, StockEntry>
*/
public function getForIngredient(int $ingredientId): Collection
{
return StockEntry::query()
->where('ingredient_id', $ingredientId)
->where('status', 'received')
->orderByDesc('received_at')
->get();
}
/**
* Liste: Pending zuerst (neuestes Bestelldatum), dann Received (neuester Eingang).
*
* @return Collection<int, StockEntry>
*/
public function listForIndex(): Collection
{
$with = [
'ingredient',
'packagingItem',
'supplier',
'location',
'quality',
'orderedByUser',
'receivedByUser',
];
$pending = StockEntry::query()->with($with)->where('status', 'pending')->orderByDesc('ordered_at')->get();
$received = StockEntry::query()->with($with)->where('status', 'received')->orderByDesc('received_at')->get();
return $pending->concat($received)->values();
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace App\Repositories;
use App\Models\Supplier;
class SupplierRepository
{
/**
* @param array<string, mixed> $data
*/
public function create(array $data): Supplier
{
$supplier = Supplier::create($this->extractSupplierAttributes($data));
$this->syncCategories($supplier, $data['supplier_category_ids'] ?? []);
return $supplier;
}
/**
* @param array<string, mixed> $data
*/
public function update(Supplier $supplier, array $data): Supplier
{
$supplier->update($this->extractSupplierAttributes($data));
$this->syncCategories($supplier, $data['supplier_category_ids'] ?? []);
return $supplier->fresh();
}
/**
* @param array<int|string>|null $categoryIds
*/
public function syncCategories(Supplier $supplier, array $categoryIds): void
{
$ids = array_values(array_filter(array_map('intval', $categoryIds)));
$supplier->supplierCategories()->sync($ids);
}
/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
protected function extractSupplierAttributes(array $data): array
{
return collect($data)->only([
'name',
'url',
'contact_person',
'email',
'phone',
'country_id',
'notes',
'active',
])->all();
}
}

View file

@ -2,45 +2,39 @@
namespace App\Repositories; namespace App\Repositories;
use Str; use App\Models\PaymentMethod;
use App\User;
use stdClass;
use Validator;
use Carbon\Carbon;
use App\Models\UserAccount; use App\Models\UserAccount;
use App\Models\UserRegister; use App\Models\UserRegister;
use App\Models\PaymentMethod;
use App\Services\UserService; use App\Services\UserService;
use App\User;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use stdClass;
use Validator;
class UserRepository extends BaseRepository { class UserRepository extends BaseRepository
{
public function __construct(User $model) public function __construct(User $model)
{ {
$this->model = $model; $this->model = $model;
} }
public function update($data) public function update($data)
{ {
if($data['user_id'] === "new" || $data['user_id'] == 0){ if ($data['user_id'] === 'new' || $data['user_id'] == 0) {
$this->model = User::create([ $this->model = User::create([
'email' => $data['email'], 'email' => $data['email'],
'password' => env('APP_KEY'), 'password' => Hash::make(config('app.key')),
]); ]);
$this->model->payment_methods = PaymentMethod::getDefaultAsArray(); $this->model->payment_methods = PaymentMethod::getDefaultAsArray();
$this->model->save(); $this->model->save();
} } else {
else{
$this->model = $this->getById($data['user_id']); $this->model = $this->getById($data['user_id']);
} }
if (! $this->model->account_id) { if (! $this->model->account_id) {
$account = new UserAccount(); $account = new UserAccount;
} else { } else {
$account = $this->model->account; $account = $this->model->account;
} }
@ -50,8 +44,8 @@ class UserRepository extends BaseRepository {
$data['birthday_day'] = isset($data['birthday_day']) ? $data['birthday_day'] : 1; $data['birthday_day'] = isset($data['birthday_day']) ? $data['birthday_day'] : 1;
$data['birthday_month'] = isset($data['birthday_month']) ? $data['birthday_month'] : 1; $data['birthday_month'] = isset($data['birthday_month']) ? $data['birthday_month'] : 1;
$data['birthday_year'] = isset($data['birthday_year']) ? $data['birthday_year'] : 1900; $data['birthday_year'] = isset($data['birthday_year']) ? $data['birthday_year'] : 1900;
$data['birthday'] = $data['birthday_day'].".".$data['birthday_month'].".".$data['birthday_year']; $data['birthday'] = $data['birthday_day'].'.'.$data['birthday_month'].'.'.$data['birthday_year'];
$data['birthday'] = $data['birthday'] == "1.1.1900" ? null : $data['birthday']; $data['birthday'] = $data['birthday'] == '1.1.1900' ? null : $data['birthday'];
$account->fill($data)->save(); $account->fill($data)->save();
@ -63,9 +57,10 @@ class UserRepository extends BaseRepository {
return true; return true;
} }
public function createUserRegister($data){ public function createUserRegister($data)
{
$obj = new stdClass(); $obj = new stdClass;
$obj->email = $data['email']; $obj->email = $data['email'];
$obj->password = Hash::make($data['password']); $obj->password = Hash::make($data['password']);
@ -88,18 +83,20 @@ class UserRepository extends BaseRepository {
UserRegister::create([ UserRegister::create([
'identifier' => $data['email'], 'identifier' => $data['email'],
'instance' => $confirmation_code, 'instance' => $confirmation_code,
'content' => $obj 'content' => $obj,
]); ]);
return $obj; return $obj;
} }
public function clearUserRegister(){ public function clearUserRegister()
{
$cleartime = date('Y-m-d H:i:s', strtotime('-1 day')); // gestern -24h $cleartime = date('Y-m-d H:i:s', strtotime('-1 day')); // gestern -24h
UserRegister::where('created_at', '<', $cleartime)->delete(); UserRegister::where('created_at', '<', $cleartime)->delete();
} }
public function create($UserRegister){ public function create($UserRegister)
{
$userObj = $UserRegister->content; $userObj = $UserRegister->content;
@ -122,7 +119,7 @@ class UserRepository extends BaseRepository {
$user->confirmation_code_to = null; $user->confirmation_code_to = null;
$user->confirmation_code_remider = 0; $user->confirmation_code_remider = 0;
$user->confirmation_date = now(); $user->confirmation_date = now();
$user->lang = !empty(\App::getLocale()) ? \App::getLocale() : "de"; $user->lang = ! empty(\App::getLocale()) ? \App::getLocale() : 'de';
$user->m_sponsor = $userObj->m_sponsor; $user->m_sponsor = $userObj->m_sponsor;
$user->account_id = $account->id; $user->account_id = $account->id;
@ -144,10 +141,10 @@ class UserRepository extends BaseRepository {
if ($user->account) { if ($user->account) {
$user->account->delete(); $user->account->delete();
} }
$user->email = "delete".time(); $user->email = 'delete'.time();
$user->password = "delete".time(); $user->password = 'delete'.time();
$user->confirmed = 0; $user->confirmed = 0;
$user->confirmation_code = "delete".time(); $user->confirmation_code = 'delete'.time();
$user->confirmation_date = null; $user->confirmation_date = null;
$user->confirmation_code_to = null; $user->confirmation_code_to = null;
$user->confirmation_code_remider = 2; $user->confirmation_code_remider = 2;
@ -162,17 +159,19 @@ class UserRepository extends BaseRepository {
return true; return true;
} }
public function reverse_charge_validate($data, $user, $route){ public function reverse_charge_validate($data, $user, $route)
{
if (isset($data['action']) && $data['action'] == 'reverse_charge_validate') { if (isset($data['action']) && $data['action'] == 'reverse_charge_validate') {
$rules = array( $rules = [
'tax_identification_number' => 'required', 'tax_identification_number' => 'required',
); ];
$validator = Validator::make($data, $rules); $validator = Validator::make($data, $rules);
if ($validator->fails()) { if ($validator->fails()) {
$data = [ $data = [
'user' => $user, 'user' => $user,
]; ];
return redirect($route)->withErrors($validator)->withInput($data); return redirect($route)->withErrors($validator)->withInput($data);
} }
$ret = $this->reverse_charge_activate($data, $user); $ret = $this->reverse_charge_activate($data, $user);
@ -183,10 +182,12 @@ class UserRepository extends BaseRepository {
$data = [ $data = [
'user' => $user, 'user' => $user,
]; ];
return redirect($route.'#user-vat-validation')->withErrors($validator)->withInput($data); return redirect($route.'#user-vat-validation')->withErrors($validator)->withInput($data);
} }
if ($ret === 'valid') { if ($ret === 'valid') {
\Session()->flash('alert-success', __('msg.VATID_successfully_entered')); \Session()->flash('alert-success', __('msg.VATID_successfully_entered'));
return redirect($route.'#user-vat-validation')->withInput($data); return redirect($route.'#user-vat-validation')->withInput($data);
return redirect($route.'#user-vat-validation')->withInput($data); return redirect($route.'#user-vat-validation')->withInput($data);
@ -194,7 +195,8 @@ class UserRepository extends BaseRepository {
} }
} }
public function reverse_charge_delete($data, $user, $route){ public function reverse_charge_delete($data, $user, $route)
{
if (isset($data['action']) && $data['action'] == 'reverse_charge_delete') { if (isset($data['action']) && $data['action'] == 'reverse_charge_delete') {
$user->account->tax_identification_number = ''; $user->account->tax_identification_number = '';
$user->account->reverse_charge = 0; $user->account->reverse_charge = 0;
@ -203,11 +205,13 @@ class UserRepository extends BaseRepository {
$user->account->save(); $user->account->save();
$data['tax_identification_number'] = ''; $data['tax_identification_number'] = '';
\Session()->flash('alert-success', __('msg.reverse_charge_procedure_and_VATID_deleted')); \Session()->flash('alert-success', __('msg.reverse_charge_procedure_and_VATID_deleted'));
return redirect($route.'#user-vat-validation')->withInput($data); return redirect($route.'#user-vat-validation')->withInput($data);
} }
} }
public function reverse_charge_activate($data, $user){ public function reverse_charge_activate($data, $user)
{
/* 'AT' => 'AT-Oesterreich', /* 'AT' => 'AT-Oesterreich',
'BE' => 'BE-Belgien', 'BE' => 'BE-Belgien',
@ -243,7 +247,7 @@ class UserRepository extends BaseRepository {
$countryCode = $user->account->country->code; $countryCode = $user->account->country->code;
} }
$vatid = str_replace(array(' ', '.', '-', ',', ', '), '', trim($data['tax_identification_number'])); $vatid = str_replace([' ', '.', '-', ',', ', '], '', trim($data['tax_identification_number']));
$cc = substr($vatid, 0, 2); $cc = substr($vatid, 0, 2);
$vatNo = substr($vatid, 2); $vatNo = substr($vatid, 2);
@ -255,13 +259,13 @@ class UserRepository extends BaseRepository {
'ssl' => [ 'ssl' => [
'verify_peer' => false, 'verify_peer' => false,
'verify_peer_name' => false, 'verify_peer_name' => false,
'allow_self_signed' => true 'allow_self_signed' => true,
],
] ]
] ),
)
]; ];
$client = new \SoapClient("https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl", $options); $client = new \SoapClient('https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl', $options);
$result = $client->checkVat(['countryCode' => $countryCode, 'vatNumber' => $vatNo]); $result = $client->checkVat(['countryCode' => $countryCode, 'vatNumber' => $vatNo]);
if ($result->valid == true) { if ($result->valid == true) {
@ -270,11 +274,10 @@ class UserRepository extends BaseRepository {
$user->account->reverse_charge_code = $countryCode; $user->account->reverse_charge_code = $countryCode;
$user->account->reverse_charge_valid = now(); $user->account->reverse_charge_valid = now();
$user->account->save(); $user->account->save();
return 'valid'; return 'valid';
} else { } else {
return 'error'; return 'error';
} }
} }
} }

View file

@ -1,23 +1,21 @@
<?php <?php
namespace App\Services; namespace App\Services;
use App\User;
use App\Models\Country;
use App\Models\Product;
use App\Models\Category;
use App\Models\LeadType;
use App\Models\Attribute; use App\Models\Attribute;
use App\Models\UserLevel;
use App\Models\Ingredient;
use App\Models\ShoppingUser;
use App\Models\AttributeType; use App\Models\AttributeType;
use App\Models\Category;
use App\Models\Country;
use App\Models\Ingredient;
use App\Models\LeadType;
use App\Models\Product;
use App\Models\ShippingCountry; use App\Models\ShippingCountry;
use Illuminate\Support\Facades\Auth; use App\Models\ShoppingUser;
use App\Models\UserLevel;
use App\User;
class HTMLHelper class HTMLHelper
{ {
public static $months = [ public static $months = [
1 => 'January', 1 => 'January',
2 => 'February', 2 => 'February',
@ -30,11 +28,9 @@ class HTMLHelper
9 => 'September', 9 => 'September',
10 => 'October', 10 => 'October',
11 => 'November', 11 => 'November',
12 => 'December' 12 => 'December',
]; ];
private static $roles = [ private static $roles = [
0 => 'Kunde', 0 => 'Kunde',
1 => 'Redakteur', 1 => 'Redakteur',
@ -44,12 +40,13 @@ class HTMLHelper
// 10 => "API", // 10 => "API",
]; ];
public static function getMonth($i)
public static function getMonth($i){ {
return self::$months[intval($i)]; return self::$months[intval($i)];
} }
public static function getTransMonths($full = false){ public static function getTransMonths($full = false)
{
$ret = []; $ret = [];
foreach (self::$months as $key => $val) { foreach (self::$months as $key => $val) {
$ret[$key] = trans('cal.months.'.$val); $ret[$key] = trans('cal.months.'.$val);
@ -57,20 +54,24 @@ class HTMLHelper
if ($full) { // ganzes Jahr if ($full) { // ganzes Jahr
$ret[13] = trans('cal.months.full_year'); $ret[13] = trans('cal.months.full_year');
} }
return $ret; return $ret;
} }
public static function getYearRange($start = 2021) public static function getYearRange($start = 2021)
{ {
$end = date("Y"); $end = date('Y');
return array_reverse(range($start, $end)); return array_reverse(range($start, $end));
} }
public static function getRoleLabel($role_id = 0){ public static function getRoleLabel($role_id = 0)
{
return '<span class="badge badge-pill '.self::getLabel($role_id).'">'.self::$roles[$role_id].'</span>'; return '<span class="badge badge-pill '.self::getLabel($role_id).'">'.self::$roles[$role_id].'</span>';
} }
public static function getLabel($id){ public static function getLabel($id)
{
switch ($id) { switch ($id) {
case 0: case 0:
return 'badge-default'; return 'badge-default';
@ -94,8 +95,9 @@ class HTMLHelper
} }
public static function getCustomListOf($name, $select){ public static function getCustomListOf($name, $select)
$ret = ""; {
$ret = '';
if ($name === 'day') { if ($name === 'day') {
$start = 1; $start = 1;
$end = 31; $end = 31;
@ -114,8 +116,8 @@ class HTMLHelper
} }
} }
if ($name === 'year') { if ($name === 'year') {
$start = date("Y", strtotime("-5 years", time())); $start = date('Y', strtotime('-5 years', time()));
$end = date("Y", strtotime("-90 years", time())); $end = date('Y', strtotime('-90 years', time()));
$values = range($start, $end); $values = range($start, $end);
$ret = '<option value="">'.__('Jahr').'</option>\n'; $ret = '<option value="">'.__('Jahr').'</option>\n';
foreach ($values as $value) { foreach ($values as $value) {
@ -127,14 +129,16 @@ class HTMLHelper
return $ret; return $ret;
} }
public static function setContentReadMore($content){
public static function setContentReadMore($content)
{
$sep = '##mehr lesen##'; $sep = '##mehr lesen##';
if (strpos($content, $sep) !== false) { if (strpos($content, $sep) !== false) {
$name = 'collapse_'.random_int(1000, 10000); $name = 'collapse_'.random_int(1000, 10000);
$split = explode($sep, $content); $split = explode($sep, $content);
$first = isset($split[0]) ? $split[0] : ""; $first = isset($split[0]) ? $split[0] : '';
$text = isset($split[1]) ? $split[1] : ""; $text = isset($split[1]) ? $split[1] : '';
$content = $first; $content = $first;
$content .= '<br><a class="btn btn-primary btn-sm mt-2 collapsed" data-toggle="collapse" href="#'.$name.'" role="button" aria-expanded="false" aria-controls="'.$name.'"> $content .= '<br><a class="btn btn-primary btn-sm mt-2 collapsed" data-toggle="collapse" href="#'.$name.'" role="button" aria-expanded="false" aria-controls="'.$name.'">
@ -147,34 +151,39 @@ class HTMLHelper
</div> </div>
</div>'; </div>';
} }
return $content; return $content;
} }
public static function getRolesOptions(){ public static function getRolesOptions()
$ret = ""; {
$ret = '';
foreach (self::$roles as $role_id => $value) { foreach (self::$roles as $role_id => $value) {
$ret .= '<option value="'.$role_id.'">'.$value.'</option>\n'; $ret .= '<option value="'.$role_id.'">'.$value.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getYearSelectOptions(){ public static function getYearSelectOptions()
$start = date("Y", strtotime("-5 years", time())); {
$end = date("Y", strtotime("+1 years", time())); $start = date('Y', strtotime('-5 years', time()));
$end = date('Y', strtotime('+1 years', time()));
$values = range($start, $end); $values = range($start, $end);
$now = date("Y", time()); $now = date('Y', time());
$ret = ""; $ret = '';
foreach ($values as $value) { foreach ($values as $value) {
$attr = ($value == $now) ? 'selected="selected"' : ''; $attr = ($value == $now) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value.'" '.$attr.'>'.$value.'</option>\n'; $ret .= '<option value="'.$value.'" '.$attr.'>'.$value.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getAttributeTypes($id = false)
public static function getAttributeTypes($id = false){ {
$values = AttributeType::where('parent_id', null)->where('active', 1)->orderBy('pos', 'asc')->get(); $values = AttributeType::where('parent_id', null)->where('active', 1)->orderBy('pos', 'asc')->get();
$ret = ""; $ret = '';
if ($id === false) { if ($id === false) {
$val = $values->first(); $val = $values->first();
$id = $val->id; $id = $val->id;
@ -184,13 +193,14 @@ class HTMLHelper
$attr = ($value->id == $id) ? 'selected="selected"' : ''; $attr = ($value->id == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getProductsWhiteLabelOptions($ids = [], $unsets = [], $type_id = false)
public static function getProductsWhiteLabelOptions($ids = [], $unsets = [], $type_id = false){ {
$values = Product::where('whitelabel', 1)->where('active', 1)->get(); $values = Product::where('whitelabel', 1)->where('active', 1)->get();
$ret = ""; $ret = '';
foreach ($values as $value) { foreach ($values as $value) {
if (is_array($unsets) && in_array($value->id, $unsets)) { if (is_array($unsets) && in_array($value->id, $unsets)) {
continue; continue;
@ -198,17 +208,18 @@ class HTMLHelper
$attr = (is_array($ids) && in_array($value->id, $ids)) ? 'selected="selected"' : ''; $attr = (is_array($ids) && in_array($value->id, $ids)) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getAttributesOptions($ids = [], $all = true, $type_id = false)
public static function getAttributesOptions($ids = array(), $all = true, $type_id = false){ {
if ($type_id) { if ($type_id) {
$values = Attribute::where('active', 1)->where('attribute_type_id', $type_id)->get(); $values = Attribute::where('active', 1)->where('attribute_type_id', $type_id)->get();
} else { } else {
$values = Attribute::where('active', 1)->get(); $values = Attribute::where('active', 1)->get();
} }
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n'; $ret .= '<option value="">'.__('no').'</option>\n';
} }
@ -216,11 +227,13 @@ class HTMLHelper
$attr = in_array($value->id, $ids) ? 'selected="selected"' : ''; $attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getProductAttributesOptions($product_attributes, $ids = [], $all = true, $type_id = false){ public static function getProductAttributesOptions($product_attributes, $ids = [], $all = true, $type_id = false)
$ret = ""; {
$ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n'; $ret .= '<option value="">'.__('no').'</option>\n';
} }
@ -230,12 +243,14 @@ class HTMLHelper
$ret .= '<option value="'.$product_attribute->attribute_id.'" '.$attr.'>'.$product_attribute->attribute->name.'</option>\n'; $ret .= '<option value="'.$product_attribute->attribute_id.'" '.$attr.'>'.$product_attribute->attribute->name.'</option>\n';
} }
} }
return $ret; return $ret;
} }
public static function getCategoriesWithoutParents($id = false, $sameId = false, $all = true){ public static function getCategoriesWithoutParents($id = false, $sameId = false, $all = true)
{
$values = Category::where('parent_id', null)->get(); $values = Category::where('parent_id', null)->get();
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n'; $ret .= '<option value="">'.__('no').'</option>\n';
} }
@ -246,15 +261,17 @@ class HTMLHelper
$attr = ($value->id == $id) ? 'selected="selected"' : ''; $attr = ($value->id == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getProductsOptions($ids = array(), $all = true){ public static function getProductsOptions($ids = [], $all = true)
{
if ($ids == null) { if ($ids == null) {
$ids = array(); $ids = [];
} }
$values = Product::where('active', 1)->get(); $values = Product::where('active', 1)->get();
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n'; $ret .= '<option value="">'.__('no').'</option>\n';
} }
@ -262,12 +279,14 @@ class HTMLHelper
$attr = in_array($value->id, $ids) ? 'selected="selected"' : ''; $attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getCategoriesOptions($ids = array(), $all = true){ public static function getCategoriesOptions($ids = [], $all = true)
{
$values = Category::where('active', 1)->get(); $values = Category::where('active', 1)->get();
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n'; $ret .= '<option value="">'.__('no').'</option>\n';
} }
@ -275,20 +294,25 @@ class HTMLHelper
$attr = in_array($value->id, $ids) ? 'selected="selected"' : ''; $attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getCategoriesByShowOn($show_on = []){ public static function getCategoriesByShowOn($show_on = [])
{
$values = Category::where('active', true)->whereJsonContains('show_on', $show_on)->orderBy('pos', 'ASC')->get(); $values = Category::where('active', true)->whereJsonContains('show_on', $show_on)->orderBy('pos', 'ASC')->get();
$ret = []; $ret = [];
foreach ($values as $value) { foreach ($values as $value) {
$ret[$value->id] = ['name' => $value->name, 'count' => $value->getProductsCountOn($show_on)]; $ret[$value->id] = ['name' => $value->name, 'count' => $value->getProductsCountOn($show_on)];
} }
return $ret; return $ret;
} }
public static function getCategoriesOptionsByShowOn($ids = array(), $all = false, $show_on = []){
public static function getCategoriesOptionsByShowOn($ids = [], $all = false, $show_on = [])
{
$values = Category::where('active', true)->whereJsonContains('show_on', $show_on)->orderBy('pos', 'ASC')->get(); $values = Category::where('active', true)->whereJsonContains('show_on', $show_on)->orderBy('pos', 'ASC')->get();
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.$all.'</option>\n'; $ret .= '<option value="">'.$all.'</option>\n';
} }
@ -297,24 +321,28 @@ class HTMLHelper
$count = $value->getProductsCountOn($show_on); $count = $value->getProductsCountOn($show_on);
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.' ('.$count.')</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.' ('.$count.')</option>\n';
} }
return $ret; return $ret;
} }
public static function getProductIngredientsOptions($has_ids = array(), $all = true){ public static function getProductIngredientsOptions($has_ids = [], $all = true)
$values = Ingredient::where('active', 1)->get(); {
$ret = ""; $values = Ingredient::where('active', 1)->orderBy('name')->get();
$attr = ""; $ret = '';
$attr = '';
foreach ($values as $value) { foreach ($values as $value) {
if (! in_array($value->id, $has_ids)) { if (! in_array($value->id, $has_ids)) {
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
} }
} }
return $ret; return $ret;
} }
public static function getLeadTypeOptions($id = false, $all = true){ public static function getLeadTypeOptions($id = false, $all = true)
{
$values = LeadType::where('active', 1)->get(); $values = LeadType::where('active', 1)->get();
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n'; $ret .= '<option value="">'.__('no').'</option>\n';
} }
@ -322,12 +350,14 @@ class HTMLHelper
$attr = ($value->id == $id) ? 'selected="selected"' : ''; $attr = ($value->id == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getUserLevelOptions($id = false, $all = true){ public static function getUserLevelOptions($id = false, $all = true)
{
$values = UserLevel::where('active', 1)->get(); $values = UserLevel::where('active', 1)->get();
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n'; $ret .= '<option value="">'.__('no').'</option>\n';
} }
@ -335,24 +365,27 @@ class HTMLHelper
$attr = ($value->id == $id) ? 'selected="selected"' : ''; $attr = ($value->id == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getCompanyOptions($company)
public static function getCompanyOptions($company){ {
$options = array(1 => __('business'), 0 => __('private'), ); $options = [1 => __('business'), 0 => __('private')];
$ret = ""; $ret = '';
foreach ($options as $id => $value) { foreach ($options as $id => $value) {
$attr = ($id == $company) ? 'selected="selected"' : ''; $attr = ($id == $company) ? 'selected="selected"' : '';
$ret .= '<option value="'.$id.'" '.$attr.'>'.$value.'</option>\n'; $ret .= '<option value="'.$id.'" '.$attr.'>'.$value.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getContriesWithMore($id, $all=true){# public static function getContriesWithMore($id, $all = true) //
{
$values = Country::all(); $values = Country::all();
$counter = 1; $counter = 1;
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n'; $ret .= '<option value="">'.__('please select').'</option>\n';
@ -367,20 +400,24 @@ class HTMLHelper
$counter++; $counter++;
} }
$ret .= '</optgroup>'; $ret .= '</optgroup>';
return $ret; return $ret;
} }
public static function getContriesCodes($id, $all=true){# public static function getContriesCodes($id, $all = true) //
{
$values = Country::all(); $values = Country::all();
$counter = 1; $counter = 1;
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n'; $ret .= '<option value="">'.__('please select').'</option>\n';
} }
foreach ($values as $value) { foreach ($values as $value) {
if(!$value->phone) continue; if (! $value->phone) {
continue;
}
if ($counter == 7) { if ($counter == 7) {
$ret .= '<optgroup label="'.__('further countrie').'">'; $ret .= '<optgroup label="'.__('further countrie').'">';
} }
@ -390,13 +427,15 @@ class HTMLHelper
$counter++; $counter++;
} }
$ret .= '</optgroup>'; $ret .= '</optgroup>';
return $ret; return $ret;
} }
public static function getCountriesWithoutUsedShippings($all=true){# public static function getCountriesWithoutUsedShippings($all = true) //
{
$values = Country::all(); $values = Country::all();
$country_ids = ShippingCountry::all()->pluck('country_id')->toArray(); $country_ids = ShippingCountry::all()->pluck('country_id')->toArray();
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n'; $ret .= '<option value="">'.__('please select').'</option>\n';
} }
@ -405,18 +444,22 @@ class HTMLHelper
$ret .= '<option value="'.$value->id.'">'.$value->getLocated().'</option>\n'; $ret .= '<option value="'.$value->id.'">'.$value->getLocated().'</option>\n';
} }
} }
return $ret; return $ret;
} }
public static function getCountryNameFormShipping($id){ public static function getCountryNameFormShipping($id)
{
$value = ShippingCountry::find($id); $value = ShippingCountry::find($id);
if ($value) { if ($value) {
return $value->country->getLocated(); return $value->country->getLocated();
} }
return "not defined";
return 'not defined';
} }
public static function getCountriesForShipping($id, $all=false, $shipping_for = false){# public static function getCountriesForShipping($id, $all = false, $shipping_for = false) //
{
$values = ShippingCountry::all(); $values = ShippingCountry::all();
if ($shipping_for) { if ($shipping_for) {
@ -430,7 +473,7 @@ class HTMLHelper
} }
$values = $temp; $values = $temp;
} }
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n'; $ret .= '<option value="">'.__('please select').'</option>\n';
} }
@ -439,72 +482,84 @@ class HTMLHelper
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->country->getLocated().'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->country->getLocated().'</option>\n';
} }
return $ret; return $ret;
} }
public static function getSalutation($id){ public static function getSalutation($id)
$values = array('mr' => __('MR'), 'ms' => __('MS')); {
$ret = ""; $values = ['mr' => __('MR'), 'ms' => __('MS')];
$ret = '';
$ret .= '<option value="">'.__('please select').'</option>\n'; $ret .= '<option value="">'.__('please select').'</option>\n';
foreach ($values as $key => $value) { foreach ($values as $key => $value) {
$attr = ($key == $id) ? 'selected="selected"' : ''; $attr = ($key == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n'; $ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getSalutationLang($id){ public static function getSalutationLang($id)
$values = array('mr' => __('MR'), 'ms' => __('MS')); {
return (!empty($values[$id]) ? $values[$id] : ''); $values = ['mr' => __('MR'), 'ms' => __('MS')];
return ! empty($values[$id]) ? $values[$id] : '';
} }
public static function getTaxSaleOptions($id){ public static function getTaxSaleOptions($id)
$values = array('1' => __('taxable_sales_1'), '2' => __('taxable_sales_2')); {
$ret = ""; $values = ['1' => __('taxable_sales_1'), '2' => __('taxable_sales_2')];
$ret = '';
$ret .= '<option value="">'.__('please select').'</option>\n'; $ret .= '<option value="">'.__('please select').'</option>\n';
foreach ($values as $key => $value) { foreach ($values as $key => $value) {
$attr = ($key == $id) ? 'selected="selected"' : ''; $attr = ($key == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n'; $ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getMembersOptions($id, $all=false){ public static function getMembersOptions($id, $all = false)
{
$values = User::where('active', '=', true)->where('blocked', '=', false)->where('payment_account', '>=', now())->get(); $values = User::where('active', '=', true)->where('blocked', '=', false)->where('payment_account', '>=', now())->get();
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n'; $ret .= '<option value="">'.__('please select').'</option>\n';
} }
foreach ($values as $value) { foreach ($values as $value) {
$attr = ($value->id == $id) ? 'selected="selected"' : ''; $attr = ($value->id == $id) ? 'selected="selected"' : '';
$to=""; $to = '';
if ($value->account) { if ($value->account) {
$to = $value->account->first_name." ".$value->account->last_name." | "; $to = $value->account->first_name.' '.$value->account->last_name.' | ';
} }
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$to.$value->email.' #'.$value->account->m_account.'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$to.$value->email.' #'.$value->account->m_account.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getUserCustomerOptions($id, $all=false){ public static function getUserCustomerOptions($id, $all = false)
{
$values = ShoppingUser::select(['id', 'billing_firstname', 'billing_lastname', 'billing_email', 'number']) $values = ShoppingUser::select(['id', 'billing_firstname', 'billing_lastname', 'billing_email', 'number'])
->where('shopping_users.member_id', '=', \Auth::user()->id)->get(); ->where('shopping_users.member_id', '=', \Auth::user()->id)->get();
$ret = ""; $ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n'; $ret .= '<option value="">'.__('please select').'</option>\n';
} }
foreach ($values as $value) { foreach ($values as $value) {
$attr = ($value->id == $id) ? 'selected="selected"' : ''; $attr = ($value->id == $id) ? 'selected="selected"' : '';
$to = $value->billing_firstname." ".$value->billing_lastname." | ".$value->billing_email; $to = $value->billing_firstname.' '.$value->billing_lastname.' | '.$value->billing_email;
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$to.' #'.$value->account->m_account.'</option>\n'; $ret .= '<option value="'.$value->id.'" '.$attr.'>'.$to.' #'.$value->account->m_account.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getAnyOptions($id, $options = [], $all=true){# public static function getAnyOptions($id, $options = [], $all = true) //
$ret = ""; {
$ret = '';
if ($all) { if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n'; $ret .= '<option value="">'.__('please select').'</option>\n';
} }
@ -512,16 +567,19 @@ class HTMLHelper
$attr = ($key == $id) ? 'selected="selected"' : ''; $attr = ($key == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n'; $ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n';
} }
return $ret; return $ret;
} }
public static function getOptionRange($select, $from=1, $to=50){ public static function getOptionRange($select, $from = 1, $to = 50)
{
$values = range($from, $to); $values = range($from, $to);
$ret = ""; $ret = '';
foreach ($values as $value) { foreach ($values as $value) {
$attr = ($value == $select) ? 'selected="selected"' : ''; $attr = ($value == $select) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value.'" '.$attr.'>'.$value.'</option>\n'; $ret .= '<option value="'.$value.'" '.$attr.'>'.$value.'</option>\n';
} }
return $ret; return $ret;
} }

View file

@ -1,108 +1,179 @@
<?php <?php
namespace App\Services; namespace App\Services;
use App\Mail\MailInvoice; use App\Mail\MailInvoice;
use App\Mail\MailLogistic; use App\Mail\MailLogistic;
use App\Services\Util;
use App\Models\Setting; use App\Models\Setting;
use App\Models\ShoppingOrder; use App\Models\ShoppingOrder;
use App\Models\UserCredit;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
class Invoice class Invoice
{ {
public static function getInvoiceNumber()
public static function getInvoiceNumber(){ {
return (int) Setting::getContentBySlug('invoice-number'); return (int) Setting::getContentBySlug('invoice-number');
} }
public static function makeNextInvoiceNumber(){ public static function makeNextInvoiceNumber()
{
$invoice_number = self::getInvoiceNumber(); $invoice_number = self::getInvoiceNumber();
$invoice_number = $invoice_number + 1; $invoice_number = $invoice_number + 1;
Setting::setContentBySlug('invoice-number', $invoice_number, 'int'); Setting::setContentBySlug('invoice-number', $invoice_number, 'int');
return $invoice_number; return $invoice_number;
} }
public static function createInvoiceNumber($invoice_number, $invoice_date){ public static function createInvoiceNumber($invoice_number, $invoice_date)
{
$prefix = \Carbon::parse($invoice_date)->format('Ym'); $prefix = \Carbon::parse($invoice_date)->format('Ym');
return $prefix.$invoice_number; return $prefix.$invoice_number;
} }
public static function getInvoiceStorageDir($invoice_date){ public static function getInvoiceStorageDir($invoice_date)
return "/invoice/".\Carbon::parse($invoice_date)->format('Y/m/'); {
return '/invoice/'.\Carbon::parse($invoice_date)->format('Y/m/');
} }
public static function getDeliveryStorageDir($invoice_date){ public static function getDeliveryStorageDir($invoice_date)
return "/delivery/".\Carbon::parse($invoice_date)->format('Y/m/'); {
return '/delivery/'.\Carbon::parse($invoice_date)->format('Y/m/');
} }
public static function makeInvoiceFilename($invoice_number){ public static function makeInvoiceFilename($invoice_number)
return "Rechnung-".$invoice_number.".pdf"; {
return 'Rechnung-'.$invoice_number.'.pdf';
} }
public static function makeDeliveryFilename($invoice_number){ public static function makeDeliveryFilename($invoice_number)
return "Lieferschein-".$invoice_number.".pdf"; {
return 'Lieferschein-'.$invoice_number.'.pdf';
} }
public static function makeCancellationFilename($invoice_number)
{
return 'Stornorechnung-'.$invoice_number.'.pdf';
}
public static function getCancellationStorageDir($invoice_date)
{
return '/cancellation/'.\Carbon::parse($invoice_date)->format('Y/m/');
}
// invoice // invoice
public static function isInvoice(ShoppingOrder $shopping_order){ public static function isInvoice(ShoppingOrder $shopping_order)
{
return isset($shopping_order->invoice['filename']) ? true : false; return isset($shopping_order->invoice['filename']) ? true : false;
} }
public static function getFilename($shopping_order){ public static function getFilename($shopping_order)
{
return isset($shopping_order->invoice['filename']) ? $shopping_order->invoice['filename'] : false; return isset($shopping_order->invoice['filename']) ? $shopping_order->invoice['filename'] : false;
} }
public static function getDir($shopping_order){ public static function getDir($shopping_order)
{
return isset($shopping_order->invoice['dir']) ? $shopping_order->invoice['dir'] : false; return isset($shopping_order->invoice['dir']) ? $shopping_order->invoice['dir'] : false;
} }
public static function getDate($shopping_order){ public static function getDate($shopping_order)
{
return isset($shopping_order->invoice['invoice_date']) ? $shopping_order->invoice['invoice_date'] : false; return isset($shopping_order->invoice['invoice_date']) ? $shopping_order->invoice['invoice_date'] : false;
} }
public static function getNumber($shopping_order){ public static function getNumber($shopping_order)
{
return isset($shopping_order->invoice['invoice_number']) ? $shopping_order->invoice['invoice_number'] : false; return isset($shopping_order->invoice['invoice_number']) ? $shopping_order->invoice['invoice_number'] : false;
} }
// cancellation
public static function isCancellationInvoice(ShoppingOrder $shopping_order)
{
return isset($shopping_order->cancellation_invoice['filename']) ? true : false;
}
public static function getCancellationFilename($shopping_order)
{
return isset($shopping_order->cancellation_invoice['filename']) ? $shopping_order->cancellation_invoice['filename'] : false;
}
public static function getCancellationDir($shopping_order)
{
return isset($shopping_order->cancellation_invoice['dir']) ? $shopping_order->cancellation_invoice['dir'] : false;
}
public static function getCancellationDate($shopping_order)
{
return isset($shopping_order->cancellation_invoice['cancellation_date']) ? $shopping_order->cancellation_invoice['cancellation_date'] : false;
}
public static function getCancellationNumber($shopping_order)
{
return isset($shopping_order->cancellation_invoice['cancellation_number']) ? $shopping_order->cancellation_invoice['cancellation_number'] : false;
}
public static function getCancellationDownloadPath(ShoppingOrder $shopping_order, $full = false)
{
$dir = self::getCancellationDir($shopping_order);
$filename = self::getCancellationFilename($shopping_order);
if (! $full) {
return $dir.$filename;
}
return \Storage::disk('public')->path($dir.$filename);
}
// delivery // delivery
public static function isDelivery(ShoppingOrder $shopping_order){ public static function isDelivery(ShoppingOrder $shopping_order)
{
return isset($shopping_order->delivery['filename']) ? true : false; return isset($shopping_order->delivery['filename']) ? true : false;
} }
public static function getDeliveryFilename($shopping_order){
public static function getDeliveryFilename($shopping_order)
{
return isset($shopping_order->delivery['filename']) ? $shopping_order->delivery['filename'] : self::makeDeliveryFilename(self::getDeliveryNumber($shopping_order)); return isset($shopping_order->delivery['filename']) ? $shopping_order->delivery['filename'] : self::makeDeliveryFilename(self::getDeliveryNumber($shopping_order));
} }
public static function getDeliveryDir($shopping_order){ public static function getDeliveryDir($shopping_order)
{
return isset($shopping_order->delivery['dir']) ? $shopping_order->delivery['dir'] : self::getDeliveryStorageDir(self::getDeliveryDate($shopping_order)); return isset($shopping_order->delivery['dir']) ? $shopping_order->delivery['dir'] : self::getDeliveryStorageDir(self::getDeliveryDate($shopping_order));
} }
public static function getDeliveryDate($shopping_order){ public static function getDeliveryDate($shopping_order)
{
return isset($shopping_order->delivery['date']) ? $shopping_order->delivery['date'] : self::getDate($shopping_order); return isset($shopping_order->delivery['date']) ? $shopping_order->delivery['date'] : self::getDate($shopping_order);
} }
public static function getDeliveryNumber($shopping_order){ public static function getDeliveryNumber($shopping_order)
{
return isset($shopping_order->delivery['number']) ? $shopping_order->delivery['number'] : self::getNumber($shopping_order); return isset($shopping_order->delivery['number']) ? $shopping_order->delivery['number'] : self::getNumber($shopping_order);
} }
public static function getDownloadPath(ShoppingOrder $shopping_order, $full = false)
public static function getDownloadPath(ShoppingOrder $shopping_order, $full = false){ {
$dir = self::getDir($shopping_order); $dir = self::getDir($shopping_order);
$filename = self::getFilename($shopping_order); $filename = self::getFilename($shopping_order);
if (! $full) { if (! $full) {
return $dir.$filename; return $dir.$filename;
} }
return \Storage::disk('public')->path($dir.$filename); return \Storage::disk('public')->path($dir.$filename);
} }
public static function getDownloadPathDelivery(ShoppingOrder $shopping_order, $full = false){ public static function getDownloadPathDelivery(ShoppingOrder $shopping_order, $full = false)
{
$dir = self::getDeliveryDir($shopping_order); $dir = self::getDeliveryDir($shopping_order);
$filename = self::getDeliveryFilename($shopping_order); $filename = self::getDeliveryFilename($shopping_order);
if (! $full) { if (! $full) {
return $dir.$filename; return $dir.$filename;
} }
return \Storage::disk('public')->path($dir.$filename); return \Storage::disk('public')->path($dir.$filename);
} }
public static function sendInvoiceMail($shopping_order){ public static function sendInvoiceMail($shopping_order)
{
$bcc = []; $bcc = [];
$billing_email = $shopping_order->shopping_user->billing_email; $billing_email = $shopping_order->shopping_user->billing_email;
if (! $billing_email) { if (! $billing_email) {
@ -120,7 +191,8 @@ class Invoice
Mail::to($billing_email)->bcc($bcc)->send(new MailInvoice($shopping_order)); Mail::to($billing_email)->bcc($bcc)->send(new MailInvoice($shopping_order));
} }
public static function sendLogisticMail(ShoppingOrder $shopping_order){ public static function sendLogisticMail(ShoppingOrder $shopping_order)
{
$to = [config('app.logistic_mail')]; // ['versand@aloe-vera.bio']; $to = [config('app.logistic_mail')]; // ['versand@aloe-vera.bio'];
Mail::to($to)->send(new MailLogistic($shopping_order)); Mail::to($to)->send(new MailLogistic($shopping_order));
} }

View file

@ -2,11 +2,10 @@
namespace App\Services; namespace App\Services;
use App\Mail\PaymentReminderEmail;
use App\Models\Logger; use App\Models\Logger;
use App\Models\PaymentReminder; use App\Models\PaymentReminder;
use App\Models\ShoppingPayment; use App\Models\ShoppingPayment;
use App\Mail\PaymentReminderEmail;
use App\Services\Util;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
@ -31,19 +30,23 @@ class PaymentReminderService
'action' => $action, 'action' => $action,
'channel' => 'payment_reminder', 'channel' => 'payment_reminder',
'message' => $message, 'message' => $message,
'level' => $level 'level' => $level,
]); ]);
} }
/** /**
* Hole alle aktiven Intervalle für Zahlungserinnerungen * Hole alle aktiven Intervalle für Zahlungserinnerungen, immer mit dem kleinsten intervall
*/ */
public function getActiveIntervals() public function getActiveIntervals()
{ {
$intervals = []; $intervals = [];
$payment_reminders = PaymentReminder::where('active', true)->get(); $payment_reminders = PaymentReminder::where('active', true)->orderBy('interval', 'asc')->get();
foreach ($payment_reminders as $reminder) { foreach ($payment_reminders as $reminder) {
if (isset($intervals[$reminder->clearingtype]) && $intervals[$reminder->clearingtype] < $reminder->interval) {
continue;
}
$intervals[$reminder->clearingtype] = $reminder->interval; $intervals[$reminder->clearingtype] = $reminder->interval;
} }
@ -79,7 +82,6 @@ class PaymentReminderService
->select('shopping_payments.*') ->select('shopping_payments.*')
->get(); ->get();
return $payments; return $payments;
} }
@ -99,7 +101,7 @@ class PaymentReminderService
'interval' => $interval, 'interval' => $interval,
'date_limit' => $date, 'date_limit' => $date,
'payments' => $payments, 'payments' => $payments,
'count' => $payments->count() 'count' => $payments->count(),
]; ];
} }
@ -158,7 +160,6 @@ class PaymentReminderService
public function sendReminder($payment) public function sendReminder($payment)
{ {
// holen der nächsten zahlungserinnerung // holen der nächsten zahlungserinnerung
$payment_reminder = $this->getReminder((int) $payment->reminder, $payment->clearingtype); $payment_reminder = $this->getReminder((int) $payment->reminder, $payment->clearingtype);
if (! $payment_reminder) { if (! $payment_reminder) {
@ -196,8 +197,8 @@ class PaymentReminderService
} }
// reminder setzen +1 // reminder setzen +1
$payment->reminder = (int) $payment->reminder + 1; $payment->reminder = (int) $payment->reminder + 1; // anzahl der Reminder
$payment->reminder_date = Carbon::now(); $payment->reminder_date = Carbon::now(); // wann wurde der letzte reminder gesendet?
$payment->save(); $payment->save();
$this->createLog( $this->createLog(
@ -242,6 +243,7 @@ class PaymentReminderService
if ($reminder >= $payment_reminders->count()) { if ($reminder >= $payment_reminders->count()) {
return false; return false;
} }
// Hole die Erinnerung an Position $reminder (0,1,2,3...) // Hole die Erinnerung an Position $reminder (0,1,2,3...)
return $payment_reminders[$reminder]; return $payment_reminders[$reminder];
} }
@ -256,7 +258,7 @@ class PaymentReminderService
'{billing_last_name}' => $shopping_user->billing_lastname, '{billing_last_name}' => $shopping_user->billing_lastname,
'{order_number}' => '<b>'.$shopping_order->getLastShoppingPayment('reference').'</b>', '{order_number}' => '<b>'.$shopping_order->getLastShoppingPayment('reference').'</b>',
'{order_date}' => '<b>'.$shopping_order->created_at->format('d.m.Y').'</b>', '{order_date}' => '<b>'.$shopping_order->created_at->format('d.m.Y').'</b>',
'{order_total}' => '<b>'.$shopping_order->getFormattedTotalShipping().'</b>' '{order_total}' => '<b>'.$shopping_order->getFormattedTotalShipping().'</b>',
]; ];
$payment_reminder->subject = str_replace( $payment_reminder->subject = str_replace(
@ -298,11 +300,12 @@ class PaymentReminderService
\Log::error('Fehler beim E-Mail-Versand: '.$e->getMessage()); \Log::error('Fehler beim E-Mail-Versand: '.$e->getMessage());
$this->createLog( $this->createLog(
'email_exception', 'email_exception',
"E-Mail Exception: " . $e->getMessage() . " für Payment ID: {$payment->id}", 'E-Mail Exception: '.$e->getMessage()." für Payment ID: {$payment->id}",
'ShoppingOrder', 'ShoppingOrder',
$payment->shopping_order_id, $payment->shopping_order_id,
5 5
); );
return false; return false;
} }
} }
@ -332,7 +335,7 @@ class PaymentReminderService
return [ return [
'type' => 'completed', 'type' => 'completed',
'message' => 'Alle Erinnerungen gesendet', 'message' => 'Alle Erinnerungen gesendet',
'days_left' => 0 'days_left' => 0,
]; ];
} }
@ -355,7 +358,7 @@ class PaymentReminderService
'type' => 'overdue', 'type' => 'overdue',
'message' => 'Nächste Erinnerung fällig', 'message' => 'Nächste Erinnerung fällig',
'days_left' => 0, 'days_left' => 0,
'next_reminder_date' => $next_reminder_date 'next_reminder_date' => $next_reminder_date,
]; ];
} }
@ -364,7 +367,7 @@ class PaymentReminderService
'message' => 'Nächste Erinnerung in '.$days_left.' Tagen', 'message' => 'Nächste Erinnerung in '.$days_left.' Tagen',
'days_left' => $days_left, 'days_left' => $days_left,
'next_reminder_date' => $next_reminder_date, 'next_reminder_date' => $next_reminder_date,
'next_reminder_interval' => $interval_difference 'next_reminder_interval' => $interval_difference,
]; ];
} }
@ -446,8 +449,7 @@ class PaymentReminderService
return [ return [
'summary' => $summary, 'summary' => $summary,
'detailed_stats' => $stats 'detailed_stats' => $stats,
]; ];
} }
} }

View file

@ -0,0 +1,343 @@
<?php
namespace App\Services;
use App\Models\Product;
use App\Models\Production;
use App\Models\StockEntry;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class ProductionService
{
/**
* @param array<string, mixed> $data
* @param array<int, array{ingredient_id: int, stock_entry_id: int, quantity_used: float|int|string}> $ingredientLines
*/
public function store(array $data, array $ingredientLines, int $userId): Production
{
return DB::transaction(function () use ($data, $ingredientLines, $userId) {
$product = Product::query()
->with(['p_ingredients', 'packagings'])
->findOrFail($data['product_id']);
$locationId = (int) $data['location_id'];
$producedQty = (int) $data['quantity'];
if ($producedQty < 1) {
throw ValidationException::withMessages(['quantity' => __('Die Stückzahl muss mindestens 1 sein.')]);
}
if ($product->p_ingredients->isEmpty()) {
throw ValidationException::withMessages(['product_id' => __('Das Produkt hat keine Rezeptur (Inhaltsstoffe).')]);
}
$requiredGrams = $this->requiredGramsByIngredient($product, $producedQty);
$sums = [];
foreach ($ingredientLines as $line) {
$iid = (int) $line['ingredient_id'];
$used = $this->parseQuantity($line['quantity_used'] ?? null);
$sums[$iid] = ($sums[$iid] ?? 0) + $used;
}
foreach ($requiredGrams as $iid => $req) {
$sum = $sums[$iid] ?? 0;
if (abs($sum - $req) > 0.02) {
throw ValidationException::withMessages([
'ingredient_lines' => __('Summe der Chargen-Mengen pro INCI muss dem Soll-Verbrauch entsprechen (INCI :id: Soll :req g, Ist :sum g).', [
'id' => $iid,
'req' => number_format($req, 2, ',', '.'),
'sum' => number_format($sum, 2, ',', '.'),
]),
]);
}
}
foreach ($sums as $iid => $_sum) {
if (! isset($requiredGrams[$iid])) {
throw ValidationException::withMessages([
'ingredient_lines' => __('Unerwarteter Inhaltsstoff in den Chargen-Zeilen.'),
]);
}
}
foreach ($ingredientLines as $line) {
$this->assertStockEntryMatchesLine($line, $locationId);
}
$mhdWarning = $this->computeMhdWarning($product, $data['produced_at'], $ingredientLines);
$production = Production::query()->create([
'product_id' => $product->id,
'location_id' => $locationId,
'produced_by' => $userId,
'produced_at' => $data['produced_at'],
'quantity' => $producedQty,
'notes' => $data['notes'] ?? null,
'mhd_warning' => $mhdWarning,
]);
foreach ($ingredientLines as $line) {
$production->productionIngredients()->create([
'ingredient_id' => (int) $line['ingredient_id'],
'stock_entry_id' => (int) $line['stock_entry_id'],
'quantity_used' => $this->parseQuantity($line['quantity_used'] ?? null),
]);
}
foreach ($product->packagings as $bom) {
$perUnit = (float) ($bom->pivot->quantity ?? 1);
$pieces = (int) round($perUnit * $producedQty);
if ($pieces < 1) {
$pieces = 1;
}
$production->productionPackagings()->create([
'packaging_item_id' => $bom->id,
'quantity_used' => $pieces,
]);
}
return $production->fresh(['product', 'location', 'productionIngredients', 'productionPackagings']);
});
}
/**
* @param array<string, mixed> $data
* @param array<int, array{ingredient_id: int, stock_entry_id: int, quantity_used: float|int|string}> $ingredientLines
*/
public function updateProduction(Production $production, array $data, array $ingredientLines, int $userId): Production
{
return DB::transaction(function () use ($production, $data, $ingredientLines) {
$product = Product::query()
->with(['p_ingredients', 'packagings'])
->findOrFail($data['product_id']);
$locationId = (int) $data['location_id'];
$producedQty = (int) $data['quantity'];
if ($producedQty < 1) {
throw ValidationException::withMessages(['quantity' => __('Die Stückzahl muss mindestens 1 sein.')]);
}
if ($product->p_ingredients->isEmpty()) {
throw ValidationException::withMessages(['product_id' => __('Das Produkt hat keine Rezeptur (Inhaltsstoffe).')]);
}
$requiredGrams = $this->requiredGramsByIngredient($product, $producedQty);
$sums = [];
foreach ($ingredientLines as $line) {
$iid = (int) $line['ingredient_id'];
$used = $this->parseQuantity($line['quantity_used'] ?? null);
$sums[$iid] = ($sums[$iid] ?? 0) + $used;
}
foreach ($requiredGrams as $iid => $req) {
$sum = $sums[$iid] ?? 0;
if (abs($sum - $req) > 0.02) {
throw ValidationException::withMessages([
'ingredient_lines' => __('Summe der Chargen-Mengen pro INCI muss dem Soll-Verbrauch entsprechen (INCI :id: Soll :req g, Ist :sum g).', [
'id' => $iid,
'req' => number_format($req, 2, ',', '.'),
'sum' => number_format($sum, 2, ',', '.'),
]),
]);
}
}
foreach ($ingredientLines as $line) {
$this->assertStockEntryMatchesLine($line, $locationId);
}
$mhdWarning = $this->computeMhdWarning($product, $data['produced_at'], $ingredientLines);
$production->update([
'product_id' => $product->id,
'location_id' => $locationId,
'produced_at' => $data['produced_at'],
'quantity' => $producedQty,
'notes' => $data['notes'] ?? null,
'mhd_warning' => $mhdWarning,
]);
$production->productionIngredients()->delete();
foreach ($ingredientLines as $line) {
$production->productionIngredients()->create([
'ingredient_id' => (int) $line['ingredient_id'],
'stock_entry_id' => (int) $line['stock_entry_id'],
'quantity_used' => $this->parseQuantity($line['quantity_used'] ?? null),
]);
}
$production->productionPackagings()->delete();
foreach ($product->packagings as $bom) {
$perUnit = (float) ($bom->pivot->quantity ?? 1);
$pieces = (int) round($perUnit * $producedQty);
if ($pieces < 1) {
$pieces = 1;
}
$production->productionPackagings()->create([
'packaging_item_id' => $bom->id,
'quantity_used' => $pieces,
]);
}
return $production->fresh(['product', 'location', 'productionIngredients', 'productionPackagings']);
});
}
/**
* @return array<int, float>
*/
public function requiredGramsByIngredient(Product $product, int $producedQuantity): array
{
$required = [];
foreach ($product->p_ingredients as $ing) {
$gram = $ing->pivot->gram;
if ($gram === null || $gram === '') {
throw ValidationException::withMessages([
'product_id' => __('Für „:name“ fehlt Gramm in der Rezeptur.', ['name' => $ing->name]),
]);
}
$factor = (float) ($ing->pivot->factor ?? 1.1);
$required[(int) $ing->id] = (float) $gram * $factor * $producedQuantity;
}
return $required;
}
/**
* @param array{ingredient_id: int, stock_entry_id: int, quantity_used?: mixed} $line
*/
public function assertStockEntryMatchesLine(array $line, int $locationId): void
{
$entry = StockEntry::query()->findOrFail((int) $line['stock_entry_id']);
if ($entry->status !== 'received') {
throw ValidationException::withMessages([
'ingredient_lines' => __('Wareneingang :id ist nicht als eingegangen gebucht.', ['id' => $entry->id]),
]);
}
if ((int) $entry->ingredient_id !== (int) $line['ingredient_id']) {
throw ValidationException::withMessages(['ingredient_lines' => __('Charge passt nicht zum Inhaltsstoff.')]);
}
if ((int) $entry->location_id !== $locationId) {
throw ValidationException::withMessages(['ingredient_lines' => __('Charge gehört zu einem anderen Lagerort.')]);
}
}
/**
* @param array<int, array{ingredient_id: int, stock_entry_id: int, quantity_used?: mixed}> $ingredientLines
*/
public function computeMhdWarning(Product $product, string $producedAt, array $ingredientLines): bool
{
if ($product->shelf_life_type !== 'fixed' || ! $product->shelf_life_months) {
return false;
}
$productEnd = Carbon::parse($producedAt)->addMonths((int) $product->shelf_life_months)->startOfDay();
foreach ($ingredientLines as $line) {
$entry = StockEntry::query()->find((int) $line['stock_entry_id']);
if (! $entry || ! $entry->best_before) {
continue;
}
if ($entry->best_before->lt($productEnd)) {
return true;
}
}
return false;
}
/**
* Letzte empfangene Wareneingänge pro Inhaltsstoff am Standort (max. 3).
*
* @return Collection<int, StockEntry>
*/
public function latestStockEntriesForIngredient(int $ingredientId, int $locationId, int $limit = 3)
{
return StockEntry::query()
->where('status', 'received')
->where('entry_type', 'ingredient')
->where('ingredient_id', $ingredientId)
->where('location_id', $locationId)
->orderByDesc('received_at')
->orderByDesc('id')
->limit($limit)
->get();
}
/**
* @return array<string, mixed>
*/
public function buildRecipePayload(Product $product, int $locationId, int $productionQuantity): array
{
$product->loadMissing(['p_ingredients', 'packagings.packagingMaterial']);
$qty = max(1, $productionQuantity);
$ingredients = [];
foreach ($product->p_ingredients as $ing) {
$gram = $ing->pivot->gram;
$factor = (float) ($ing->pivot->factor ?? 1.1);
$req = ($gram !== null && $gram !== '') ? (float) $gram * $factor * $qty : null;
$ingredients[] = [
'id' => $ing->id,
'name' => $ing->name,
'gram' => $gram !== null && $gram !== '' ? (float) $gram : null,
'factor' => $factor,
'required_grams_total' => $req,
'stock_entries' => $this->latestStockEntriesForIngredient((int) $ing->id, $locationId)->map(function (StockEntry $se) {
return [
'id' => $se->id,
'batch_number' => $se->batch_number,
'best_before' => $se->best_before?->format('Y-m-d'),
'received_at' => $se->received_at?->format('Y-m-d'),
'received_quantity' => $se->received_quantity !== null ? (float) $se->received_quantity : null,
];
})->values()->all(),
];
}
$packagings = [];
foreach ($product->packagings as $pk) {
$perUnit = (float) ($pk->pivot->quantity ?? 1);
$packagings[] = [
'id' => $pk->id,
'name' => $pk->name,
'quantity_per_product' => $perUnit,
'total_pieces' => (int) round($perUnit * max(1, $productionQuantity)),
'weight_grams' => $pk->weight_grams !== null ? (float) $pk->weight_grams : null,
'material_name' => $pk->packagingMaterial?->name,
];
}
return [
'product' => [
'id' => $product->id,
'name' => $product->name,
'shelf_life_type' => $product->shelf_life_type,
'shelf_life_months' => $product->shelf_life_months,
],
'location_id' => $locationId,
'production_quantity' => $productionQuantity,
'ingredients' => $ingredients,
'packagings' => $packagings,
];
}
private function parseQuantity(mixed $value): float
{
if ($value === null || $value === '') {
return 0.0;
}
if (is_numeric($value)) {
return (float) $value;
}
$n = reFormatNumber((string) $value);
return $n !== null && $n !== '' ? (float) $n : 0.0;
}
}

View file

@ -3,23 +3,39 @@
namespace App; namespace App;
use App\Mail\MailResetPassword; use App\Mail\MailResetPassword;
use App\Models\File;
use App\Models\LeadType;
use App\Models\PaymentMethod; use App\Models\PaymentMethod;
use App\Models\UserAccount;
use App\Models\UserHistory;
use App\Models\UserLevel;
use App\Models\UserPayCredit;
use App\Models\UserShop;
use App\Models\UserUpdateEmail;
use App\Models\UserWhitelabelProduct;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Laravel\Passport\Client;
use Laravel\Passport\HasApiTokens; use Laravel\Passport\HasApiTokens;
use Laravel\Passport\Token;
use Util; use Util;
/** /**
* App\User * App\User
* *
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications * @property-read DatabaseNotificationCollection|DatabaseNotification[] $notifications
* @property-read int|null $notifications_count * @property-read int|null $notifications_count
*
* @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User newQuery() * @method static \Illuminate\Database\Eloquent\Builder|User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User query() * @method static \Illuminate\Database\Eloquent\Builder|User query()
*
* @property int $id * @property int $id
* @property string $email * @property string $email
* @property \Illuminate\Support\Carbon|null $email_verified_at * @property \Illuminate\Support\Carbon|null $email_verified_at
@ -52,6 +68,7 @@ use Util;
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property string|null $deleted_at * @property string|null $deleted_at
*
* @method static \Illuminate\Database\Eloquent\Builder|User whereAboOptions($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereAboOptions($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereAccountId($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereAccountId($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereAccountPayment($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereAccountPayment($value)
@ -84,37 +101,44 @@ use Util;
* @method static \Illuminate\Database\Eloquent\Builder|User whereTestMode($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereTestMode($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereWizard($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereWizard($value)
* @property-read \App\Models\UserAccount|null $account *
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\File[] $files * @property-read UserAccount|null $account
* @property-read Collection|File[] $files
* @property-read int|null $files_count * @property-read int|null $files_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\UserHistory[] $user_histories * @property-read Collection|UserHistory[] $user_histories
* @property-read int|null $user_histories_count * @property-read int|null $user_histories_count
* @property-read \App\Models\UserLevel|null $user_level * @property-read UserLevel|null $user_level
* @property-read User|null $user_sponsor * @property-read User|null $user_sponsor
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\UserUpdateEmail[] $user_update_email * @property-read Collection|UserUpdateEmail[] $user_update_email
* @property-read int|null $user_update_email_count * @property-read int|null $user_update_email_count
* @property string|null $payment_account * @property string|null $payment_account
* @property-read \App\Models\UserLevel|null $next_user_level * @property-read UserLevel|null $next_user_level
*
* @method static \Illuminate\Database\Eloquent\Builder|User wherePaymentAccount($value) * @method static \Illuminate\Database\Eloquent\Builder|User wherePaymentAccount($value)
*
* @property string|null $payment_credit * @property string|null $payment_credit
*
* @method static \Illuminate\Database\Eloquent\Builder|User wherePaymentCredit($value) * @method static \Illuminate\Database\Eloquent\Builder|User wherePaymentCredit($value)
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\UserPayCredit[] $user_pay_credits *
* @property-read Collection|UserPayCredit[] $user_pay_credits
* @property-read int|null $user_pay_credits_count * @property-read int|null $user_pay_credits_count
* @property-read \Illuminate\Database\Eloquent\Collection|\Laravel\Passport\Client[] $clients * @property-read Collection|Client[] $clients
* @property-read int|null $clients_count * @property-read int|null $clients_count
* @property-read \Illuminate\Database\Eloquent\Collection|\Laravel\Passport\Token[] $tokens * @property-read Collection|Token[] $tokens
* @property-read int|null $tokens_count * @property-read int|null $tokens_count
* @property-read \App\Models\UserShop|null $shop * @property-read UserShop|null $shop
* @property int|null $lead_type_id * @property int|null $lead_type_id
* @property-read \App\Models\LeadType|null $lead_type * @property-read LeadType|null $lead_type
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\UserWhitelabelProduct> $whitelabel_products * @property-read Collection<int, UserWhitelabelProduct> $whitelabel_products
* @property-read int|null $whitelabel_products_count * @property-read int|null $whitelabel_products_count
*
* @method static \Illuminate\Database\Eloquent\Builder|User whereLeadTypeId($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereLeadTypeId($value)
*
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class User extends Authenticatable class User extends Authenticatable
{ {
use Notifiable, HasApiTokens; use HasApiTokens, Notifiable;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -142,13 +166,15 @@ class User extends Authenticatable
protected $casts = [ protected $casts = [
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'settings' => 'array', 'settings' => 'array',
'payment_methods' => 'array' 'payment_methods' => 'array',
]; ];
private $userImage = false; private $userImage = false;
private $userImageLink = false; private $userImageLink = false;
public function account(){ public function account()
{
return $this->belongsTo('App\Models\UserAccount', 'account_id'); return $this->belongsTo('App\Models\UserAccount', 'account_id');
} }
@ -157,33 +183,42 @@ class User extends Authenticatable
return $this->hasOne('App\Models\UserShop', 'user_id', 'id'); return $this->hasOne('App\Models\UserShop', 'user_id', 'id');
} }
public function user_level(){ public function user_level()
{
return $this->belongsTo('App\Models\UserLevel', 'm_level'); return $this->belongsTo('App\Models\UserLevel', 'm_level');
} }
public function next_user_level(){ public function next_user_level()
{
return $this->belongsTo('App\Models\UserLevel', 'next_m_level'); return $this->belongsTo('App\Models\UserLevel', 'next_m_level');
} }
public function user_sponsor(){ public function user_sponsor()
{
return $this->belongsTo('App\User', 'm_sponsor'); return $this->belongsTo('App\User', 'm_sponsor');
} }
public function lead_type(){ public function lead_type()
{
return $this->belongsTo('App\Models\LeadType', 'lead_type_id'); return $this->belongsTo('App\Models\LeadType', 'lead_type_id');
} }
public function sponsorHasCommisson(){ public function sponsorHasCommisson()
{
if ($this->user_sponsor && $this->user_sponsor->user_level && $this->user_sponsor->user_level->partner_provision) { if ($this->user_sponsor && $this->user_sponsor->user_level && $this->user_sponsor->user_level->partner_provision) {
return true; return true;
} }
return false; return false;
} }
public function files(){
public function files()
{
return $this->hasMany('App\Models\File', 'user_id', ''); return $this->hasMany('App\Models\File', 'user_id', '');
} }
public function user_histories(){ public function user_histories()
{
return $this->hasMany('App\Models\UserHistory', 'user_id', ''); return $this->hasMany('App\Models\UserHistory', 'user_id', '');
} }
@ -202,38 +237,43 @@ class User extends Authenticatable
return $this->hasMany('App\Models\UserWhitelabelProduct', 'user_id', 'id'); return $this->hasMany('App\Models\UserWhitelabelProduct', 'user_id', 'id');
} }
public function getMUserSponsor(){ public function getMUserSponsor()
{
if ($this->user_sponsor && $this->user_sponsor->account) { if ($this->user_sponsor && $this->user_sponsor->account) {
return $this->user_sponsor->account->first_name." ".$this->user_sponsor->account->last_name." | ".$this->user_sponsor->email; return $this->user_sponsor->account->first_name.' '.$this->user_sponsor->account->last_name.' | '.$this->user_sponsor->email;
} }
} }
public function getFullName($email=true){
$ret = ""; public function getFullName($email = true)
{
$ret = '';
if ($this->account) { if ($this->account) {
$ret = $this->account->first_name." ".$this->account->last_name; $ret = $this->account->first_name.' '.$this->account->last_name;
} }
if ($email && $this->id > 1) { if ($email && $this->id > 1) {
$ret .= " | ".$this->email; $ret .= ' | '.$this->email;
} }
return $ret; return $ret;
} }
public function getFullAddress($email=true){ public function getFullAddress($email = true)
$ret = ""; {
$ret = '';
if ($this->account) { if ($this->account) {
$ret .= $this->account->first_name." ".$this->account->last_name."\n"; $ret .= $this->account->first_name.' '.$this->account->last_name."\n";
$ret .= $this->account->address."\n"; $ret .= $this->account->address."\n";
$ret .= $this->account->address_2 ? $this->account->address_2."\n" : ""; $ret .= $this->account->address_2 ? $this->account->address_2."\n" : '';
$ret .= $this->account->zipcode." ".$this->account->city."\n"; $ret .= $this->account->zipcode.' '.$this->account->city."\n";
$ret .= $email ? $this->email."\n" : ""; $ret .= $email ? $this->email."\n" : '';
$pre = $this->account->pre_phone_id != "" ? $this->account->pre_phone->phone." " : ""; $pre = $this->account->pre_phone_id != '' ? $this->account->pre_phone->phone.' ' : '';
$ret .= $this->account->phone ? $pre.$this->account->phone."\n" : ""; $ret .= $this->account->phone ? $pre.$this->account->phone."\n" : '';
$pre = $this->account->pre_mobil_id != "" ? $this->account->pre_mobil->phone." " : ""; $pre = $this->account->pre_mobil_id != '' ? $this->account->pre_mobil->phone.' ' : '';
$ret .= $this->account->mobil ? $pre.$this->account->mobil."\n" : ""; $ret .= $this->account->mobil ? $pre.$this->account->mobil."\n" : '';
} }
return $ret; return $ret;
@ -247,16 +287,16 @@ class User extends Authenticatable
if ($this->admin >= 1) { if ($this->admin >= 1) {
return true; return true;
} }
return false; return false;
} }
/** public function isPasswort(): bool
* @return bool {
*/ if (Hash::check(config('app.key'), $this->password)) {
public function isPasswort(){
if($this->password == env('APP_KEY')){
return false; return false;
} }
return true; return true;
} }
@ -268,6 +308,7 @@ class User extends Authenticatable
if ($this->admin >= 7) { if ($this->admin >= 7) {
return true; return true;
} }
return false; return false;
} }
@ -279,6 +320,7 @@ class User extends Authenticatable
if ($this->admin >= 8) { if ($this->admin >= 8) {
return true; return true;
} }
return false; return false;
} }
@ -290,6 +332,7 @@ class User extends Authenticatable
if ($this->admin >= 9) { if ($this->admin >= 9) {
return true; return true;
} }
return false; return false;
} }
@ -301,6 +344,7 @@ class User extends Authenticatable
if ($this->admin >= 10) { if ($this->admin >= 10) {
return true; return true;
} }
return false; return false;
} }
@ -312,7 +356,6 @@ class User extends Authenticatable
return $this->test_mode ? true : false; return $this->test_mode ? true : false;
} }
/** /**
* @return bool * @return bool
*/ */
@ -321,113 +364,152 @@ class User extends Authenticatable
if ($this->active == 1 && $this->blocked == 0 && $this->wizard >= 10) { if ($this->active == 1 && $this->blocked == 0 && $this->wizard >= 10) {
return true; return true;
} }
return false; return false;
} }
public function isActive(){ public function isActive()
{
return ($this->active == 1 && $this->blocked == 0) ? true : false; return ($this->active == 1 && $this->blocked == 0) ? true : false;
} }
public function isActiveAccount(){ public function isActiveAccount()
{
if ($this->isActive() && $this->payment_account) { if ($this->isActive() && $this->payment_account) {
return Carbon::parse($this->payment_account)->gt(Carbon::now()); return Carbon::parse($this->payment_account)->gt(Carbon::now());
} }
return false; return false;
} }
public function isRenewalAccount()
public function isRenewalAccount(){ {
if ($this->payment_account) { if ($this->payment_account) {
return Carbon::parse($this->payment_account)->modify('-'.(config('main.renewal_days')).' days')->lt(Carbon::now()); return Carbon::parse($this->payment_account)->modify('-'.(config('main.renewal_days')).' days')->lt(Carbon::now());
} }
return false; return false;
} }
public function nextRenewalAccount(){ public function nextRenewalAccount()
return $this->payment_account ? Carbon::parse($this->payment_account)->modify('-'.config('main.renewal_days').' days')->format(\Util::formatDateTimeDB()) : false ; {
return $this->payment_account ? Carbon::parse($this->payment_account)->modify('-'.config('main.renewal_days').' days')->format(Util::formatDateTimeDB()) : false;
} }
public function daysActiveAccount(){ public function daysActiveAccount()
{
return Carbon::now()->diffInDays(Carbon::parse($this->payment_account), false); return Carbon::now()->diffInDays(Carbon::parse($this->payment_account), false);
} }
public function modifyActiveAccount($add = "1 year"){ public function modifyActiveAccount($add = '1 year')
return Carbon::parse($this->payment_account)->modify($add)->format(\Util::formatDateTimeDB()); {
return Carbon::parse($this->payment_account)->modify($add)->format(Util::formatDateTimeDB());
} }
public function daysHumansActiveAccount(){ public function daysHumansActiveAccount()
{
return Carbon::now()->diffForHumans(Carbon::parse($this->payment_account)); return Carbon::now()->diffForHumans(Carbon::parse($this->payment_account));
} }
public function daysActiveShop(){ public function daysActiveShop()
{
return Carbon::now()->diffInDays(Carbon::parse($this->payment_shop), false); return Carbon::now()->diffInDays(Carbon::parse($this->payment_shop), false);
} }
public function modifyActiveShop($add = "1 year"){ public function modifyActiveShop($add = '1 year')
return Carbon::parse($this->payment_shop)->modify($add)->format(\Util::formatDateTimeDB()); {
return Carbon::parse($this->payment_shop)->modify($add)->format(Util::formatDateTimeDB());
} }
public function daysHumansActiveShop(){ public function daysHumansActiveShop()
{
return Carbon::now()->diffForHumans(Carbon::parse($this->payment_shop)); return Carbon::now()->diffForHumans(Carbon::parse($this->payment_shop));
} }
public function isAboOption(){ public function isAboOption()
{
return false; return false;
} }
/** /**
* @return string * @return string
*/ */
public function getConfirmationDateFormat(){ public function getConfirmationDateFormat()
if(!$this->attributes['confirmation_date']){ return ""; } {
return Carbon::parse($this->attributes['confirmation_date'])->format(\Util::formatDateTimeDB()); if (! $this->attributes['confirmation_date']) {
return '';
}
return Carbon::parse($this->attributes['confirmation_date'])->format(Util::formatDateTimeDB());
} }
/** /**
* @return string * @return string
*/ */
public function getActiveDateFormat(){ public function getActiveDateFormat()
if(!$this->attributes['active_date']){ return ""; } {
return Carbon::parse($this->attributes['active_date'])->format(\Util::formatDateTimeDB()); if (! $this->attributes['active_date']) {
return '';
}
return Carbon::parse($this->attributes['active_date'])->format(Util::formatDateTimeDB());
} }
/** /**
* @return string * @return string
*/ */
public function getAgreementFormat(){ public function getAgreementFormat()
if(!$this->attributes['agreement']){ return ""; } {
return Carbon::parse($this->attributes['agreement'])->format(\Util::formatDateTimeDB()); if (! $this->attributes['agreement']) {
return '';
} }
public function getPaymentAccountDateFormat($time = true){ return Carbon::parse($this->attributes['agreement'])->format(Util::formatDateTimeDB());
if(!$this->attributes['payment_account']){ return ""; }
if(!$time){
return Carbon::parse($this->attributes['payment_account'])->format(\Util::formatDateDB());
}
return Carbon::parse($this->attributes['payment_account'])->format(\Util::formatDateTimeDB());
} }
public function getPaymentShopDateFormat($time = true){ public function getPaymentAccountDateFormat($time = true)
if(!$this->attributes['payment_shop']){ return ""; } {
if(!$time){ if (! $this->attributes['payment_account']) {
return Carbon::parse($this->attributes['payment_shop'])->format(\Util::formatDateDB()); return '';
} }
return Carbon::parse($this->attributes['payment_shop'])->format(\Util::formatDateTimeDB()); if (! $time) {
return Carbon::parse($this->attributes['payment_account'])->format(Util::formatDateDB());
} }
public function getReleaseAccountFormat($time = true){ return Carbon::parse($this->attributes['payment_account'])->format(Util::formatDateTimeDB());
if(!$this->attributes['release_account']){ return ""; }
if(!$time){
return Carbon::parse($this->attributes['release_account'])->format(\Util::formatDateDB());
} }
return Carbon::parse($this->attributes['release_account'])->format(\Util::formatDateTimeDB());
public function getPaymentShopDateFormat($time = true)
{
if (! $this->attributes['payment_shop']) {
return '';
}
if (! $time) {
return Carbon::parse($this->attributes['payment_shop'])->format(Util::formatDateDB());
}
return Carbon::parse($this->attributes['payment_shop'])->format(Util::formatDateTimeDB());
}
public function getReleaseAccountFormat($time = true)
{
if (! $this->attributes['release_account']) {
return '';
}
if (! $time) {
return Carbon::parse($this->attributes['release_account'])->format(Util::formatDateDB());
}
return Carbon::parse($this->attributes['release_account'])->format(Util::formatDateTimeDB());
} }
public function getFormattedPaymentCredit() public function getFormattedPaymentCredit()
{ {
return isset($this->attributes['payment_credit']) ? Util::formatNumber($this->attributes['payment_credit']) : "0"; return isset($this->attributes['payment_credit']) ? Util::formatNumber($this->attributes['payment_credit']) : '0';
} }
public function setSetting(array $revisions, bool $save = true){ public function setSetting(array $revisions, bool $save = true)
{
if (! $this->settings) { if (! $this->settings) {
$this->settings = []; $this->settings = [];
} }
@ -435,55 +517,65 @@ class User extends Authenticatable
if ($save) { if ($save) {
$this->save(); $this->save();
} }
return $this; return $this;
} }
public function getSetting($key, $default = null){ public function getSetting($key, $default = null)
{
return isset($this->settings[$key]) ? $this->settings[$key] : $default; return isset($this->settings[$key]) ? $this->settings[$key] : $default;
} }
public function getPaymentMethodsShort(){ public function getPaymentMethodsShort()
$ret = ""; {
$ret = '';
if ($this->payment_methods !== null) { if ($this->payment_methods !== null) {
foreach ($this->payment_methods as $payment_method) { foreach ($this->payment_methods as $payment_method) {
if ($find = PaymentMethod::find($payment_method)) { if ($find = PaymentMethod::find($payment_method)) {
$ret .= $find->short." | "; $ret .= $find->short.' | ';
} }
} }
$ret = rtrim($ret, " | "); $ret = rtrim($ret, ' | ');
} }
return $ret; return $ret;
} }
/** /**
* @return string * @return string
*/ */
public function getLandByCountry(){ public function getLandByCountry()
{
if ($this->account && $this->account->country_id) { if ($this->account && $this->account->country_id) {
$code = $this->account->country->code; $code = $this->account->country->code;
if($code == "FR"){ if ($code == 'FR') {
return 'fr'; return 'fr';
} }
if($code == "CH"){ if ($code == 'CH') {
return 'de'; return 'de';
} }
if($code == "NL"){ if ($code == 'NL') {
return 'nl'; return 'nl';
} }
if($code == "DE"){ if ($code == 'DE') {
return 'de'; return 'de';
} }
} }
return "de";
return 'de';
} }
public function getBirthdayFormat($format = "d.m.Y"){ public function getBirthdayFormat($format = 'd.m.Y')
{
if ($this->account && $this->account->getBirthdayRaw()) { if ($this->account && $this->account->getBirthdayRaw()) {
return (int) Carbon::parse($this->account->getBirthdayRaw())->format($format); return (int) Carbon::parse($this->account->getBirthdayRaw())->format($format);
} }
return null; return null;
} }
public function hasProfileImage(){ public function hasProfileImage()
{
if ($this->userImage) { if ($this->userImage) {
return $this->userImage; return $this->userImage;
@ -494,13 +586,16 @@ class User extends Authenticatable
if (\Storage::disk('user')->has($this->id.'/avatar.png')) { if (\Storage::disk('user')->has($this->id.'/avatar.png')) {
$this->userImage = $this->id.'/avatar.png'; $this->userImage = $this->id.'/avatar.png';
} }
return $this->userImage; return $this->userImage;
} }
public function getProfileImage(){ public function getProfileImage()
{
if ($this->hasProfileImage()) { if ($this->hasProfileImage()) {
return str_replace('/', '_', $this->userImage); return str_replace('/', '_', $this->userImage);
} }
return null; return null;
} }

View file

@ -32,16 +32,17 @@
"srmklive/paypal": "~3.0" "srmklive/paypal": "~3.0"
}, },
"require-dev": { "require-dev": {
"spatie/laravel-ignition": "^2.0", "barryvdh/laravel-debugbar": "^3.13",
"nunomaduro/collision": "^8.1", "barryvdh/laravel-ide-helper": "^3.0",
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",
"laravel/boost": "^1.0",
"laravel/pint": "^1.0", "laravel/pint": "^1.0",
"laravel/sail": "^1.26", "laravel/sail": "^1.26",
"mockery/mockery": "^1.6.2", "mockery/mockery": "^1.6.2",
"nunomaduro/collision": "^8.1",
"pestphp/pest": "^2.0", "pestphp/pest": "^2.0",
"pestphp/pest-plugin-laravel": "^2.0", "pestphp/pest-plugin-laravel": "^2.0",
"barryvdh/laravel-debugbar": "^3.13", "spatie/laravel-ignition": "^2.0"
"barryvdh/laravel-ide-helper": "^3.0"
}, },
"config": { "config": {
"optimize-autoloader": true, "optimize-autoloader": true,

2514
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
<?php
namespace Database\Factories;
use App\Models\Location;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Location>
*/
class LocationFactory extends Factory
{
protected $model = Location::class;
public function definition(): array
{
return [
'name' => $this->faker->city(),
'active' => true,
];
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Database\Factories;
use App\Models\MaterialQuality;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<MaterialQuality>
*/
class MaterialQualityFactory extends Factory
{
protected $model = MaterialQuality::class;
public function definition(): array
{
return [
'name' => $this->faker->unique()->words(2, true),
'pos' => $this->faker->numberBetween(0, 50),
];
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Database\Factories;
use App\Models\PackagingItem;
use App\Models\PackagingMaterial;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<PackagingItem>
*/
class PackagingItemFactory extends Factory
{
protected $model = PackagingItem::class;
public function definition(): array
{
return [
'packaging_material_id' => PackagingMaterial::factory(),
'supplier_id' => null,
'name' => $this->faker->words(3, true),
'category' => 'packaging',
'weight_grams' => 0,
'min_stock_alert' => null,
'product_id' => null,
'active' => true,
];
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Database\Factories;
use App\Models\PackagingMaterial;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<PackagingMaterial>
*/
class PackagingMaterialFactory extends Factory
{
protected $model = PackagingMaterial::class;
public function definition(): array
{
return [
'name' => $this->faker->unique()->word(),
'pos' => $this->faker->numberBetween(0, 50),
];
}
}

View file

@ -0,0 +1,208 @@
<?php
namespace Database\Factories;
use App\Models\Ingredient;
use App\Models\Location;
use App\Models\MaterialQuality;
use App\Models\PackagingItem;
use App\Models\StockEntry;
use App\Models\Supplier;
use App\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<StockEntry>
*/
class StockEntryFactory extends Factory
{
protected $model = StockEntry::class;
/**
* @return array<string, mixed>
*/
public function definition(): array
{
$orderedAt = Carbon::instance($this->faker->dateTimeBetween('-3 months', 'now'));
return [
'entry_type' => 'ingredient',
'ingredient_id' => fn () => $this->makeIngredient()->id,
'packaging_item_id' => null,
'supplier_id' => Supplier::factory(),
'location_id' => Location::factory(),
'unit' => 'gram',
'ordered_by' => fn () => $this->makeOrderUser()->id,
'ordered_at' => $orderedAt->format('Y-m-d'),
'ordered_quantity' => $this->faker->randomFloat(2, 250, 50_000),
'price_per_kg' => $this->faker->randomFloat(4, 2, 120),
'price_total' => null,
'received_by' => null,
'received_at' => null,
'received_quantity' => null,
'batch_number' => null,
'best_before' => null,
'quality_id' => null,
'status' => 'pending',
];
}
public function ingredient(): static
{
return $this->state(fn () => [
'entry_type' => 'ingredient',
'ingredient_id' => fn () => $this->makeIngredient()->id,
'packaging_item_id' => null,
'unit' => 'gram',
'price_per_kg' => $this->faker->randomFloat(4, 2, 120),
'price_total' => null,
]);
}
public function packaging(): static
{
return $this->state(function () {
$supplier = Supplier::factory()->create();
$item = PackagingItem::factory()->create([
'supplier_id' => $supplier->id,
'category' => 'packaging',
]);
return [
'entry_type' => 'packaging',
'ingredient_id' => null,
'packaging_item_id' => $item->id,
'supplier_id' => $supplier->id,
'unit' => 'piece',
'price_per_kg' => null,
'price_total' => $this->faker->randomFloat(4, 15, 2500),
];
});
}
public function label(): static
{
return $this->state(function () {
$supplier = Supplier::factory()->create();
$item = PackagingItem::factory()->create([
'supplier_id' => $supplier->id,
'category' => 'label',
]);
return [
'entry_type' => 'label',
'ingredient_id' => null,
'packaging_item_id' => $item->id,
'supplier_id' => $supplier->id,
'unit' => 'piece',
'price_per_kg' => null,
'price_total' => $this->faker->randomFloat(4, 5, 800),
];
});
}
public function shippingOffice(): static
{
return $this->state(function () {
$supplier = Supplier::factory()->create();
$item = PackagingItem::factory()->create([
'supplier_id' => $supplier->id,
'category' => 'shipping_office',
]);
return [
'entry_type' => 'shipping_office',
'ingredient_id' => null,
'packaging_item_id' => $item->id,
'supplier_id' => $supplier->id,
'unit' => 'piece',
'price_per_kg' => null,
'price_total' => $this->faker->randomFloat(4, 8, 1500),
];
});
}
public function pending(): static
{
return $this->state(fn () => [
'status' => 'pending',
'received_by' => null,
'received_at' => null,
'received_quantity' => null,
'batch_number' => null,
'best_before' => null,
'quality_id' => null,
]);
}
public function received(): static
{
return $this->state(function (array $attributes) {
$receiver = $this->makeOrderUser();
$orderedAt = $attributes['ordered_at'] ?? now()->format('Y-m-d');
$orderedCarbon = Carbon::parse($orderedAt);
$receivedCarbon = (clone $orderedCarbon)->addDays($this->faker->numberBetween(1, 21));
if ($receivedCarbon->isFuture()) {
$receivedCarbon = Carbon::now();
}
$qty = $attributes['ordered_quantity'] ?? 1000;
$entryType = $attributes['entry_type'] ?? 'ingredient';
$extra = [
'status' => 'received',
'received_by' => $receiver->id,
'received_at' => $receivedCarbon->format('Y-m-d'),
'received_quantity' => round((float) $qty, 2),
];
if ($entryType === 'ingredient') {
$extra['batch_number'] = $this->faker->bothify('CH-####-??');
$extra['best_before'] = $this->faker->dateTimeBetween('+2 months', '+24 months')->format('Y-m-d');
$extra['quality_id'] = MaterialQuality::query()->inRandomOrder()->value('id');
} else {
$extra['batch_number'] = null;
$extra['best_before'] = null;
$extra['quality_id'] = null;
}
return $extra;
});
}
private function makeIngredient(): Ingredient
{
$name = $this->faker->randomElement([
'Shea Butter',
'Kokosöl raffiniert',
'Mandelöl süß',
'Jojobaöl',
'Distillat Rosenhydrolat',
]).' '.$this->faker->unique()->numerify('###');
return Ingredient::query()->create([
'name' => $name,
'trans_name' => '',
'inci' => $this->faker->optional(0.7)->words(3, true) ?? '',
'trans_inci' => '',
'effect' => '',
'trans_effect' => '',
'active' => true,
'pos' => 0,
]);
}
private function makeOrderUser(): User
{
return User::query()->create([
'email' => 'sf_u_'.str_replace('.', '', uniqid('', true)).'@factory.test',
'password' => bcrypt('password'),
'admin' => 7,
'confirmed' => true,
'active' => true,
'wizard' => 100,
'blocked' => false,
]);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Database\Factories;
use App\Models\SupplierCategory;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<SupplierCategory>
*/
class SupplierCategoryFactory extends Factory
{
protected $model = SupplierCategory::class;
public function definition(): array
{
return [
'name' => $this->faker->unique()->words(2, true),
'pos' => $this->faker->numberBetween(0, 50),
];
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace Database\Factories;
use App\Models\Country;
use App\Models\Supplier;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Supplier>
*/
class SupplierFactory extends Factory
{
protected $model = Supplier::class;
public function definition(): array
{
return [
'name' => $this->faker->company(),
'url' => $this->faker->optional()->url(),
'contact_person' => $this->faker->optional()->name(),
'email' => $this->faker->optional()->companyEmail(),
'phone' => $this->faker->optional()->phoneNumber(),
'country_id' => Country::query()->value('id') ?? $this->resolveCountryId(),
'notes' => null,
'active' => true,
];
}
protected function resolveCountryId(): int
{
$country = Country::query()->firstOrCreate(
['code' => 'TE'],
[
'phone' => '00',
'en' => 'Testland',
'de' => 'Testland',
'es' => 'Testland',
'fr' => 'Testland',
'it' => 'Testland',
'ru' => 'Testland',
'active' => true,
]
);
return (int) $country->id;
}
}

View file

@ -1,8 +1,8 @@
<?php <?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCountriesTable extends Migration class CreateCountriesTable extends Migration
{ {
@ -35,14 +35,11 @@ class CreateCountriesTable extends Migration
$table->boolean('currency_calc')->default(false); $table->boolean('currency_calc')->default(false);
$table->decimal('currency_faktor', 4, 2)->nullable(); $table->decimal('currency_faktor', 4, 2)->nullable();
$table->boolean('active')->default(true);
$table->text('trans_name')->nullable(); $table->text('trans_name')->nullable();
$table->text('attr')->nullable(); $table->text('attr')->nullable();
$table->timestamps(); $table->timestamps();
}); });
} }

View file

@ -1,8 +1,8 @@
<?php <?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAttributesTable extends Migration class CreateAttributesTable extends Migration
{ {
@ -25,16 +25,13 @@ class CreateAttributesTable extends Migration
$table->string('slug')->unique()->index(); $table->string('slug')->unique()->index();
$table->timestamps(); $table->timestamps();
$table->foreign('parent_id') $table->foreign('parent_id')
->references('id') ->references('id')
->on('attributes'); ->on('attributes');
$table->foreign('attribute_type_id') // FK zu attribute_types wird in create_attribute_types_table Migration gesetzt (Reihenfolge-Problem)
->references('id')
->on('attribute_types');
}); });
} }

View file

@ -1,8 +1,8 @@
<?php <?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProductAttributesTable extends Migration class CreateProductAttributesTable extends Migration
{ {
@ -32,10 +32,7 @@ class CreateProductAttributesTable extends Migration
->on('attributes') ->on('attributes')
->onDelete('cascade'); ->onDelete('cascade');
$table->foreign('type_id') // FK zu attribute_types wird in create_attribute_types_table Migration gesetzt (Reihenfolge-Problem)
->references('id')
->on('attribute_types')
->onDelete('cascade');
}); });
} }

View file

@ -1,8 +1,8 @@
<?php <?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProductImagesTable extends Migration class CreateProductImagesTable extends Migration
{ {
@ -37,10 +37,7 @@ class CreateProductImagesTable extends Migration
->on('products') ->on('products')
->onDelete('cascade'); ->onDelete('cascade');
$table->foreign('user_wl_product_id') // FK zu user_whitelabel_products wird in create_user_whitelabel_products_table Migration gesetzt (Reihenfolge-Problem)
->references('id')
->on('user_whitelabel_products')
->onDelete('cascade');
}); });
} }

View file

@ -25,7 +25,7 @@ class CreateProductBuysTable extends Migration
->references('id') ->references('id')
->on('products'); ->on('products');
$table->foreign('user_id') $table->foreign('auth_user_id')
->references('id') ->references('id')
->on('users'); ->on('users');
}); });

View file

@ -25,14 +25,24 @@ class CreateAttributeTypesTable extends Migration
$table->string('slug')->unique()->index(); $table->string('slug')->unique()->index();
$table->timestamps(); $table->timestamps();
$table->foreign('parent_id') $table->foreign('parent_id')
->references('id') ->references('id')
->on('attributes'); ->on('attributes');
});
Schema::table('attributes', function (Blueprint $table) {
$table->foreign('attribute_type_id')
->references('id')
->on('attribute_types');
});
Schema::table('product_attributes', function (Blueprint $table) {
$table->foreign('type_id')
->references('id')
->on('attribute_types')
->onDelete('cascade');
}); });
} }
@ -43,6 +53,14 @@ class CreateAttributeTypesTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::table('product_attributes', function (Blueprint $table) {
$table->dropForeign(['type_id']);
});
Schema::table('attributes', function (Blueprint $table) {
$table->dropForeign(['attribute_type_id']);
});
Schema::dropIfExists('attribute_types'); Schema::dropIfExists('attribute_types');
} }
} }

View file

@ -36,6 +36,13 @@ class CreateUserWhitelabelProductsTable extends Migration
->on('products') ->on('products')
->onDelete('cascade'); ->onDelete('cascade');
}); });
Schema::table('product_images', function (Blueprint $table) {
$table->foreign('user_wl_product_id')
->references('id')
->on('user_whitelabel_products')
->onDelete('cascade');
});
} }
/** /**
@ -45,6 +52,10 @@ class CreateUserWhitelabelProductsTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::table('product_images', function (Blueprint $table) {
$table->dropForeign(['user_wl_product_id']);
});
Schema::dropIfExists('user_whitelabel_products'); Schema::dropIfExists('user_whitelabel_products');
} }
} }

View file

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('shopping_orders', function (Blueprint $table) {
$table->text('cancellation_invoice')->nullable()->after('invoice');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('shopping_orders', function (Blueprint $table) {
$table->dropColumn('cancellation_invoice');
});
}
};

View file

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('product_ingredients', function (Blueprint $table) {
$table->unsignedSmallInteger('pos')->default(0)->after('ingredient_id');
$table->decimal('gram', 12, 2)->nullable()->after('pos');
$table->decimal('factor', 4, 2)->default(1.10)->after('gram');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('product_ingredients', function (Blueprint $table) {
$table->dropColumn(['pos', 'gram', 'factor']);
});
}
};

View file

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('products', function (Blueprint $table) {
$table->enum('shelf_life_type', ['pao', 'fixed'])->nullable()->after('max_buy_num');
$table->unsignedTinyInteger('shelf_life_months')->nullable()->after('shelf_life_type');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn(['shelf_life_type', 'shelf_life_months']);
});
}
};

View file

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('ingredients', function (Blueprint $table) {
$table->decimal('default_factor', 4, 2)->default(1.10)->after('pos');
$table->decimal('min_stock_alert', 12, 2)->nullable()->after('default_factor');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('ingredients', function (Blueprint $table) {
$table->dropColumn(['default_factor', 'min_stock_alert']);
});
}
};

View file

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('locations', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->boolean('active')->default(true);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('locations');
}
};

View file

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('supplier_categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->unsignedTinyInteger('pos')->default(0);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('supplier_categories');
}
};

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('suppliers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('url')->nullable();
$table->string('contact_person')->nullable();
$table->string('email')->nullable();
$table->string('phone', 100)->nullable();
$table->unsignedInteger('country_id');
$table->text('notes')->nullable();
$table->boolean('active')->default(true);
$table->timestamps();
$table->softDeletes();
$table->foreign('country_id')->references('id')->on('countries');
});
}
public function down(): void
{
Schema::dropIfExists('suppliers');
}
};

View file

@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('supplier_supplier_category', function (Blueprint $table) {
$table->id();
$table->foreignId('supplier_id')->constrained('suppliers')->cascadeOnDelete();
$table->foreignId('supplier_category_id')->constrained('supplier_categories')->cascadeOnDelete();
$table->timestamps();
$table->unique(['supplier_id', 'supplier_category_id'], 'supplier_supplier_cat_unique');
});
}
public function down(): void
{
Schema::dropIfExists('supplier_supplier_category');
}
};

View file

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('material_qualities', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->unsignedTinyInteger('pos')->default(0);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('material_qualities');
}
};

View file

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('packaging_materials', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->unsignedTinyInteger('pos')->default(0);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('packaging_materials');
}
};

View file

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('packaging_items', function (Blueprint $table) {
$table->id();
$table->foreignId('packaging_material_id')->constrained('packaging_materials');
$table->foreignId('supplier_id')->nullable()->constrained('suppliers')->nullOnDelete();
$table->string('name');
$table->enum('category', ['packaging', 'label', 'shipping_office']);
$table->decimal('weight_grams', 10, 2)->default(0);
$table->unsignedInteger('min_stock_alert')->nullable();
$table->unsignedInteger('product_id')->nullable();
$table->foreign('product_id')->references('id')->on('products')->nullOnDelete();
$table->boolean('active')->default(true);
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('packaging_items');
}
};

View file

@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('stock_entries', function (Blueprint $table) {
$table->id();
$table->enum('entry_type', ['ingredient', 'packaging', 'label', 'shipping_office']);
$table->unsignedInteger('ingredient_id')->nullable();
$table->foreign('ingredient_id')->references('id')->on('ingredients')->nullOnDelete();
$table->foreignId('packaging_item_id')->nullable()->constrained('packaging_items')->nullOnDelete();
$table->foreignId('supplier_id')->constrained('suppliers');
$table->foreignId('location_id')->constrained('locations');
$table->enum('unit', ['gram', 'piece']);
$table->unsignedInteger('ordered_by');
$table->foreign('ordered_by')->references('id')->on('users');
$table->date('ordered_at');
$table->decimal('ordered_quantity', 12, 2);
$table->decimal('price_per_kg', 10, 4)->nullable();
$table->decimal('price_total', 10, 4)->nullable();
$table->unsignedInteger('received_by')->nullable();
$table->foreign('received_by')->references('id')->on('users')->nullOnDelete();
$table->date('received_at')->nullable();
$table->decimal('received_quantity', 12, 2)->nullable();
$table->string('batch_number', 100)->nullable();
$table->date('best_before')->nullable();
$table->foreignId('quality_id')->nullable()->constrained('material_qualities')->nullOnDelete();
$table->enum('status', ['pending', 'received'])->default('pending');
$table->timestamps();
$table->index(['status', 'ordered_at']);
});
}
public function down(): void
{
Schema::dropIfExists('stock_entries');
}
};

View file

@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('product_packagings', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('product_id');
$table->foreign('product_id')->references('id')->on('products')->cascadeOnDelete();
$table->foreignId('packaging_item_id')->constrained('packaging_items')->cascadeOnDelete();
$table->decimal('quantity', 12, 2)->default(1);
$table->unsignedSmallInteger('pos')->default(0);
$table->timestamps();
$table->unique(['product_id', 'packaging_item_id']);
});
if (Schema::hasTable('packaging_items')) {
$rows = DB::table('packaging_items')
->whereNotNull('product_id')
->orderBy('id')
->get(['id', 'product_id']);
foreach ($rows as $row) {
DB::table('product_packagings')->insert([
'product_id' => $row->product_id,
'packaging_item_id' => $row->id,
'quantity' => 1,
'pos' => 0,
'created_at' => now(),
'updated_at' => now(),
]);
}
}
}
public function down(): void
{
Schema::dropIfExists('product_packagings');
}
};

View file

@ -0,0 +1,56 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('productions', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('product_id');
$table->foreign('product_id')->references('id')->on('products')->cascadeOnDelete();
$table->foreignId('location_id')->constrained('locations');
$table->unsignedInteger('produced_by');
$table->foreign('produced_by')->references('id')->on('users');
$table->date('produced_at');
$table->unsignedInteger('quantity');
$table->text('notes')->nullable();
$table->boolean('mhd_warning')->default(false);
$table->timestamps();
$table->index(['produced_at', 'location_id']);
});
Schema::create('production_ingredients', function (Blueprint $table) {
$table->id();
$table->foreignId('production_id')->constrained('productions')->cascadeOnDelete();
$table->unsignedInteger('ingredient_id');
$table->foreign('ingredient_id')->references('id')->on('ingredients');
$table->foreignId('stock_entry_id')->constrained('stock_entries');
$table->decimal('quantity_used', 12, 2);
$table->timestamps();
$table->index(['production_id', 'ingredient_id']);
});
Schema::create('production_packagings', function (Blueprint $table) {
$table->id();
$table->foreignId('production_id')->constrained('productions')->cascadeOnDelete();
$table->foreignId('packaging_item_id')->constrained('packaging_items');
$table->unsignedInteger('quantity_used');
$table->timestamps();
$table->index('production_id');
});
}
public function down(): void
{
Schema::dropIfExists('production_packagings');
Schema::dropIfExists('production_ingredients');
Schema::dropIfExists('productions');
}
};

View file

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('packaging_items', function (Blueprint $table) {
$table->string('url', 500)->nullable()->after('min_stock_alert');
});
}
public function down(): void
{
Schema::table('packaging_items', function (Blueprint $table) {
$table->dropColumn('url');
});
}
};

Some files were not shown because too many files have changed in this diff Show more