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
DB_CONNECTION=mysql
DB_HOST=192.168.1.8
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=grueneseele
DB_USERNAME=kadmin
DB_PASSWORD=KT32vQ7ix
DB_DATABASE=partner_gruene_seele
DB_USERNAME=root
DB_PASSWORD=password
#DB_CONNECTION=mysql
#DB_HOST=localhost
#DB_HOST=mysql
#DB_PORT=3306
#DB_DATABASE=web28_db4
#DB_USERNAME=web28_4
@ -51,16 +51,21 @@ MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_PORT=6384
#REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=w017e534.kasserver.com
MAIL_PORT=587
MAIL_USERNAME=m0496c96
MAIL_PASSWORD=mZtVp7WQcs6DC3hf
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=dev@adametz.media
MAIL_FROM_NAME="DEV Grüne Seele"
MAIL_HOST=mailpit
MAIL_PORT=1029
#MAIL_DRIVER=smtp
#MAIL_HOST=w017e534.kasserver.com
#MAIL_PORT=587
#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_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
*/
protected $signature = 'payments:reminders';
protected $description = 'Run Payments Reminders';
private $timeStart;
private $dev = false;
private $paymentReminderService;
private $stats = [
'total_processed' => 0,
'reminders_sent' => 0,
'errors' => 0,
'skipped' => 0
'skipped' => 0,
];
public function __construct(PaymentReminderService $paymentReminderService)
@ -68,7 +72,7 @@ class PaymentsReminders extends Command
$executionTime = round(microtime(true) - $this->timeStart, 2);
$this->info("\n=== PAYMENT REMINDERS ABGESCHLOSSEN ===");
$this->info("Ausführungszeit: {$executionTime} Sekunden");
$this->info("Statistiken:");
$this->info('Statistiken:');
$this->info(" - Gesamt verarbeitet: {$this->stats['total_processed']}");
$this->info(" - Erinnerungen gesendet: {$this->stats['reminders_sent']}");
$this->info(" - Fehler: {$this->stats['errors']}");
@ -76,15 +80,17 @@ class PaymentsReminders extends Command
\Log::info('PaymentsReminders Command completed successfully', [
'execution_time' => $executionTime,
'stats' => $this->stats
'stats' => $this->stats,
]);
return 0;
} catch (\Exception $e) {
\Log::error('PaymentsReminders Command failed', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
'trace' => $e->getTraceAsString(),
]);
$this->error('Command failed: ' . $e->getMessage());
$this->error('Command failed: '.$e->getMessage());
return 1;
}
}
@ -98,40 +104,44 @@ class PaymentsReminders extends Command
// Hole alle aktiven PaymentReminder und gruppiere sie nach clearingtype
$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()) {
$this->warn("Keine aktiven PaymentReminder gefunden!");
$this->warn('Keine aktiven PaymentReminder gefunden!');
return;
}
// Finde für jeden clearingtype das kleinste Intervall (in Tagen)
$intervals = $this->paymentReminderService->getActiveIntervals();
$this->info("Gefundene clearingtypes mit kleinsten Intervallen:");
foreach($intervals as $clearingtype => $interval){
$this->info('Gefundene clearingtypes mit kleinsten Intervallen:');
foreach ($intervals as $clearingtype => $interval) {
$this->line(" - {$clearingtype}: {$interval} Tage");
}
// Verarbeite jeden clearingtype mit seinem kleinsten Intervall
foreach($intervals as $clearingtype => $interval){
foreach ($intervals as $clearingtype => $interval) {
$this->info("\n--- Verarbeite clearingtype: {$clearingtype} mit Intervall: {$interval} Tage ---");
$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
$shopping_payments = $this->paymentReminderService->getOpenPaymentsForClearingType($clearingtype, $interval);
$this->info("Gefundene offene Zahlungen für {$clearingtype}: " . $shopping_payments->count());
$this->info("Gefundene offene Zahlungen für {$clearingtype}: ".$shopping_payments->count());
if ($shopping_payments->isEmpty()) {
$this->line(" Keine Zahlungen für {$clearingtype} gefunden.");
$this->line("Keine Zahlungen für {$clearingtype} gefunden.");
continue;
}
// Verarbeite jede Zahlung
foreach($shopping_payments as $shopping_payment){
$this->line('--- START processPayment VERARBEITUNG');
foreach ($shopping_payments as $shopping_payment) {
$this->processPayment($shopping_payment, $clearingtype);
}
}
@ -145,24 +155,23 @@ class PaymentsReminders extends Command
$this->stats['total_processed']++;
try {
$this->line(" Verarbeite Order ID: {$shopping_payment->shopping_order_id}, Created: {$shopping_payment->created_at->format('d.m.Y H:i:s')}, Amount: {$shopping_payment->amount}, Reminder: {$shopping_payment->reminder}");
$this->line("Verarbeite Order ID: {$shopping_payment->shopping_order_id}, Created: {$shopping_payment->created_at->format('d.m.Y H:i:s')}, Amount: {$shopping_payment->amount}, Reminder: {$shopping_payment->reminder}");
// Prüfe ob eine Erinnerung gesendet werden soll
if ($this->shouldSendReminder($shopping_payment, $clearingtype)) {
$this->sendReminderForPayment($shopping_payment);
} else {
$this->line(" ⏭️ Übersprungen - Keine Erinnerung fällig");
$this->line('Übersprungen - Keine Erinnerung fällig');
$this->stats['skipped']++;
}
} 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']++;
\Log::error('Error processing payment reminder', [
'order_id' => $shopping_payment->shopping_order_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
$payment_reminders = PaymentReminder::where('active', true)
->where('clearingtype', $clearingtype)
->orderBy('interval', 'asc')
->orderBy('interval', 'asc') // von kein nach gross
->get();
if ($payment_reminders->isEmpty()) {
$this->line('shouldSendReminder - keine PaymentReminders');
return false;
}
// Wenn alle Erinnerungen bereits gesendet wurden
if ($shopping_payment->reminder >= $payment_reminders->count()) {
$this->line('shouldSendReminder - alle Erinnerungen wurden bereits gesendet');
return false;
}
// Hole die nächste Erinnerung
$next_reminder = $payment_reminders[$shopping_payment->reminder];
$next_reminder = isset($payment_reminders[$shopping_payment->reminder]) ? $payment_reminders[$shopping_payment->reminder] : null;
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
if ($shopping_payment->reminder == 0) {
if ($shopping_payment->reminder == null || $shopping_payment->reminder == 0) {
$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;
}
// Hole die nächste Erinnerung
// Wenn bereits Erinnerungen gesendet wurden, prüfe das nächste Intervall
if ($shopping_payment->reminder_date) {
$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;
$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);
}
@ -218,12 +246,12 @@ class PaymentsReminders extends Command
private function sendReminderForPayment($shopping_payment)
{
try {
$this->line(" 📧 Sende Erinnerung...");
$this->line('Sende Erinnerung...');
$result = $this->paymentReminderService->sendReminder($shopping_payment);
if ($result) {
$this->line(" ✅ Erinnerung erfolgreich gesendet");
$this->line('Erinnerung erfolgreich gesendet');
$this->stats['reminders_sent']++;
// Log für Cron-Job
@ -231,21 +259,20 @@ class PaymentsReminders extends Command
'order_id' => $shopping_payment->shopping_order_id,
'payment_id' => $shopping_payment->id,
'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 {
$this->line(" ⚠️ Keine Erinnerung gesendet (keine weitere Erinnerung verfügbar)");
$this->line('Keine Erinnerung gesendet (keine weitere Erinnerung verfügbar)');
$this->stats['skipped']++;
}
} catch (\Exception $e) {
$this->error(" ❌ Fehler beim Senden der Erinnerung: " . $e->getMessage());
$this->error('Fehler beim Senden der Erinnerung: '.$e->getMessage());
$this->stats['errors']++;
\Log::error('Error sending payment reminder', [
'order_id' => $shopping_payment->shopping_order_id,
'payment_id' => $shopping_payment->id,
'error' => $e->getMessage()
'error' => $e->getMessage(),
]);
}
}

View file

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

View file

@ -1,21 +1,17 @@
<?php
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\MailVerifyAccount;
use App\Models\UserHistory;
use App\Models\UserMessage;
use App\User;
use Carbon;
use Illuminate\Support\Facades\Mail;
class UserCheckPaymentsAccounts
{
/*RULES
reminders
> 29 renewal_days > set next_m_level to m_level
@ -27,53 +23,56 @@ class UserCheckPaymentsAccounts
> 0 deaktiv = reminder_deaktiv //status 35
*/
public static function userReminderPayments(User $user, $dev){
//35 reminder_deaktiv
if(!$user->isActiveAccount()){ // payment_account gt now
public static function userReminderPayments(User $user, $dev)
{
// 35 reminder_deaktiv
if (! $user->isActiveAccount()) { // payment_account gt now
return self::checkIsReminderSend($user, 35, $dev);
}
//34 reminder_last
if($user->daysActiveAccount() <= config('main.remind_last_days')){
// 34 reminder_last
if ($user->daysActiveAccount() <= config('main.remind_last_days')) {
return self::checkIsReminderSend($user, 34, $dev);
}
//33 reminder_sec
if($user->daysActiveAccount() <= config('main.remind_sec_days')){
// 33 reminder_sec
if ($user->daysActiveAccount() <= config('main.remind_sec_days')) {
return self::checkIsReminderSend($user, 33, $dev);
}
//31 reminder_first
if($user->daysActiveAccount() > config('main.remind_sec_days')){
// 31 reminder_first
if ($user->daysActiveAccount() > config('main.remind_sec_days')) {
return self::checkIsReminderSend($user, 31, $dev);
}
return 0;
}
private static function checkIsReminderSend(User $user, $status, $dev){
private static function checkIsReminderSend(User $user, $status, $dev)
{
$isSend = UserHistory::whereUserId($user->id)
->whereAction('reminder_payments')
->whereIdentifier($user->payment_account)
->whereStatus($status)
->get()->last();
->whereAction('reminder_payments')
->whereIdentifier($user->payment_account)
->whereStatus($status)
->get()->last();
if($isSend){
if ($isSend) {
return 0;
}
if($dev){
if ($dev) {
$referenz = 0;
}else{
} else {
$referenz = self::sendReminderMail($user, $status);
}
UserHistory::create(['user_id' => $user->id, 'action'=>'reminder_payments', 'referenz'=>$referenz, 'identifier'=>$user->payment_account, 'status'=>$status]);
return $status;
UserHistory::create(['user_id' => $user->id, 'action' => 'reminder_payments', 'referenz' => $referenz, 'identifier' => $user->payment_account, 'status' => $status]);
return $status;
}
private function sendReminderMail(User $user, $status){
private static function sendReminderMail(User $user, $status)
{
$days = $user->daysActiveAccount();
if($days < 0){
$days = $days*-1;
if ($days < 0) {
$days = $days * -1;
}
$datetime = $user->getPaymentAccountDateFormat();
@ -85,77 +84,75 @@ class UserCheckPaymentsAccounts
$price = 'von '.$user->payment_order_product->getFormattedPrice().' EUR';
}*/
$message = __('reminder.copy_first_'.$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]);
$message = __('reminder.copy_first_'.$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);
$message = preg_replace("/[\n\r]/","",$message);
$message_last = preg_replace("/[\n\r]/","",$message_last);
$message = preg_replace("/[\n\r]/", '', $message);
$message_last = preg_replace("/[\n\r]/", '', $message_last);
$data = [
'subject' => __('reminder.subject')." | ID: ".$status,
'subject' => __('reminder.subject').' | ID: '.$status,
'message' => $message,
'message_last' => $message_last,
'url' => route('user_membership'),
'button' => $button,
];
//dump($data);
// dump($data);
$sender = User::find(1);
$customer_mail = UserMessage::create([
'user_id' => $user->id,
'user_id' => $user->id,
'send_user_id' => $sender->id,
'email' => $user->email,
'subject' => $data['subject'],
'message' => $data['message']." ".$data['message_last'],
'message' => $data['message'].' '.$data['message_last'],
]);
try{
if($status >= 34){
//Mail::to($user->email)->bcc(config('app.info_mail'))->send(new MailCustomMessage($user, $data, $sender, false));
}else{
//Mail::to($user->email)->send(new MailCustomMessage($user, $data, $sender, false));
try {
if ($status >= 34) {
// Mail::to($user->email)->bcc(config('app.info_mail'))->send(new MailCustomMessage($user, $data, $sender, false));
} else {
// 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());
// Never reached
$customer_mail->fail = true;
$customer_mail->error = $e->getMessage();
$customer_mail->save();
return 0;
}
$customer_mail->send = true;
$customer_mail->sent_at = now();
$customer_mail->save();
return 1;
}
}
/*public function checkConfirmation()
{
User Register sind in der DB UserRegister, erst bei bestätigung wird es in die User DB übertragen
/*public function checkConfirmation()
{
User Register sind in der DB UserRegister, erst bei bestätigung wird es in die User DB übertragen
$now = date('Y-m-d H:i:s');
$next = date('Y-m-d H:i:s', strtotime('+3 week'));
$now = date('Y-m-d H:i:s');
$next = date('Y-m-d H:i:s', strtotime('+3 week'));
$users = User::where('confirmed', '=', 0)->where('confirmation_code_to', '<', $now)->get();
$users = User::where('confirmed', '=', 0)->where('confirmation_code_to', '<', $now)->get();
foreach ($users as $user) {
foreach ($users as $user) {
//delete user
if ($user->confirmation_code_remider == 1) {
$this->userRepo->deleteUser($user);
}
//send new remider
if ($user->confirmation_code_remider == 0) {
Mail::to($user->email)->bcc(config('app.info_mail'))->send(new MailVerifyAccount($user->confirmation_code, $user));
$user->confirmation_code_to = $next;
$user->confirmation_code_remider = 1;
$user->save();
}
}
return "TOSK";
}*/
//delete user
if ($user->confirmation_code_remider == 1) {
$this->userRepo->deleteUser($user);
}
//send new remider
if ($user->confirmation_code_remider == 0) {
Mail::to($user->email)->bcc(config('app.info_mail'))->send(new MailVerifyAccount($user->confirmation_code, $user));
$user->confirmation_code_to = $next;
$user->confirmation_code_remider = 1;
$user->save();
}
}
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;
use Storage;
use Response;
use App\Models\File;
use App\Models\ShoppingOrder;
use App\Models\UserCredit;
use App\Services\Credit;
use App\Services\Invoice;
use App\Services\PDFMerger;
use Auth;
use Response;
use Storage;
class FileController extends Controller
{
@ -15,26 +19,25 @@ class FileController extends Controller
*
* @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;
}
abort(404);
abort(404);
}
public function show($id = null, $disk = null, $do='file')
public function show($id = null, $disk = null, $do = 'file')
{
$path = "";
$filename = "";
$path = '';
$filename = '';
if($disk === 'user'){
$file = \App\Models\File::findOrFail($id);
if ($disk === 'user') {
$file = File::findOrFail($id);
$this->isPermission($file->user_id);
$path = Storage::disk($disk)->path($file->dir.$file->filename);
if (file_exists($path)) {
@ -42,23 +45,29 @@ class FileController extends Controller
}
}
if ($disk === 'invoice'){
$shopping_order = \App\Models\ShoppingOrder::findOrFail($id);
if ($disk === 'invoice') {
$shopping_order = ShoppingOrder::findOrFail($id);
$this->isPermission($shopping_order->auth_user_id);
$filename = Invoice::getFilename($shopping_order);
$path = Invoice::getDownloadPath($shopping_order);
}
if ($disk === 'delivery'){
$shopping_order = \App\Models\ShoppingOrder::findOrFail($id);
if ($disk === 'delivery') {
$shopping_order = ShoppingOrder::findOrFail($id);
$this->isPermission($shopping_order->auth_user_id);
$filename = Invoice::getDeliveryFilename($shopping_order);
$path = Invoice::getDownloadPathDelivery($shopping_order);
}
if ($disk === 'invoice_delivery'){
$shopping_order = \App\Models\ShoppingOrder::findOrFail($id);
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') {
$shopping_order = ShoppingOrder::findOrFail($id);
$this->isPermission($shopping_order->auth_user_id);
$ifilename = Invoice::getFilename($shopping_order);
@ -66,7 +75,7 @@ class FileController extends Controller
$dfilename = Invoice::getDeliveryFilename($shopping_order);
$dpath = Invoice::getDownloadPathDelivery($shopping_order, true);
$oMerger = new \App\Services\PDFMerger();
$oMerger = new PDFMerger;
$oMerger->init();
$oMerger->addPDF($ipath);
@ -75,61 +84,61 @@ class FileController extends Controller
$oMerger->setFileName($filename);
$oMerger->merge();
$file = $oMerger->output();
return Response::make($file, 200)
->header("Content-Type", 'application/pdf')
->header('Content-Type', 'application/pdf')
->header('Content-disposition', 'attachment; filename="'.$filename.'"');
}
if ($disk === 'credit'){
$UserCredit = \App\Models\UserCredit::findOrFail($id);
if ($disk === 'credit') {
$UserCredit = UserCredit::findOrFail($id);
$this->isPermission($UserCredit->auth_user_id);
$filename = Credit::getFilename($UserCredit);
$path = Credit::getDownloadPath($UserCredit);
}
if (!Storage::disk('public')->exists($path)) {
if (! Storage::disk('public')->exists($path)) {
return Response::make('File no found.', 404);
}
$file = Storage::disk('public')->get($path);
$mime = Storage::disk('public')->mimeType($path);
if($do === 'download'){
if ($do === 'download') {
return Response::make($file, 200)
->header("Content-Type", $mime)
->header('Content-Type', $mime)
->header('Content-disposition', 'attachment; filename="'.$filename.'"');
/* $full_path = Invoice::getDownloadPath($shopping_order, true);
$he
if (file_exists($full_path)) {
return Response::download($full_path, $filename);
}*/
/* $full_path = Invoice::getDownloadPath($shopping_order, true);
$he
if (file_exists($full_path)) {
return Response::download($full_path, $filename);
}*/
}
if($do === 'stream'){
if ($do === 'stream') {
return Response::make($file, 200)
->header("Content-Type", $mime)
->header('Content-disposition','filename="'.$filename.'"');
}
if($do === 'file'){
return Response::make($file, 200)
->header("Content-Type", $mime)
->header('Content-Type', $mime)
->header('Content-disposition', 'filename="'.$filename.'"');
}
if($do === 'image'){
return Response::make($file, 200)
->header("Content-Type", $mime);
}
if($do === 'pdf'){
$path = storage_path().'/app/public/' . $path;
$headers = array(
'Content-Type:'. $mime,
// 'Content-Length: ' . $file->size
// 'Content-Disposition: ' . $stream . '; filename=' . $file->original_name
);
return Response::download($path, $filename, $headers);
}
if ($do === 'file') {
return Response::make($file, 200)
->header('Content-Type', $mime)
->header('Content-disposition', 'filename="'.$filename.'"');
}
if ($do === 'image') {
return Response::make($file, 200)
->header('Content-Type', $mime);
}
if ($do === 'pdf') {
$path = storage_path().'/app/public/'.$path;
$headers = [
'Content-Type:'.$mime,
// 'Content-Length: ' . $file->size
// 'Content-Disposition: ' . $stream . '; filename=' . $file->original_name
];
return Response::download($path, $filename, $headers);
}
}
}

View file

@ -2,18 +2,12 @@
namespace App\Http\Controllers;
use App\Models\Category;
use App\Models\Ingredient;
use App\Models\IqImage;
use App\Models\ProductCategory;
use App\Models\ProductIngredient;
use Request;
class IngredientController extends Controller
{
public function __construct()
{
$this->middleware('copyreader');
@ -25,22 +19,24 @@ class IngredientController extends Controller
$data = [
'values' => Ingredient::all(),
];
return view('admin.ingredient.index', $data);
}
public function edit($id)
{
if($id === "new"){
$model = new Ingredient();
if ($id === 'new') {
$model = new Ingredient;
$model->active = true;
}else{
} else {
$model = Ingredient::findOrFail($id);
}
$data = [
'model' => $model,
//'trans' => array_keys(config('localization.supportedLocales')),
// 'trans' => array_keys(config('localization.supportedLocales')),
];
return view('admin.ingredient.edit', $data);
}
@ -49,30 +45,42 @@ class IngredientController extends Controller
$data = Request::all();
$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);
}else{
} else {
$model = Ingredient::find($data['id']);
$model->fill($data)->save();
}
\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');
return redirect(route('admin_product_ingredients'));
}
$model = Ingredient::findOrFail($id);
$model->delete();
\Session()->flash('alert-success', 'Eintrag gelöscht');
return redirect(route('admin_product_ingredients'));
}
}

View file

@ -3,6 +3,8 @@
namespace App\Http\Controllers;
use App\Models\Country;
use App\Models\Ingredient;
use App\Models\PackagingItem;
use App\Models\Product;
use App\Models\ProductImage;
use App\Models\ProductIngredient;
@ -10,8 +12,6 @@ use App\Repositories\ProductRepository;
use Request;
use Validator;
class ProductController extends Controller
{
protected $productRepo;
@ -24,35 +24,40 @@ class ProductController extends Controller
public function index()
{
if(Request::get('show_active_products')){
if (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();
}else{
} else {
$values = Product::orderBy('pos', 'DESC')->orderBy('id', 'DESC')->get();
}
$data = [
'values' => $values
'values' => $values,
];
return view('admin.product.index', $data);
}
public function edit($id)
{
if($id === "new"){
$model = new Product();
if ($id === 'new') {
$model = new Product;
$model->active = true;
}else{
} else {
$model = Product::findOrFail($id);
$model->load(['packagings.packagingMaterial']);
}
$country_for_prices = Country::where('own_eur', '=', true)->orWhere('currency', '=', true)->get();
$data = [
'product' => $model,
'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);
}
@ -61,34 +66,36 @@ class ProductController extends Controller
$data = Request::all();
$rules = array(
$rules = [
'name' => 'required',
);
];
/*if(isset($data['number']) && $data['number'] != ""){
$rules['number'] = 'int';
}*/
if(isset($data['wp_number'])){
if($data['id'] !== "new"){
if (isset($data['wp_number'])) {
if ($data['id'] !== 'new') {
$model = Product::findOrFail($data['id']);
$rules['wp_number'] = 'unique:products,wp_number,'.$model->id;
}else{
} else {
$rules['wp_number'] = 'unique:products,wp_number';
}
}
$validator = Validator::make(Request::all(), $rules);
if($data['id'] === "new"){
$model = new Product();
}else{
if ($data['id'] === 'new') {
$model = new Product;
} else {
$model = Product::findOrFail($data['id']);
$model->load(['packagings.packagingMaterial']);
}
$country_for_prices = Country::where('own_eur', '=', true)->orWhere('currency', '=', true)->get();
$data = [
'product' => $model,
'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()) {
@ -98,34 +105,41 @@ class ProductController extends Controller
} else {
$product = $this->productRepo->update(Request::all());
\Session()->flash('alert-save', true);
return redirect(route('admin_product_edit', [$product->id]));
}
\Session()->flash('alert-save', '1');
return redirect(route('admin_product_show'));
}
public function copy($id){
public function copy($id)
{
$model = Product::findOrFail($id);
$product = $this->productRepo->copy($model);
\Session()->flash('alert-success', 'Eintrag kopiert');
return redirect(route('admin_product_show'));
}
public function delete($id, $do = 'product', $did = null){
if($do === 'product'){
public function delete($id, $do = 'product', $did = null)
{
if ($do === 'product') {
$model = Product::findOrFail($id);
$model->delete();
\Session()->flash('alert-success', 'Eintrag gelöscht');
return redirect(route('admin_product_show'));
}
if($do === 'ingredient'){
if ($do === 'ingredient') {
$model = Product::findOrFail($id);
$ProductIngredient = ProductIngredient::where('ingredient_id', $did)->where('product_id', $model->id)->first();
if($ProductIngredient){
if ($ProductIngredient) {
$ProductIngredient->delete();
\Session()->flash('alert-success', 'Eintrag gelöscht');
return redirect(route('admin_product_edit', [$model->id]));
}
@ -134,24 +148,29 @@ class ProductController extends Controller
}
// 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'));
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);
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;
}
@ -160,9 +179,9 @@ class ProductController extends Controller
$product_image->{$attr} = $val;
$product_image->save();
\Session()->flash('alert-success', "Wert gespeichert");
\Session()->flash('alert-success', 'Wert gespeichert');
return redirect()->back();
}
}

View file

@ -2,46 +2,48 @@
namespace App\Http\Controllers;
use Request;
use App\Models\Setting;
use App\Services\Payment;
use App\Models\ShoppingUser;
use App\Models\ShoppingOrder;
use App\Models\UserPayCredit;
use App\Models\ShoppingPayment;
use App\Models\PaymentTransaction;
use App\Services\CustomerPriority;
use App\Models\ShoppingUser;
use App\Repositories\InvoiceRepository;
use App\Services\CustomerPriority;
use App\Services\Invoice;
use App\Services\Payment;
use App\Services\PaymentService;
use Request;
class SalesController extends Controller
{
public function __construct(){
public function __construct()
{
$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_member_id', null);
set_user_attr('filter_art', null);
set_user_attr('filter_shipped', null);
return redirect(route('admin_sales'));
}
//set Filter!
// set Filter!
$filter_members = ShoppingOrder::join('users', 'member_id', '=', 'users.id')->groupBy('member_id')->join('user_accounts', 'account_id', '=', 'user_accounts.id')->select('users.id', 'users.email', 'user_accounts.first_name', 'user_accounts.last_name')->get();
$data = [
'filter_members' => $filter_members,
];
return view('admin.sales.index', $data);
}
public function detail($id){
public function detail($id)
{
$ShoppingOrder = ShoppingOrder::find($id);
if($ShoppingOrder->shipped == 0){
if ($ShoppingOrder->shipped == 0) {
$ShoppingOrder->shipped = 1;
$ShoppingOrder->save();
}
@ -54,37 +56,42 @@ class SalesController extends Controller
return view('admin.sales.detail', $data);
}
public function detailStore($id){
public function detailStore($id)
{
$data = Request::all();
$change_member_error = false;
if($data['action']==='shopping-order-change-member'){
if(!isset($data['change_member_key']) || $data['change_member_key'] !== config('main.edit_data_pass')){
$change_member_error = "Das Passwort ist falsch.";
}else{
//change
if ($data['action'] === 'shopping-order-change-member') {
if (! isset($data['change_member_key']) || $data['change_member_key'] !== config('main.edit_data_pass')) {
$change_member_error = 'Das Passwort ist falsch.';
} else {
// change
$shopping_order = ShoppingOrder::findOrFail($data['id']);
CustomerPriority::newMemberForOrder($shopping_order, $data['change_member_id'], $data['customer_set_member_for']);
\Session()->flash('alert-save', true);
return redirect(route('admin_sales_detail', [$shopping_order->id]));
}
}
if($data['action']==='shopping-user-is-like-member'){
if(!isset($data['change_member_key']) || $data['change_member_key'] !== config('main.edit_data_pass')){
\Session()->flash('alert-error', 'Das Passwort ist falsch.');
return redirect($data['back']);
}else{
if(!isset($data['is_like_shopping_user_id'])){
if ($data['action'] === 'shopping-user-is-like-member') {
if (! isset($data['change_member_key']) || $data['change_member_key'] !== config('main.edit_data_pass')) {
\Session()->flash('alert-error', 'Das Passwort ist falsch.');
return redirect($data['back']);
} else {
if (! isset($data['is_like_shopping_user_id'])) {
\Session()->flash('alert-error', 'Keine Änderung ausgewählt');
return redirect($data['back']);
}
$shopping_user = ShoppingUser::findOrFail($data['id']);
$set_like_shopping_user = ShoppingUser::findOrFail($data['is_like_shopping_user_id']);
$send_member_mail = isset($data['send_member_mail']) ? true : false;
$change_shopping_user = isset($data['change_shopping_user']) ? true : false;
//Mail send in setIsLike
// Mail send in setIsLike
CustomerPriority::setIsLike($shopping_user, $set_like_shopping_user, $send_member_mail, $change_shopping_user);
\Session()->flash('alert-save', true);
return redirect($data['back']);
}
}
@ -95,54 +102,57 @@ class SalesController extends Controller
'isAdmin' => true,
'isView' => $ShoppingOrder->auth_user_id ? 'sales_user' : 'sales_customer',
];
return view('admin.sales.detail', $data);
}
public function datatable(){
public function datatable()
{
$query = ShoppingOrder::with('shopping_user', 'shopping_payments')->select('shopping_orders.*');
set_user_attr('filter_txaction', Request::get('filter_txaction'));
if(Request::get('filter_txaction') != ""){
if(Request::get('filter_txaction') === 'NULL'){
$query->where('txaction', '=', NULL);
if (Request::get('filter_txaction') != '') {
if (Request::get('filter_txaction') === 'NULL') {
$query->where('txaction', '=', null);
}else{
} else {
$query->where('txaction', '=', Request::get('filter_txaction'));
}
}
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'));
}
set_user_attr('filter_art', Request::get('filter_art'));
if(Request::get('filter_art') != ""){
if(Request::get('filter_art') === 'user_order'){
$query->where('shopping_orders.auth_user_id', '!=', NULL)->where('payment_for', '!=', 6);
}elseif(Request::get('filter_art') === 'customer_order'){
$query->where('shopping_orders.auth_user_id', NULL);
}elseif(Request::get('filter_art') === 'user_for_customer'){
$query->where('shopping_user_id', '!=', NULL)->where('payment_for', '=', 6);
if (Request::get('filter_art') != '') {
if (Request::get('filter_art') === 'user_order') {
$query->where('shopping_orders.auth_user_id', '!=', null)->where('payment_for', '!=', 6);
} elseif (Request::get('filter_art') === 'customer_order') {
$query->where('shopping_orders.auth_user_id', null);
} elseif (Request::get('filter_art') === 'user_for_customer') {
$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'));
if(Request::get('filter_shipped') != ""){
if (Request::get('filter_shipped') != '') {
$query->where('shipped', '=', Request::get('filter_shipped'));
}
return \DataTables::eloquent($query)
->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) {
return $ShoppingOrder->created_at->format("d.m.Y");
return $ShoppingOrder->created_at->format('d.m.Y');
})
->addColumn('txaction', function (ShoppingOrder $ShoppingOrder) {
return Payment::getShoppingOrderBadge($ShoppingOrder);
})
->addColumn('total_shipping', function (ShoppingOrder $ShoppingOrder) {
return $ShoppingOrder->getFormattedTotalShipping()."";
return $ShoppingOrder->getFormattedTotalShipping().' €';
})
->addColumn('payment', function (ShoppingOrder $ShoppingOrder) {
return $ShoppingOrder->getLastShoppingPayment('getPaymentType');
@ -157,10 +167,10 @@ class SalesController extends Controller
return $ShoppingOrder->getLastShoppingPayment('reference');
})
->addColumn('member_id', function (ShoppingOrder $ShoppingOrder) {
if($ShoppingOrder->member_id) {
return $ShoppingOrder->member_id ? '<a href="' . route('admin_lead_edit', [$ShoppingOrder->member_id]) . '">' . $ShoppingOrder->member->getFullName() . '</a>' : '';
if ($ShoppingOrder->member_id) {
return $ShoppingOrder->member_id ? '<a href="'.route('admin_lead_edit', [$ShoppingOrder->member_id]).'">'.$ShoppingOrder->member->getFullName().'</a>' : '';
}
if($ShoppingOrder->shopping_user && $ShoppingOrder->shopping_user->is_like){
if ($ShoppingOrder->shopping_user && $ShoppingOrder->shopping_user->is_like) {
return '<button type="button" class="btn btn-xs btn-outline-info" data-toggle="modal" data-target="#modals-load-content"
data-id="'.$ShoppingOrder->shopping_user->id.'"
data-action="shopping-user-is-like-member"
@ -168,6 +178,7 @@ class SalesController extends Controller
data-modal="modal-xl"
data-route="'.route('modal_load').'"><span class="fa fa-edit"></span> Vertriebspartner zuordnen</button>';
}
return '';
})
@ -400,34 +411,35 @@ class SalesController extends Controller
->make(true);
}*/
public function store(){
public function store()
{
$data = Request::all();
if(!isset($data['id'])){
if (! isset($data['id'])) {
abort(404);
}
if(isset($data['action'])){
if($data['action'] === 'store_shipped' && isset($data['shipped'])){
if (isset($data['action'])) {
if ($data['action'] === 'store_shipped' && isset($data['shipped'])) {
$shopping_order = ShoppingOrder::findOrFail($data['id']);
$shopping_order->shipped = $data['shipped'];
$shopping_order->save();
//handel Promotion Product and credit by storno
// handel Promotion Product and credit by storno
Payment::handelUserPromotionOrder($shopping_order);
Payment::handelUserShopOrder($shopping_order);
if($shopping_order->getAPIShippedType() === 'sent' || $shopping_order->getAPIShippedType() === 'close'){
if(!$shopping_order->shipped_at){
if ($shopping_order->getAPIShippedType() === 'sent' || $shopping_order->getAPIShippedType() === 'close') {
if (! $shopping_order->shipped_at) {
$shopping_order->shipped_at = now();
$shopping_order->save();
//is shipped set pending_to
if($shopping_order->shopping_order_margin){
if($shopping_order->shopping_order_margin->hasPartnerCommission()){
$days = Setting::getContentBySlug('pending_partner_commissions_in_days');
// is shipped set pending_to
if ($shopping_order->shopping_order_margin) {
if ($shopping_order->shopping_order_margin->hasPartnerCommission()) {
$days = Setting::getContentBySlug('pending_partner_commissions_in_days');
$days = $days ? $days : 20;
$partner_commission_pending_to = $shopping_order->shipped_at;
$partner_commission_pending_to->addDays($days);
$shopping_order->shopping_order_margin->partner_commission_pending_to = $partner_commission_pending_to;
$shopping_order->shopping_order_margin->save();
}else{
} else {
$days = Setting::getContentBySlug('pending_order_margins_in_days');
$days = $days ? $days : 20;
$margin_pending_to = $shopping_order->shipped_at;
@ -437,25 +449,25 @@ class SalesController extends Controller
}
}
}
}else{
} else {
$shopping_order->shipped_at = null;
$shopping_order->save();
if($shopping_order->shopping_order_margin){
//zurücksetzen der pending_to
if ($shopping_order->shopping_order_margin) {
// zurücksetzen der pending_to
$shopping_order->shopping_order_margin->partner_commission_pending_to = null;
$shopping_order->shopping_order_margin->margin_pending_to = null;
$shopping_order->shopping_order_margin->save();
}
}
if($shopping_order->getAPIShippedType() === 'cancel'){
if($shopping_order->shopping_order_margin){
if ($shopping_order->getAPIShippedType() === 'cancel') {
if ($shopping_order->shopping_order_margin) {
$shopping_order->shopping_order_margin->cancellation = true;
$shopping_order->shopping_order_margin->partner_commission_pending_to = null;
$shopping_order->shopping_order_margin->margin_pending_to = null;
$shopping_order->shopping_order_margin->save();
}
}else{
if($shopping_order->shopping_order_margin && $shopping_order->shopping_order_margin->cancellation){
} else {
if ($shopping_order->shopping_order_margin && $shopping_order->shopping_order_margin->cancellation) {
$shopping_order->shopping_order_margin->cancellation = false;
$shopping_order->shopping_order_margin->partner_commission_pending_to = null;
$shopping_order->shopping_order_margin->margin_pending_to = null;
@ -463,53 +475,94 @@ class SalesController extends Controller
}
}
}
/* txaction ändern
ä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'])) {
PaymentService::updateTransactionStatus($data['id'], $data['txaction'], $data['payment_id']);
}
}
if(isset($data['back'])){
if (isset($data['back'])) {
return redirect($data['back']);
}
return back();
}
public function invoice(){
public function invoice()
{
$data = Request::all();
if(!isset($data['id'])){
if (! isset($data['id'])) {
abort(404);
}
if(isset($data['action'])){
if($data['action'] === 'create_invoice'){
if (isset($data['action'])) {
if ($data['action'] === 'create_invoice') {
$shopping_order = ShoppingOrder::findOrFail($data['id']);
$invoice_repo = new InvoiceRepository($shopping_order);
if(\App\Services\Invoice::isInvoice($shopping_order)){
if (Invoice::isInvoice($shopping_order)) {
$user_invoice = $invoice_repo->update($data);
}else{
} else {
$user_invoice = $invoice_repo->create($data);
}
return redirect(route('admin_sales_detail', [$shopping_order->id]));
}
}
}
public function sendLogisticMail($id){
$shopping_order = ShoppingOrder::findOrFail($id);
if(\App\Services\Invoice::isInvoice($shopping_order)){
\App\Services\Invoice::sendLogisticMail($shopping_order);
\Session()->flash('alert-success', "Rechnung / Lieferschein wurde an den Versand gesendet.");
}else{
\Session()->flash('alert-error', "Keine Rechnung vorhanden.");
public function invoiceCancellation()
{
$data = Request::all();
if (! isset($data['id'])) {
abort(404);
}
return redirect(route('admin_sales_detail', [$shopping_order->id]));
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);
if (Invoice::isInvoice($shopping_order)) {
Invoice::sendLogisticMail($shopping_order);
\Session()->flash('alert-success', 'Rechnung / Lieferschein wurde an den Versand gesendet.');
} else {
\Session()->flash('alert-error', 'Keine Rechnung vorhanden.');
}
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
namespace App\Mail;
use App\User;
use App\Services\Invoice;
use App\Models\ShoppingOrder;
use App\Services\Invoice;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Storage;
use Illuminate\Contracts\Queue\ShouldQueue;
class MailLogistic extends Mailable
{
@ -18,65 +17,66 @@ class MailLogistic extends Mailable
public $subject;
public function __construct(ShoppingOrder $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 ?? '';
$hasWhiteLabel = $shopping_order->user_white_label || $shopping_order->hasWhitelabelProducts();
$this->subject = 'Partner';
if($shopping_order->user_white_label){
//Bei allen, die ein eigenes Logo haben
if ($hasWhiteLabel) {
// Bei allen, die ein eigenes Logo haben
$this->subject .= ' (mit Logo)';
}else{
//Bei allen, die kein Tattoostudio sind
} else {
// Bei allen, die kein Tattoostudio sind
$this->subject = ' - ';
}
if($shopping_order->shopping_user->same_as_billing){
//Rechnungsadresse und Lieferadresse sind gleich
if ($shopping_order->shopping_user->same_as_billing) {
// Rechnungsadresse und Lieferadresse sind gleich
$this->subject = '';
}else{
//hat eine andere Lieferadresse
} else {
// hat eine andere Lieferadresse
$this->subject = ' Lieferadresse';
}
$this->subject .= ' '.$company.' (' . $name . ')';
$this->subject .= ' '.$company.' ('.$name.')';
}
public function build()
{
$title = false;
$title = false;
$copy1line = false;
$filename = Invoice::getFilename($this->shopping_order);
$path = Invoice::getDownloadPath($this->shopping_order);
if (!Storage::disk('public')->exists($path)) {
if (! Storage::disk('public')->exists($path)) {
return;
}
$file = Storage::disk('public')->path($path);
$mime = Storage::disk('public')->mimeType($path);
$mail = $this->view('emails.logistic')->with([
'title' => $title,
'copy1line' => $copy1line,
])->attach($file,[
'copy1line' => $copy1line,
])->attach($file, [
'as' => $filename,
'mime' => $mime,
]);
//Wenn das Logo gesetzt ist und die Rechnungsadresse und Lieferadresse unterschiedlich sind, dann wird der Lieferschein angehängt
if($this->shopping_order->user_white_label && !$this->shopping_order->shopping_user->same_as_billing){
// Wenn mindestens ein White-Label-Produkt enthalten ist, wird der Lieferschein (Etikett-Infos) mit angehängt
$hasWhiteLabel = $this->shopping_order->user_white_label || $this->shopping_order->hasWhitelabelProducts();
if ($hasWhiteLabel) {
$filenameDelivery = Invoice::getDeliveryFilename($this->shopping_order);
$pathDelivery = Invoice::getDownloadPathDelivery($this->shopping_order);
if (!Storage::disk('public')->exists($pathDelivery)) {
if (! Storage::disk('public')->exists($pathDelivery)) {
return;
}
$fileDelivery = Storage::disk('public')->path($pathDelivery);
$mimeDelivery = Storage::disk('public')->mimeType($pathDelivery);
$mail->attach($fileDelivery,[
$mail->attach($fileDelivery, [
'as' => $filenameDelivery,
'mime' => $mimeDelivery,
]); // attach file;

View file

@ -9,6 +9,7 @@ namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* Class Ingredient
@ -25,10 +26,10 @@ use Illuminate\Database\Eloquent\Model;
* @property Carbon $created_at
* @property Carbon $updated_at
* @property Collection|Product[] $products
* @package App\Models
* @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 $products_count
*
* @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 query()
@ -43,38 +44,52 @@ 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 whereTransName($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Ingredient whereUpdatedAt($value)
*
* @mixin \Eloquent
*/
class Ingredient extends Model
{
protected $table = 'ingredients';
protected $table = 'ingredients';
protected $casts = [
'active' => 'bool',
'pos' => 'int'
];
protected $casts = [
'active' => 'bool',
'pos' => 'int',
'default_factor' => 'decimal:2',
'min_stock_alert' => 'decimal:2',
];
protected $fillable = [
'name',
'trans_name',
'inci',
'trans_inci',
'effect',
'trans_effect',
'active',
'pos'
];
protected $fillable = [
'name',
'trans_name',
'inci',
'trans_inci',
'effect',
'trans_effect',
'active',
'pos',
'default_factor',
'min_stock_alert',
'material_quality_id',
];
public function products()
{
return $this->belongsToMany(Product::class, 'product_ingredients')
->withPivot('id')
->withTimestamps();
}
/**
* @return BelongsTo<MaterialQuality, $this>
*/
public function materialQuality(): BelongsTo
{
return $this->belongsTo(MaterialQuality::class);
}
public function products()
{
return $this->belongsToMany(Product::class, 'product_ingredients')
->withPivot('id', 'pos', 'gram', 'factor')
->withTimestamps()
->orderByPivot('pos');
}
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,38 +20,56 @@ use Illuminate\Database\Eloquent\Model;
* @property bool $active
* @property Carbon|null $created_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
{
protected $table = 'payment_reminders';
protected $table = 'payment_reminders';
protected $casts = [
'interval' => 'int',
'active' => 'bool'
];
protected $casts = [
'interval' => 'int',
'active' => 'bool',
];
protected $fillable = [
'title',
'subject',
'interval',
'message',
'action',
'clearingtype',
'active'
];
protected $fillable = [
'title',
'subject',
'interval',
'message',
'action',
'clearingtype',
'active',
];
protected static $clearingtypes = [
protected static $clearingtypes = [
'fnc' => 'Rechnung',
'vor' => 'Vorkasse',
];
public function getClearingtype(){
return isset(self::$clearingtypes[$this->clearingtype]) ? self::$clearingtypes[$this->clearingtype] : 'Kein Typ';
}
public function getClearingtype()
{
return isset(self::$clearingtypes[$this->clearingtype]) ? self::$clearingtypes[$this->clearingtype] : 'Kein Typ';
}
public static function returnClearingtypes(){
return self::$clearingtypes;
}
public static function returnClearingtypes()
{
return self::$clearingtypes;
}
}

View file

@ -5,8 +5,12 @@ namespace App\Models;
use App\Services\Type;
use App\Services\Util;
use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
/**
* App\Models\Product
@ -34,13 +38,14 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property int|null $pos
* @property int $active
* @property int|null $amount
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductAttribute[] $attributes
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductCategory[] $categories
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductImage[] $images
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property Carbon|null $deleted_at
* @property-read Collection|ProductAttribute[] $attributes
* @property-read Collection|ProductCategory[] $categories
* @property-read Collection|ProductImage[] $images
* @property-write mixed $price_vk
*
* @method static bool|null forceDelete()
* @method static \Illuminate\Database\Query\Builder|\App\Models\Product onlyTrashed()
* @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\Query\Builder|\App\Models\Product withTrashed()
* @method static \Illuminate\Database\Query\Builder|\App\Models\Product withoutTrashed()
*
* @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 whereSlug($value)
* @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 query()
*
* @property int|null $weight
* @property int|null $show_at
* @property array|null $action
* @property-read int|null $attributes_count
* @property-read int|null $categories_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 whereShowAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereWeight($value)
*
* @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
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product wherePoints($value)
*
* @property string|null $identifier
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereIdentifier($value)
*
* @property int|null $upgrade_to_id
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereUpgradeToId($value)
*
* @property int|null $contents_total
* @property int|null $unit
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereContentsTotal($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 int|null $wp_number
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereWpNumber($value)
*
* @property bool|null $single_commission
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Product whereShippingAddon($value)
*
* @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 \Illuminate\Database\Eloquent\Collection|\App\Models\Ingredient[] $p_ingredients
* @property-read Collection|Ingredient[] $p_ingredients
* @property-read int|null $p_ingredients_count
* @property bool $amount_commission
* @property string|null $value_commission
* @property string|null $partner_commission
*
* @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 whereSingleCommission($value)
* @method static \Illuminate\Database\Eloquent\Builder|Product whereValueCommission($value)
*
* @property bool|null $shipping_addon
* @property bool|null $max_buy
* @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
*
* @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 withUniqueSlugConstraints(\Illuminate\Database\Eloquent\Model $model, string $attribute, array $config, string $slug)
*
* @property string|null $short_copy
* @property array|null $show_on
*
* @method static \Illuminate\Database\Eloquent\Builder|Product whereShortCopy($value)
* @method static \Illuminate\Database\Eloquent\Builder|Product whereShowOn($value)
*
* @property bool $exclude_stats_sales
* @property bool|null $whitelabel
* @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 \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProductImage> $whitelabel_images
* @property-read Collection<int, ProductImage> $whitelabel_images
* @property-read int|null $whitelabel_images_count
*
* @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 whereWhitelabelName($value)
*
* @mixin \Eloquent
*/
class Product extends Model
{
/*identifiers
show_upgrade # in membership payment can upgrade this package to show order
show_order # in membership payment show always
@ -172,8 +201,8 @@ class Product extends Model
'max_buy_num' => 'int',
'whitelabel' => 'bool',
];
use Sluggable;
use Sluggable;
use SoftDeletes;
protected $dates = ['deleted_at'];
@ -215,7 +244,9 @@ class Product extends Model
'upgrade_to_id',
'shipping_addon',
'max_buy',
'max_buy_num'
'max_buy_num',
'shelf_life_type',
'shelf_life_months',
];
@ -225,8 +256,9 @@ class Product extends Model
'show_order' => 'Wird immer als Option angezeigt',
'upgrade' => 'Produktupgrade zur Produkt ID',
'upgrade_member' => 'Vertriebspartnerupgrade zur Karriere ID',
//'proportional_voucher' => 'Anteiliger Gutschein Vertriebspartner',
// 'proportional_voucher' => 'Anteiliger Gutschein Vertriebspartner',
];
public $unitTypes = [
0 => '',
1 => 'ml',
@ -238,9 +270,9 @@ class Product extends Model
public $actions = [
0 => 'payment_for_account',
1 => 'charging_credits',
// 1 => 'payment_for_shop',
// 2 => 'payment_for_shop_upgrade',
// 4 => 'payment_for_lead_upgrade',
// 1 => 'payment_for_shop',
// 2 => 'payment_for_shop_upgrade',
// 4 => 'payment_for_lead_upgrade',
];
@ -248,34 +280,39 @@ class Product extends Model
{
return [
'slug' => [
'source' => 'name'
]
'source' => 'name',
],
];
}
public function attributes(){
return $this->hasMany(ProductAttribute::class, 'product_id', 'id')->where('type_id','!=', 1);
public function attributes()
{
return $this->hasMany(ProductAttribute::class, 'product_id', 'id')->where('type_id', '!=', 1);
}
public function attribute_variants(){
return $this->hasMany(ProductAttribute::class, 'product_id', 'id')->where('type_id','=', 1);
public function attribute_variants()
{
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');
}
public function images(){
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type','=', 'product')->orderBy('pos');
public function images()
{
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'product')->orderBy('pos');
}
public function imagesActive(){
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type','=', 'product')->where('active', true)->orderBy('pos');
public function imagesActive()
{
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'product')->where('active', true)->orderBy('pos');
}
public function whitelabel_images(){
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type','=', 'wllogo')->orderBy('pos');
public function whitelabel_images()
{
return $this->hasMany(ProductImage::class, 'product_id', 'id')->where('type', '=', 'wllogo')->orderBy('pos');
}
public function country_prices()
@ -291,81 +328,130 @@ class Product extends Model
public function p_ingredients()
{
return $this->belongsToMany(Ingredient::class, 'product_ingredients')
->withPivot('id')
->withTimestamps();
->withPivot('id', 'pos', 'gram', 'factor', 'recipe_type')
->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()
{
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;
if($len && $clean){
if ($len && $clean) {
return substr_ellipsis($ret, $len, $clean);
}
return $ret;
}
public function getActionName($id = 0){
if(isset($this->actions[$id])){
public function getActionName($id = 0)
{
if (isset($this->actions[$id])) {
return $this->actions[$id];
}
return false;
}
public function getWhiteLableName($id = 0){
public function getWhiteLableName($id = 0)
{
return $this->whitelabel_name ? $this->whitelabel_name : $this->name;
}
public function _format_number($value){
return preg_replace("/[^0-9,]/", "", $value);
public function _format_number($value)
{
return preg_replace('/[^0-9,]/', '', $value);
}
public function setPriceAttribute( $value ) {
public function setPriceAttribute($value)
{
$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;
}
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;
}
public function setValueCommissionAttribute( $value ) {
public function setValueCommissionAttribute($value)
{
$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;
}
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()
{
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)
{
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()
{
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()
{
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;
}
/*price by user Factor*/
private function calcPriceUserFactor($price){
/* price by user Factor */
private function calcPriceUserFactor($price)
{
/*
Nicht in benutzung, die margin errechnet sich im Warenkorb, wegen der Staffelprovision
if(\Auth::user() && \Auth::user()->user_level){
@ -388,15 +474,19 @@ class Product extends Model
*/
return $price;
}
/*price net*/
private function calcPriceNet($price, $country=null){
/* price net */
private function calcPriceNet($price, $country = null)
{
$tax = $this->getTaxWith($country);
$tax_rate = ($tax + 100) / 100;
$tax_rate = ($tax + 100) / 100;
return $price / $tax_rate;
}
//price calu with
public function getPriceWith(Bool $net = true, Bool $ufactor = true, $country = null, $commission=false){
// price calu with
public function getPriceWith(bool $net = true, bool $ufactor = true, $country = null, $commission = false)
{
$price = isset($this->attributes['price']) ? $this->attributes['price'] : null;
/*$cprice = $country ? $this->getCPrice($country) : null; //eigener Preis für Land
@ -405,81 +495,95 @@ class Product extends Model
$price = $net ? $this->calcPriceNet($price, $country) : $price;
$price = $ufactor ? $this->calcPriceUserFactor($price) : $price;
$price = $commission ? $this->calcPriceUserCommission($price) : $price;
return round($price, 2);
}
/*out*/
public function getFormattedPriceWith(Bool $net = true, Bool $ufactor = true)
/* out */
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;
$ctax = $country ? $this->getCTax($country) : null;
return $ctax !== null ? $ctax : $tax;
}
public function getBasePriceFormattedFull(){
if($price = $this->getBasePrice()){
public function getBasePriceFormattedFull()
{
if ($price = $this->getBasePrice()) {
$unit = $this->attributes['unit'];
//ml g
if($unit === 1 || $unit === 2){
return Util::formatNumber($price) . ' € / 100 '.$this->getUnitType();
// ml g
if ($unit === 1 || $unit === 2) {
return Util::formatNumber($price).' € / 100 '.$this->getUnitType();
}
//l kg
if($unit === 3 || $unit === 4){
return Util::formatNumber($price) . ' € / 1 '.$this->getUnitType();
// l kg
if ($unit === 3 || $unit === 4) {
return Util::formatNumber($price).' € / 1 '.$this->getUnitType();
}
}
return "";
return '';
}
public function getBasePriceFormatted(){
if($price = $this->getBasePrice()){
public function getBasePriceFormatted()
{
if ($price = $this->getBasePrice()) {
return Util::formatNumber($price);
}
return "";
return '';
}
public function getBasePrice(){
if(isset($this->attributes['unit']) && isset($this->attributes['contents_total']) && $this->attributes['contents_total'] != 0){
public function getBasePrice()
{
if (isset($this->attributes['unit']) && isset($this->attributes['contents_total']) && $this->attributes['contents_total'] != 0) {
$unit = $this->attributes['unit'];
//ml g
if($unit === 1 || $unit === 2){
// ml g
if ($unit === 1 || $unit === 2) {
return $this->attributes['price'] * 100 / $this->attributes['contents_total'];
}
//l kg
if($unit === 3 || $unit === 4){
return $this->attributes['price'] * 1000 / $this->attributes['contents_total'];
// l kg
if ($unit === 3 || $unit === 4) {
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] : '-';
}
public function getShowAtType(){
public function getShowAtType()
{
return isset(Type::$showATs[$this->show_at]) ? Type::$showATs[$this->show_at] : '-';
}
public function getShowOnTypes($seperator = false){
public function getShowOnTypes($seperator = false)
{
$ret = [];
if($this->show_on && is_array($this->show_on)){
foreach($this->show_on as $show){
if ($this->show_on && is_array($this->show_on)) {
foreach ($this->show_on as $show) {
$ret[] = isset(Type::$showONs[$show]) ? Type::$showONs[$show] : '-';
}
}
return $seperator ? implode($seperator, $ret) : $ret;
}
public function setPosAttribute($value){
public function setPosAttribute($value)
{
$this->attributes['pos'] = is_numeric($value) ? $value : null;
}
public function getLang($key)
{
$lang = \App::getLocale();
@ -487,62 +591,75 @@ class Product extends Model
return $this->{$key};
}
$trans = $this->getTrans($key, $lang);
if (!$trans || $trans == '') {
if (! $trans || $trans == '') {
return $this->{$key};
}
return $trans;
}
public function getTrans($key, $lang)
{
$key = 'trans_' . $key;
if (!empty($this->{$key}[$lang])) {
$key = 'trans_'.$key;
if (! empty($this->{$key}[$lang])) {
return $this->{$key}[$lang];
}
}
public function getTranNames()
{
$ret = "";
foreach ((array) $this->trans_name as $value){
$ret = '';
foreach ((array) $this->trans_name as $value) {
$ret .= $value.', ';
}
return rtrim($ret, ', ');
}
public function getCountryPrice($country_id){
return $this->country_prices->where('country_id', '=', $country_id)->first() ?: new CountryPrice();
public function getCountryPrice($country_id)
{
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;
}
public function getCTax($country_id){
public function getCTax($country_id)
{
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;
}
public function getCCurrency($country_id){
public function getCCurrency($country_id)
{
return $this->getCountryPrice($country_id)->c_currency;
}
public function getRealPrice(Country $country){
if($country->own_eur && $this->getCPrice($country->id)){
public function getRealPrice(Country $country)
{
if ($country->own_eur && $this->getCPrice($country->id)) {
return $this->getCPrice($country->id);
}
return $this->price;
}
public function getFormattedPriceCurrencyWith(Bool $net = true, Bool $ufactor = true, Country $country = null, $commission = false){
$ret = "";
if($country && isset($country->currency) && $country->currency){
$price = $this->getPriceWith($net, $ufactor, $country, $commission);
$ret = formatNumber($price * $country->currency_faktor)." ".$country->currency_unit;
public function getFormattedPriceCurrencyWith(bool $net = true, bool $ufactor = true, ?Country $country = null, $commission = false)
{
$ret = '';
if ($country && isset($country->currency) && $country->currency) {
$price = $this->getPriceWith($net, $ufactor, $country, $commission);
$ret = formatNumber($price * $country->currency_faktor).' '.$country->currency_unit;
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 Ingredient $ingredient
* @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 newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient query()
@ -28,29 +28,37 @@ 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 whereProductId($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductIngredient whereUpdatedAt($value)
*
* @mixin \Eloquent
*/
class ProductIngredient extends Model
{
protected $table = 'product_ingredients';
protected $table = 'product_ingredients';
protected $casts = [
'product_id' => 'int',
'ingredient_id' => 'int'
];
protected $casts = [
'product_id' => 'int',
'ingredient_id' => 'int',
'pos' => 'int',
'gram' => 'decimal:3',
'factor' => 'decimal:2',
];
protected $fillable = [
'product_id',
'ingredient_id'
];
protected $fillable = [
'product_id',
'ingredient_id',
'pos',
'gram',
'factor',
'recipe_type',
];
public function ingredient()
{
return $this->belongsTo(Ingredient::class, 'ingredient_id');
}
public function ingredient()
{
return $this->belongsTo(Ingredient::class, 'ingredient_id');
}
public function product()
{
return $this->belongsTo(Product::class, 'product_id');
}
public function product()
{
return $this->belongsTo(Product::class, 'product_id');
}
}

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;
use App\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
/**
* App\Models\ShoppingOrder
@ -22,16 +25,17 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property int|null $weight
* @property int|null $paid
* @property string|null $txaction
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\User|null $auth_user
* @property-read \App\Models\Country $country
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ShoppingOrderItem[] $shopping_order_items
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read User|null $auth_user
* @property-read Country $country
* @property-read Collection|ShoppingOrderItem[] $shopping_order_items
* @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 \App\Models\ShoppingUser $shopping_user
* @property-read \App\Models\UserShop $user_shop
* @property-read ShoppingUser $shopping_user
* @property-read UserShop $user_shop
*
* @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 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 whereUserShopId($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereWeight($value)
*
* @property int|null $payment_for
*
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder wherePaymentFor($value)
*
* @property int|null $member_id
* @property string|null $mode
* @property-read \App\User|null $member
* @property-read \App\Models\UserHistory|null $user_history
* @property-read User|null $member
* @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 whereMode($value)
* @property \Illuminate\Support\Carbon|null $deleted_at
*
* @property Carbon|null $deleted_at
* @property string|null $user_deleted_at
*
* @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 whereUserDeletedAt($value)
* @method static \Illuminate\Database\Query\Builder|\App\Models\ShoppingOrder withTrashed()
* @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 $subtotal_shipping
* @property int|null $points
* @property int|null $shipped
* @property string|null $tracking
* @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 whereShipped($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 whereTracking($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingOrder whereWpInvoicePath($value)
*
* @property array|null $wp_notice
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereWpNotice($value)
*
* @property string|null $subtotal_full
* @property-read \App\Models\ShoppingOrderMargin|null $shopping_order_margin
* @property-read \App\Models\ShoppingPayment|null $shopping_payment_last
* @property-read ShoppingOrderMargin|null $shopping_order_margin
* @property-read ShoppingPayment|null $shopping_payment_last
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereSubtotalFull($value)
*
* @property string|null $discount
* @property string|null $payment_credit
*
* @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 whereSubtotalShipping($value)
*
* @property string|null $total_without_credit
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereTotalWithoutCredit($value)
*
* @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 whereShippedAt($value)
*
* @property string|null $invoice_number
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereInvoiceNumber($value)
*
* @property int|null $promotion_user_id
* @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 whereShippingOption($value)
*
* @property array|null $delivery
*
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereDelivery($value)
*
* @property bool $user_white_label
*
* @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
*/
class ShoppingOrder extends Model
@ -114,6 +149,7 @@ class ShoppingOrder extends Model
protected $table = 'shopping_orders';
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $fillable = [
@ -139,6 +175,7 @@ class ShoppingOrder extends Model
'weight',
'paid',
'invoice',
'cancellation_invoice',
'delivery',
'user_white_label',
'invoice_number',
@ -155,6 +192,7 @@ class ShoppingOrder extends Model
protected $casts = [
'wp_notice' => 'array',
'invoice' => 'array',
'cancellation_invoice' => 'array',
'delivery' => 'array',
'shipped_at' => 'datetime',
'user_white_label' => 'boolean',
@ -167,14 +205,14 @@ class ShoppingOrder extends Model
3 => 'abgeschlossen',
5 => 'Wartestellung',
4 => 'Abholung',
10 => 'storniert'
10 => 'storniert',
];
public static $paymentForTypes = [
0 => '',
1 => 'Registrierung',
2 => 'Mitgliedschaft',
3 => 'Guthabenaufladung', //guthaben
3 => 'Guthabenaufladung', // guthaben
4 => 'VP.Bestellung Abholung',
5 => 'VP.Bestellung Lieferung',
6 => 'VP.Kundenbestellung',
@ -182,7 +220,7 @@ class ShoppingOrder extends Model
8 => 'Shop',
9 => '-',
10 => 'extern',
11 => ''
11 => '',
];
public static $paymentForColors = [
@ -198,18 +236,17 @@ class ShoppingOrder extends Model
8 => 'info',
9 => 'default',
10 => 'info',
11 => 'default'
11 => 'default',
];
public static $apiShippedTypes = [
0 => 'open', //(Fullfilment durch Händler)',
1 => 'process', //(Fullfilment: nicht Versand)
2 => 'sent', //(Fullfilment: Versand erfolgt)'
3 => 'close', //(Fullfilment: Versand erfolgt)',
4 => 'pick_up', //(Fullfilment: Versand erfolgt)',
10 => 'cancel'
0 => 'open', // (Fullfilment durch Händler)',
1 => 'process', // (Fullfilment: nicht Versand)
2 => 'sent', // (Fullfilment: Versand erfolgt)'
3 => 'close', // (Fullfilment: Versand erfolgt)',
4 => 'pick_up', // (Fullfilment: Versand erfolgt)',
10 => 'cancel',
];
public static $shippedColors = [
@ -224,101 +261,126 @@ class ShoppingOrder extends Model
public function shopping_user()
{
return $this->belongsTo('App\Models\ShoppingUser','shopping_user_id');
return $this->belongsTo('App\Models\ShoppingUser', 'shopping_user_id');
}
public function country()
{
return $this->belongsTo('App\Models\ShippingCountry','country_id');
return $this->belongsTo('App\Models\ShippingCountry', 'country_id');
}
public function shipping_country()
{
return $this->belongsTo('App\Models\ShippingCountry','country_id');
return $this->belongsTo('App\Models\ShippingCountry', 'country_id');
}
public function promotion_user()
{
return $this->belongsTo('App\Models\PromotionUser','promotion_user_id');
return $this->belongsTo('App\Models\PromotionUser', 'promotion_user_id');
}
public function user_shop()
{
return $this->belongsTo('App\Models\UserShop','user_shop_id');
return $this->belongsTo('App\Models\UserShop', 'user_shop_id');
}
//can null
// can null
public function member()
{
return $this->belongsTo('App\User','member_id');
return $this->belongsTo('App\User', 'member_id');
}
//can null
// can null
public function auth_user()
{
return $this->belongsTo('App\User','auth_user_id');
return $this->belongsTo('App\User', 'auth_user_id');
}
public function user_history()
{
return $this->hasOne('App\Models\UserHistory','shopping_order_id')->latest();
return $this->hasOne('App\Models\UserHistory', 'shopping_order_id')->latest();
}
public function shopping_order_margin()
{
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');
}
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');
}
public function shopping_payment_last(){
public function shopping_payment_last()
{
return $this->hasOne('App\Models\ShoppingPayment', 'shopping_order_id')->latest();
}
public function setUserHistoryValue($values = []){
if($user_history = $this->user_history){
foreach ($values as $key=>$val){
public function setUserHistoryValue($values = [])
{
if ($user_history = $this->user_history) {
foreach ($values as $key => $val) {
$user_history->{$key} = $val;
}
$user_history->save();
}
}
public function getLastShoppingPayment($key=false){
public function getLastShoppingPayment($key = false)
{
$shopping_payment = $this->shopping_payments->last();
if($shopping_payment){
if($key === 'getPaymentType'){
if ($shopping_payment) {
if ($key === 'getPaymentType') {
return $shopping_payment->getPaymentType();
}
if($key === 'reference'){
if ($key === 'reference') {
return $shopping_payment->reference;
}
}
return "";
return '';
}
public function getShippedType(){
return isset(self::$shippedTypes[$this->shipped]) ? self::$shippedTypes[$this->shipped] : "";
public function getShippedType()
{
return isset(self::$shippedTypes[$this->shipped]) ? self::$shippedTypes[$this->shipped] : '';
}
public function getAPIShippedType(){
return isset(self::$apiShippedTypes[$this->shipped]) ? self::$apiShippedTypes[$this->shipped] : "free";
public function getAPIShippedType()
{
return isset(self::$apiShippedTypes[$this->shipped]) ? self::$apiShippedTypes[$this->shipped] : 'free';
}
public function getShippedColor(){
return isset(self::$shippedColors[$this->shipped]) ? self::$shippedColors[$this->shipped] : "default";
public function getShippedColor()
{
return isset(self::$shippedColors[$this->shipped]) ? self::$shippedColors[$this->shipped] : 'default';
}
public function getPaymentForType(){
return isset(self::$paymentForTypes[$this->payment_for]) ? self::$paymentForTypes[$this->payment_for] : "";
public function getPaymentForType()
{
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()
@ -360,39 +422,46 @@ class ShoppingOrder extends Model
{
return formatNumber($this->attributes['tax']);
}
public function getFormattedTotalWithoutCredit()
{
return formatNumber($this->attributes['total_without_credit']);
}
public function getFormattedPaymentCredit()
{
return formatNumber($this->attributes['payment_credit']);
}
public function getFormattedTotalShipping()
{
return formatNumber($this->attributes['total_shipping']);
}
public function getItemsCount(){
public function getItemsCount()
{
$count = 0;
if($this->shopping_order_items){
foreach ($this->shopping_order_items as $shopping_order_item){
if ($this->shopping_order_items) {
foreach ($this->shopping_order_items as $shopping_order_item) {
$count += $shopping_order_item->qty;
}
}
return $count;
}
public function isInvoice(){
public function isInvoice()
{
return $this->user_invoice ? true : false;
}
public function isPickUp(){
public function isPickUp()
{
return $this->shipping_option === 'pick_up' ? true : false;
}
public function isTax(){
public function isTax()
{
return $this->tax > 0 ? true : false;
}
}

View file

@ -3,7 +3,9 @@
namespace App\Models;
use App\Services\Util;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
/**
* App\Models\ShoppingPayment
@ -18,11 +20,12 @@ use Illuminate\Database\Eloquent\Model;
* @property string $currency
* @property string|null $status
* @property string|null $txaction
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\PaymentTransaction[] $payment_transactions
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read Collection|PaymentTransaction[] $payment_transactions
* @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 newQuery()
* @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 whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingPayment whereWallettype($value)
*
* @property string|null $mode
*
* @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
*/
class ShoppingPayment extends Model
@ -58,7 +70,7 @@ class ShoppingPayment extends Model
'reminder',
'reminder_date',
'txaction',
'mode'
'mode',
];
protected $casts = [
@ -66,44 +78,46 @@ class ShoppingPayment extends Model
'reminder_date' => 'datetime',
];
public function shopping_order()
{
return $this->belongsTo('App\Models\ShoppingOrder','shopping_order_id');
return $this->belongsTo('App\Models\ShoppingOrder', 'shopping_order_id');
}
public function payment_transactions()
{
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';
}
if($this->clearingtype === 'cc') {
if ($this->clearingtype === 'cc') {
return 'Kreditkarte';
}
if($this->clearingtype === 'vor') {
if ($this->clearingtype === 'vor') {
return 'Vorkasse';
}
if($this->clearingtype === 'elv') {
if ($this->clearingtype === 'elv') {
return 'SEPA Lastschrift';
}
if($this->clearingtype === 'sb') {
if ($this->clearingtype === 'sb') {
return 'Sofort Überweisung';
}
if($this->clearingtype === 'fnc') {
if ($this->clearingtype === 'fnc') {
return 'Rechnung';
}
if($this->clearingtype === 'non') {
if ($this->clearingtype === 'non') {
return 'keine';
}
return 'keine';
}
public function getPaymentAmount(){
return Util::formatNumber($this->amount/100)." ".$this->currency;
public function getPaymentAmount()
{
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;
use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@ -15,6 +17,7 @@ class AppServiceProvider extends ServiceProvider
public function boot()
{
Schema::defaultStringLength(191);
URL::forceScheme('https');
}
/**
@ -26,7 +29,7 @@ class AppServiceProvider extends ServiceProvider
{
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;
use App\Models\CountryPrice;
use App\Models\Attribute;
use App\Models\CountryPrice;
use App\Models\Ingredient;
use App\Models\Product;
use App\Models\ProductAttribute;
use App\Models\ProductCategory;
use App\Models\ProductImage;
use App\Models\ProductIngredient;
use App\Services\Slim;
class ProductRepository extends BaseRepository {
class ProductRepository extends BaseRepository
{
public function __construct(Product $model)
{
$this->model = $model;
}
/**
* refresh.
*/
@ -37,59 +34,211 @@ class ProductRepository extends BaseRepository {
$data['max_buy'] = isset($data['max_buy']) ? 1 : 0;
$data['show_on'] = isset($data['show_on']) ? $data['show_on'] : null;
if($data['id'] === "new"){
$this->model = Product::create($data);
if (array_key_exists('shelf_life_type', $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->fill($data);
$this->model->save();
}
$this->updateCategories(isset($data['categories']) ? $data['categories'] : []);
$this->updateAttributes(isset($data['attributes']) ? $data['attributes'] : []);
$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);
return $this->model;
}
public function updateIngredients($data = array())
public function updatePackagings(array $data = []): bool
{
$ProductIngredient = $this->model->p_ingredients()->pluck('ingredient_id')->toArray();
//set attr
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,
]);
}
}
if (! isset($data['pp_packaging_item_id']) || ! is_array($data['pp_packaging_item_id'])) {
$this->model->packagings()->detach();
return true;
}
$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 updateCategories($data = array())
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) {
if(($pos = array_search($category->category_id, $data)) !== FALSE){
if (($pos = array_search($category->category_id, $data)) !== false) {
unset($data[$pos]);
}else{
} else {
$category->delete();
}
}
//set attr
if(is_array($data)){
// set attr
if (is_array($data)) {
foreach ($data as $id) {
ProductCategory::create([
'product_id' => $this->model->id,
@ -97,20 +246,21 @@ class ProductRepository extends BaseRepository {
]);
}
}
return true;
}
public function updateAttributes($data = array())
public function updateAttributes($data = [])
{
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]);
}else{
} else {
$attribute->delete();
}
}
//set attr
if(is_array($data)){
// set attr
if (is_array($data)) {
foreach ($data as $id) {
$attribute = Attribute::findOrFail($id);
ProductAttribute::create([
@ -120,20 +270,21 @@ class ProductRepository extends BaseRepository {
]);
}
}
return true;
}
public function updateWLVariants($data = array())
public function updateWLVariants($data = [])
{
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]);
}else{
} else {
$variant->delete();
}
}
//set attr
if(is_array($data)){
// set attr
if (is_array($data)) {
foreach ($data as $id) {
$attribute = Attribute::findOrFail($id);
ProductAttribute::create([
@ -143,34 +294,32 @@ class ProductRepository extends BaseRepository {
]);
}
}
return true;
}
public function updateWLImageAttributs($attributes = [], $variants = [])
{
//abgleich der attributes gegen die variants
// abgleich der attributes gegen die variants
foreach ($attributes as $image => $value) {
foreach ($value as $k => $val) {
if(!is_array($variants) || !in_array($val, $variants)){
if (! is_array($variants) || ! in_array($val, $variants)) {
unset($attributes[$image][$k]);
}
}
}
foreach ($this->model->whitelabel_images as $image) {
$image->update([
'attributes' => isset($attributes[$image->id]) ? $attributes[$image->id] : NULL,
]);
$image->update([
'attributes' => isset($attributes[$image->id]) ? $attributes[$image->id] : null,
]);
}
return true;
}
public function updateCountryPrices($data)
{
if(!isset($data['country_prices']) || !is_array($data['country_prices'])){
if (! isset($data['country_prices']) || ! is_array($data['country_prices'])) {
return false;
}
foreach ($data['country_prices'] as $k => $country_id) {
@ -178,61 +327,77 @@ class ProductRepository extends BaseRepository {
'country_id' => $country_id,
'product_id' => $this->model->id,
],
[
'c_price' => isset($data['c_price'][$country_id]) ? reFormatNumber($data['c_price'][$country_id]) : null,
'c_tax' => isset($data['c_tax'][$country_id]) ? reFormatNumber($data['c_tax'][$country_id]) : null,
'c_price_old' => isset($data['c_price_old'][$country_id]) ? reFormatNumber($data['c_price_old'][$country_id]) : null,
'c_currency' => isset($data['c_currency'][$country_id]) ? reFormatNumber($data['c_currency'][$country_id]) : null,
]);
[
'c_price' => isset($data['c_price'][$country_id]) ? reFormatNumber($data['c_price'][$country_id]) : null,
'c_tax' => isset($data['c_tax'][$country_id]) ? reFormatNumber($data['c_tax'][$country_id]) : null,
'c_price_old' => isset($data['c_price_old'][$country_id]) ? reFormatNumber($data['c_price_old'][$country_id]) : null,
'c_currency' => isset($data['c_currency'][$country_id]) ? reFormatNumber($data['c_currency'][$country_id]) : null,
]);
}
return true;
}
public function copy($model)
{
$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->save();
//categories
foreach ($model->categories as $category){
// categories
foreach ($model->categories as $category) {
ProductCategory::create([
'product_id' => $this->model->id,
'category_id' => $category->category_id,
]);
}
//attributes
foreach ($model->attributes as $attribute){
// attributes
foreach ($model->attributes as $attribute) {
ProductAttribute::create([
'product_id' => $this->model->id,
'type_id' => $this->model->attribute_type_id,
'type_id' => $attribute->type_id,
'attribute_id' => $attribute->attribute_id,
]);
}
//INCS
$ingredients = $model->p_ingredients()->pluck('ingredient_id')->toArray();
if(is_array($ingredients)){
foreach ($ingredients as $incs_id){
ProductIngredient::create([
'product_id' => $this->model->id,
'ingredient_id' => $incs_id,
]);
}
foreach ($model->p_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' => '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',
]);
}
//images
foreach ($model->images as $image){
$name = \App\Services\Slim::sanitizeFileName($image->original_name);
$name = uniqid() . '_' . $name;
$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);
//copy
// images
foreach ($model->images as $image) {
$name = Slim::sanitizeFileName($image->original_name);
$name = uniqid().'_'.$name;
// copy
$data = \Storage::disk('public')->copy(
'images/product/'.$image->product_id.'/'.$image->filename,
'images/product/'.$this->model->id.'/'.$name
@ -246,7 +411,7 @@ class ProductRepository extends BaseRepository {
'ext' => $image->ext,
'mine' => $image->mine,
'size' => $image->size,
'attributes' => $image->attributes
'attributes' => $image->attributes,
]);
}
@ -254,10 +419,5 @@ class ProductRepository extends BaseRepository {
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,60 +2,54 @@
namespace App\Repositories;
use Str;
use App\User;
use stdClass;
use Validator;
use Carbon\Carbon;
use App\Models\PaymentMethod;
use App\Models\UserAccount;
use App\Models\UserRegister;
use App\Models\PaymentMethod;
use App\Services\UserService;
use App\User;
use Illuminate\Support\Facades\Hash;
use stdClass;
use Validator;
class UserRepository extends BaseRepository {
class UserRepository extends BaseRepository
{
public function __construct(User $model)
{
$this->model = $model;
}
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([
'email' => $data['email'],
'password' => env('APP_KEY'),
'password' => Hash::make(config('app.key')),
]);
$this->model->payment_methods = PaymentMethod::getDefaultAsArray();
$this->model->save();
}
else{
} else {
$this->model = $this->getById($data['user_id']);
}
if(!$this->model->account_id){
$account = new UserAccount();
}else{
if (! $this->model->account_id) {
$account = new UserAccount;
} else {
$account = $this->model->account;
}
$data['same_as_billing'] = !isset($data['same_as_billing']) ? 0 : 1;
$data['same_as_billing'] = ! isset($data['same_as_billing']) ? 0 : 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_year'] = isset($data['birthday_year']) ? $data['birthday_year'] : 1900;
$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_day'].'.'.$data['birthday_month'].'.'.$data['birthday_year'];
$data['birthday'] = $data['birthday'] == '1.1.1900' ? null : $data['birthday'];
$account->fill($data)->save();
if(!$this->model->account_id){
if (! $this->model->account_id) {
$this->model->account_id = $account->id;
$this->model->save();
}
@ -63,9 +57,10 @@ class UserRepository extends BaseRepository {
return true;
}
public function createUserRegister($data){
public function createUserRegister($data)
{
$obj = new stdClass();
$obj = new stdClass;
$obj->email = $data['email'];
$obj->password = Hash::make($data['password']);
@ -80,7 +75,7 @@ class UserRepository extends BaseRepository {
$obj->confirmation_code_to = date('Y-m-d H:i:s', strtotime('+1 week'));
$obj->confirmation_code_remider = 0;
$obj->m_sponsor = config('app.main_user_id');
if(isset($data['from_member_id'])){
if (isset($data['from_member_id'])) {
$obj->m_sponsor = (int) str_replace('gs', '', $data['from_member_id']) - config('main.add_number_id');
}
$confirmation_code = UserService::createConfirmationCode();
@ -88,24 +83,26 @@ class UserRepository extends BaseRepository {
UserRegister::create([
'identifier' => $data['email'],
'instance' => $confirmation_code,
'content' => $obj
'content' => $obj,
]);
return $obj;
}
public function clearUserRegister(){
$cleartime = date('Y-m-d H:i:s', strtotime('-1 day')); //gestern -24h
public function clearUserRegister()
{
$cleartime = date('Y-m-d H:i:s', strtotime('-1 day')); // gestern -24h
UserRegister::where('created_at', '<', $cleartime)->delete();
}
public function create($UserRegister){
public function create($UserRegister)
{
$userObj = $UserRegister->content;
$user = User::create([
'email' => $userObj->email,
'password' =>$userObj->password,
'password' => $userObj->password,
]);
$account = UserAccount::create([
@ -122,7 +119,7 @@ class UserRepository extends BaseRepository {
$user->confirmation_code_to = null;
$user->confirmation_code_remider = 0;
$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->account_id = $account->id;
@ -132,7 +129,7 @@ class UserRepository extends BaseRepository {
$user = User::find($user->id);
//clear
// clear
$identifier = $UserRegister->identifier;
UserRegister::where('identifier', $identifier)->delete();
@ -141,13 +138,13 @@ class UserRepository extends BaseRepository {
public function deleteUser(User $user)
{
if($user->account){
if ($user->account) {
$user->account->delete();
}
$user->email = "delete".time();
$user->password = "delete".time();
$user->email = 'delete'.time();
$user->password = 'delete'.time();
$user->confirmed = 0;
$user->confirmation_code = "delete".time();
$user->confirmation_code = 'delete'.time();
$user->confirmation_date = null;
$user->confirmation_code_to = null;
$user->confirmation_code_remider = 2;
@ -162,31 +159,35 @@ class UserRepository extends BaseRepository {
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'){
$rules = array(
if (isset($data['action']) && $data['action'] == 'reverse_charge_validate') {
$rules = [
'tax_identification_number' => 'required',
);
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
$data = [
'user' => $user,
];
return redirect($route)->withErrors($validator)->withInput($data);
}
$ret = $this->reverse_charge_activate($data, $user);
if($ret === 'error'){
if ($ret === 'error') {
$validator = Validator::make($data, []);
$validator->errors()->add('tax_identification_number_validated', __('msg.VATID_could_not_be_validated'));
$data['reverse_charge'] = 0;
$data = [
'user' => $user,
];
return redirect($route.'#user-vat-validation')->withErrors($validator)->withInput($data);
}
if($ret === 'valid'){
if ($ret === 'valid') {
\Session()->flash('alert-success', __('msg.VATID_successfully_entered'));
return redirect($route.'#user-vat-validation')->withInput($data);
return redirect($route.'#user-vat-validation')->withInput($data);
@ -194,20 +195,23 @@ class UserRepository extends BaseRepository {
}
}
public function reverse_charge_delete($data, $user, $route){
if(isset($data['action']) && $data['action'] == 'reverse_charge_delete'){
public function reverse_charge_delete($data, $user, $route)
{
if (isset($data['action']) && $data['action'] == 'reverse_charge_delete') {
$user->account->tax_identification_number = '';
$user->account->reverse_charge = 0;
$user->account->reverse_charge_code = null;
$user->account->reverse_charge_code = null;
$user->account->reverse_charge_valid = null;
$user->account->save();
$data['tax_identification_number'] = '';
\Session()->flash('alert-success', __('msg.reverse_charge_procedure_and_VATID_deleted'));
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',
'BE' => 'BE-Belgien',
@ -237,44 +241,43 @@ class UserRepository extends BaseRepository {
'SI' => 'SI-Slowenien',
'SK' => 'SK-Slowakei',
'XI' => 'XI-Nordirland', */
$countryCode = 'DE';
$countryCode = 'DE';
if($user->account->country_id){
$countryCode = $user->account->country->code;
}
if ($user->account->country_id) {
$countryCode = $user->account->country->code;
}
$vatid = str_replace(array(' ', '.', '-', ',', ', '), '', trim($data['tax_identification_number']));
$cc = substr($vatid, 0, 2);
$vatNo = substr($vatid, 2);
$vatid = str_replace([' ', '.', '-', ',', ', '], '', trim($data['tax_identification_number']));
$cc = substr($vatid, 0, 2);
$vatNo = substr($vatid, 2);
$options = [
'cache_wsdl' => WSDL_CACHE_NONE,
'trace' => 1,
'stream_context' => stream_context_create(
[
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
]
]
)
];
$options = [
'cache_wsdl' => WSDL_CACHE_NONE,
'trace' => 1,
'stream_context' => stream_context_create(
[
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
],
]
),
];
$client = new \SoapClient("https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl", $options);
$result = $client->checkVat(['countryCode' => $countryCode, 'vatNumber' => $vatNo]);
if($result->valid == true) {
$user->account->tax_identification_number = $data['tax_identification_number'];
$user->account->reverse_charge = 1;
$user->account->reverse_charge_code = $countryCode;
$user->account->reverse_charge_valid = now();
$user->account->save();
return 'valid';
} else {
return 'error';
}
}
$client = new \SoapClient('https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl', $options);
$result = $client->checkVat(['countryCode' => $countryCode, 'vatNumber' => $vatNo]);
if ($result->valid == true) {
$user->account->tax_identification_number = $data['tax_identification_number'];
$user->account->reverse_charge = 1;
$user->account->reverse_charge_code = $countryCode;
$user->account->reverse_charge_valid = now();
$user->account->save();
return 'valid';
} else {
return 'error';
}
}
}

View file

@ -1,76 +1,77 @@
<?php
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\UserLevel;
use App\Models\Ingredient;
use App\Models\ShoppingUser;
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 Illuminate\Support\Facades\Auth;
use App\Models\ShoppingUser;
use App\Models\UserLevel;
use App\User;
class HTMLHelper
{
public static $months = [
1 => 'January',
2 => 'February',
3 => 'March',
4 => 'April',
5 => 'May',
6 => 'June',
7 => 'July',
8 => 'August',
9 => 'September',
10 => 'October',
11 => 'November',
12 => 'December'
2 => 'February',
3 => 'March',
4 => 'April',
5 => 'May',
6 => 'June',
7 => 'July',
8 => 'August',
9 => 'September',
10 => 'October',
11 => 'November',
12 => 'December',
];
private static $roles = [
0 => 'Kunde',
1 => 'Redakteur',
7 => 'Admin',
8 => 'SuperAdmin',
9 => 'SySAdmin',
//10 => "API",
// 10 => "API",
];
public static function getMonth($i){
public static function getMonth($i)
{
return self::$months[intval($i)];
}
public static function getTransMonths($full = false){
public static function getTransMonths($full = false)
{
$ret = [];
foreach(self::$months as $key=>$val){
$ret[$key] = trans('cal.months.'.$val);
}
if($full){ //ganzes Jahr
foreach (self::$months as $key => $val) {
$ret[$key] = trans('cal.months.'.$val);
}
if ($full) { // ganzes Jahr
$ret[13] = trans('cal.months.full_year');
}
return $ret;
return $ret;
}
public static function getYearRange($start = 2021)
{
$end = date("Y");
$end = date('Y');
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>';
}
public static function getLabel($id){
public static function getLabel($id)
{
switch ($id) {
case 0:
return 'badge-default';
@ -94,31 +95,32 @@ class HTMLHelper
}
public static function getCustomListOf($name, $select){
$ret = "";
if($name === 'day'){
public static function getCustomListOf($name, $select)
{
$ret = '';
if ($name === 'day') {
$start = 1;
$end = 31;
$values = range($start, $end);
$ret = '<option value="">'.__('Tag').'</option>\n';
foreach ($values as $value){
foreach ($values as $value) {
$attr = ($value == $select) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value.'" '.$attr.'>'.$value.'</option>\n';
}
}
if($name === 'month'){
if ($name === 'month') {
$ret = '<option value="">'.__('Monat').'</option>\n';
foreach (self::$months as $key=>$value){
foreach (self::$months as $key => $value) {
$attr = ($key == $select) ? 'selected="selected"' : '';
$ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n';
}
}
if($name === 'year'){
$start = date("Y", strtotime("-5 years", time()));
$end = date("Y", strtotime("-90 years", time()));
if ($name === 'year') {
$start = date('Y', strtotime('-5 years', time()));
$end = date('Y', strtotime('-90 years', time()));
$values = range($start, $end);
$ret = '<option value="">'.__('Jahr').'</option>\n';
foreach ($values as $value){
foreach ($values as $value) {
$attr = ($value == $select) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value.'" '.$attr.'>'.$value.'</option>\n';
}
@ -127,14 +129,16 @@ class HTMLHelper
return $ret;
}
public static function setContentReadMore($content){
public static function setContentReadMore($content)
{
$sep = '##mehr lesen##';
if(strpos($content, $sep) !== false){
if (strpos($content, $sep) !== false) {
$name = 'collapse_'.random_int(1000, 10000);
$split = explode($sep, $content);
$first = isset($split[0]) ? $split[0] : "";
$text = isset($split[1]) ? $split[1] : "";
$first = isset($split[0]) ? $split[0] : '';
$text = isset($split[1]) ? $split[1] : '';
$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.'">
@ -147,381 +151,435 @@ class HTMLHelper
</div>
</div>';
}
return $content;
}
public static function getRolesOptions(){
$ret = "";
foreach (self::$roles as $role_id => $value){
public static function getRolesOptions()
{
$ret = '';
foreach (self::$roles as $role_id => $value) {
$ret .= '<option value="'.$role_id.'">'.$value.'</option>\n';
}
return $ret;
}
public static function getYearSelectOptions(){
$start = date("Y", strtotime("-5 years", time()));
$end = date("Y", strtotime("+1 years", time()));
public static function getYearSelectOptions()
{
$start = date('Y', strtotime('-5 years', time()));
$end = date('Y', strtotime('+1 years', time()));
$values = range($start, $end);
$now = date("Y", time());
$ret = "";
foreach ($values as $value){
$now = date('Y', time());
$ret = '';
foreach ($values as $value) {
$attr = ($value == $now) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value.'" '.$attr.'>'.$value.'</option>\n';
}
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();
$ret = "";
if($id === false){
$ret = '';
if ($id === false) {
$val = $values->first();
$id = $val->id;
}
foreach ($values as $value){
foreach ($values as $value) {
$attr = ($value->id == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
}
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();
$ret = "";
foreach ($values as $value){
if(is_array($unsets) && in_array($value->id, $unsets)){
$ret = '';
foreach ($values as $value) {
if (is_array($unsets) && in_array($value->id, $unsets)) {
continue;
}
$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';
}
return $ret;
}
public static function getAttributesOptions($ids = array(), $all = true, $type_id = false){
if($type_id){
public static function getAttributesOptions($ids = [], $all = true, $type_id = false)
{
if ($type_id) {
$values = Attribute::where('active', 1)->where('attribute_type_id', $type_id)->get();
}else{
} else {
$values = Attribute::where('active', 1)->get();
}
$ret = "";
if($all){
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n';
}
foreach ($values as $value){
$attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
foreach ($values as $value) {
$attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
}
return $ret;
}
public static function getProductAttributesOptions($product_attributes, $ids = [], $all = true, $type_id = false){
$ret = "";
if($all){
public static function getProductAttributesOptions($product_attributes, $ids = [], $all = true, $type_id = false)
{
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n';
}
foreach ($product_attributes as $product_attribute){
if($product_attribute->attribute){
$attr = (is_array($ids) && in_array($product_attribute->attribute_id, $ids)) ? 'selected="selected"' : '';
foreach ($product_attributes as $product_attribute) {
if ($product_attribute->attribute) {
$attr = (is_array($ids) && in_array($product_attribute->attribute_id, $ids)) ? 'selected="selected"' : '';
$ret .= '<option value="'.$product_attribute->attribute_id.'" '.$attr.'>'.$product_attribute->attribute->name.'</option>\n';
}
}
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();
$ret = "";
if($all){
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n';
}
foreach ($values as $value){
if($sameId == $value->id){
foreach ($values as $value) {
if ($sameId == $value->id) {
continue;
}
$attr = ($value->id == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
}
return $ret;
}
public static function getProductsOptions($ids = array(), $all = true){
if($ids == null){
$ids = array();
public static function getProductsOptions($ids = [], $all = true)
{
if ($ids == null) {
$ids = [];
}
$values = Product::where('active', 1)->get();
$ret = "";
if($all){
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n';
}
foreach ($values as $value){
$attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
}
return $ret;
}
public static function getCategoriesOptions($ids = array(), $all = true){
$values = Category::where('active', 1)->get();
$ret = "";
if($all){
$ret .= '<option value="">'.__('no').'</option>\n';
}
foreach ($values as $value){
foreach ($values as $value) {
$attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
}
return $ret;
}
public static function getCategoriesByShowOn($show_on = []){
$values = Category::where('active', true)->whereJsonContains('show_on', $show_on)->orderBy('pos', 'ASC')->get();
public static function getCategoriesOptions($ids = [], $all = true)
{
$values = Category::where('active', 1)->get();
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n';
}
foreach ($values as $value) {
$attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
}
return $ret;
}
public static function getCategoriesByShowOn($show_on = [])
{
$values = Category::where('active', true)->whereJsonContains('show_on', $show_on)->orderBy('pos', 'ASC')->get();
$ret = [];
foreach ($values as $value){
foreach ($values as $value) {
$ret[$value->id] = ['name' => $value->name, 'count' => $value->getProductsCountOn($show_on)];
}
return $ret;
}
public static function getCategoriesOptionsByShowOn($ids = array(), $all = false, $show_on = []){
$values = Category::where('active', true)->whereJsonContains('show_on', $show_on)->orderBy('pos', 'ASC')->get();
$ret = "";
if($all){
$ret .= '<option value="">'.$all.'</option>\n';
}
foreach ($values as $value){
$attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
$count = $value->getProductsCountOn($show_on);
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.' ('.$count.')</option>\n';
}
return $ret;
}
public static function getProductIngredientsOptions($has_ids = array(), $all = true){
$values = Ingredient::where('active', 1)->get();
$ret = "";
$attr = "";
foreach ($values as $value){
if(!in_array($value->id, $has_ids)){
public static function getCategoriesOptionsByShowOn($ids = [], $all = false, $show_on = [])
{
$values = Category::where('active', true)->whereJsonContains('show_on', $show_on)->orderBy('pos', 'ASC')->get();
$ret = '';
if ($all) {
$ret .= '<option value="">'.$all.'</option>\n';
}
foreach ($values as $value) {
$attr = in_array($value->id, $ids) ? 'selected="selected"' : '';
$count = $value->getProductsCountOn($show_on);
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.' ('.$count.')</option>\n';
}
return $ret;
}
public static function getProductIngredientsOptions($has_ids = [], $all = true)
{
$values = Ingredient::where('active', 1)->orderBy('name')->get();
$ret = '';
$attr = '';
foreach ($values as $value) {
if (! in_array($value->id, $has_ids)) {
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
}
}
return $ret;
}
public static function getLeadTypeOptions($id = false, $all = true){
public static function getLeadTypeOptions($id = false, $all = true)
{
$values = LeadType::where('active', 1)->get();
$ret = "";
if($all){
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n';
}
foreach ($values as $value){
foreach ($values as $value) {
$attr = ($value->id == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
}
return $ret;
}
public static function getUserLevelOptions($id = false, $all = true){
public static function getUserLevelOptions($id = false, $all = true)
{
$values = UserLevel::where('active', 1)->get();
$ret = "";
if($all){
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('no').'</option>\n';
}
foreach ($values as $value){
foreach ($values as $value) {
$attr = ($value->id == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->name.'</option>\n';
}
return $ret;
}
public static function getCompanyOptions($company){
$options = array(1 => __('business'), 0 => __('private'), );
$ret = "";
foreach ($options as $id => $value){
public static function getCompanyOptions($company)
{
$options = [1 => __('business'), 0 => __('private')];
$ret = '';
foreach ($options as $id => $value) {
$attr = ($id == $company) ? 'selected="selected"' : '';
$ret .= '<option value="'.$id.'" '.$attr.'>'.$value.'</option>\n';
}
return $ret;
}
public static function getContriesWithMore($id, $all=true){#
public static function getContriesWithMore($id, $all = true) //
{
$values = Country::all();
$counter = 1;
$ret = "";
if($all){
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n';
}
foreach ($values as $value){
if( $counter == 7){
foreach ($values as $value) {
if ($counter == 7) {
$ret .= '<optgroup label="'.__('further countrie').'">';
}
$attr = ($value->id == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->getLocated().'</option>\n';
$counter ++;
$counter++;
}
$ret .= '</optgroup>';
return $ret;
}
public static function getContriesCodes($id, $all=true){#
public static function getContriesCodes($id, $all = true) //
{
$values = Country::all();
$counter = 1;
$ret = "";
if($all){
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n';
}
foreach ($values as $value){
foreach ($values as $value) {
if(!$value->phone) continue;
if( $counter == 7){
if (! $value->phone) {
continue;
}
if ($counter == 7) {
$ret .= '<optgroup label="'.__('further countrie').'">';
}
$attr = ($value->id == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->phone.'('.$value->getLocated().')</option>\n';
$counter ++;
$counter++;
}
$ret .= '</optgroup>';
return $ret;
}
public static function getCountriesWithoutUsedShippings($all=true){#
public static function getCountriesWithoutUsedShippings($all = true) //
{
$values = Country::all();
$country_ids = ShippingCountry::all()->pluck('country_id')->toArray();
$ret = "";
if($all){
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n';
}
foreach ($values as $value){
if(!in_array($value->id, $country_ids)){
foreach ($values as $value) {
if (! in_array($value->id, $country_ids)) {
$ret .= '<option value="'.$value->id.'">'.$value->getLocated().'</option>\n';
}
}
return $ret;
}
public static function getCountryNameFormShipping($id){
public static function getCountryNameFormShipping($id)
{
$value = ShippingCountry::find($id);
if($value){
if ($value) {
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();
if($shipping_for){
if ($shipping_for) {
$temp = [];
foreach($values as $value){
if($shipping = $value->shipping){
if($shipping->getShippingPricesFirstBy($shipping_for)){
foreach ($values as $value) {
if ($shipping = $value->shipping) {
if ($shipping->getShippingPricesFirstBy($shipping_for)) {
$temp[] = $value;
}
}
}
$values = $temp;
}
$ret = "";
if($all){
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n';
}
foreach ($values as $value){
foreach ($values as $value) {
$attr = ($value->id == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$value->country->getLocated().'</option>\n';
}
return $ret;
}
public static function getSalutation($id){
$values = array('mr' => __('MR'), 'ms' => __('MS'));
$ret = "";
public static function getSalutation($id)
{
$values = ['mr' => __('MR'), 'ms' => __('MS')];
$ret = '';
$ret .= '<option value="">'.__('please select').'</option>\n';
foreach ($values as $key => $value){
foreach ($values as $key => $value) {
$attr = ($key == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n';
}
return $ret;
}
public static function getSalutationLang($id){
$values = array('mr' => __('MR'), 'ms' => __('MS'));
return (!empty($values[$id]) ? $values[$id] : '');
public static function getSalutationLang($id)
{
$values = ['mr' => __('MR'), 'ms' => __('MS')];
return ! empty($values[$id]) ? $values[$id] : '';
}
public static function getTaxSaleOptions($id){
$values = array('1' => __('taxable_sales_1'), '2' => __('taxable_sales_2'));
$ret = "";
public static function getTaxSaleOptions($id)
{
$values = ['1' => __('taxable_sales_1'), '2' => __('taxable_sales_2')];
$ret = '';
$ret .= '<option value="">'.__('please select').'</option>\n';
foreach ($values as $key => $value){
foreach ($values as $key => $value) {
$attr = ($key == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n';
}
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();
$ret = "";
if($all){
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n';
}
foreach ($values as $value){
foreach ($values as $value) {
$attr = ($value->id == $id) ? 'selected="selected"' : '';
$to="";
if($value->account){
$to = $value->account->first_name." ".$value->account->last_name." | ";
$to = '';
if ($value->account) {
$to = $value->account->first_name.' '.$value->account->last_name.' | ';
}
$ret .= '<option value="'.$value->id.'" '.$attr.'>'.$to.$value->email.' #'.$value->account->m_account.'</option>\n';
}
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'])
->where('shopping_users.member_id', '=', \Auth::user()->id)->get();
$ret = "";
if($all){
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n';
}
foreach ($values as $value){
foreach ($values as $value) {
$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';
}
return $ret;
}
public static function getAnyOptions($id, $options = [], $all=true){#
$ret = "";
if($all){
public static function getAnyOptions($id, $options = [], $all = true) //
{
$ret = '';
if ($all) {
$ret .= '<option value="">'.__('please select').'</option>\n';
}
foreach ($options as $key=>$value){
foreach ($options as $key => $value) {
$attr = ($key == $id) ? 'selected="selected"' : '';
$ret .= '<option value="'.$key.'" '.$attr.'>'.$value.'</option>\n';
}
return $ret;
}
public static function getOptionRange($select, $from=1, $to=50){
public static function getOptionRange($select, $from = 1, $to = 50)
{
$values = range($from, $to);
$ret = "";
foreach ($values as $value){
$ret = '';
foreach ($values as $value) {
$attr = ($value == $select) ? 'selected="selected"' : '';
$ret .= '<option value="'.$value.'" '.$attr.'>'.$value.'</option>\n';
}
return $ret;
}

View file

@ -1,127 +1,199 @@
<?php
namespace App\Services;
use App\Mail\MailInvoice;
use App\Mail\MailLogistic;
use App\Services\Util;
use App\Models\Setting;
use App\Models\ShoppingOrder;
use App\Models\UserCredit;
use Illuminate\Support\Facades\Mail;
class Invoice
{
public static function getInvoiceNumber(){
public static function getInvoiceNumber()
{
return (int) Setting::getContentBySlug('invoice-number');
}
public static function makeNextInvoiceNumber(){
public static function makeNextInvoiceNumber()
{
$invoice_number = self::getInvoiceNumber();
$invoice_number = $invoice_number+1;
$invoice_number = $invoice_number + 1;
Setting::setContentBySlug('invoice-number', $invoice_number, 'int');
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');
return $prefix.$invoice_number;
}
public static function getInvoiceStorageDir($invoice_date){
return "/invoice/".\Carbon::parse($invoice_date)->format('Y/m/');
public static function getInvoiceStorageDir($invoice_date)
{
return '/invoice/'.\Carbon::parse($invoice_date)->format('Y/m/');
}
public static function getDeliveryStorageDir($invoice_date){
return "/delivery/".\Carbon::parse($invoice_date)->format('Y/m/');
public static function getDeliveryStorageDir($invoice_date)
{
return '/delivery/'.\Carbon::parse($invoice_date)->format('Y/m/');
}
public static function makeInvoiceFilename($invoice_number){
return "Rechnung-".$invoice_number.".pdf";
public static function makeInvoiceFilename($invoice_number)
{
return 'Rechnung-'.$invoice_number.'.pdf';
}
public static function makeDeliveryFilename($invoice_number){
return "Lieferschein-".$invoice_number.".pdf";
public static function makeDeliveryFilename($invoice_number)
{
return 'Lieferschein-'.$invoice_number.'.pdf';
}
//invoice
public static function isInvoice(ShoppingOrder $shopping_order){
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
public static function isInvoice(ShoppingOrder $shopping_order)
{
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;
}
public static function getDir($shopping_order){
public static function getDir($shopping_order)
{
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;
}
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;
}
//delivery
public static function isDelivery(ShoppingOrder $shopping_order){
// 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
public static function isDelivery(ShoppingOrder $shopping_order)
{
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));
}
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));
}
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);
}
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);
}
public static function getDownloadPath(ShoppingOrder $shopping_order, $full = false){
public static function getDownloadPath(ShoppingOrder $shopping_order, $full = false)
{
$dir = self::getDir($shopping_order);
$filename = self::getFilename($shopping_order);
if(!$full){
if (! $full) {
return $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);
$filename = self::getDeliveryFilename($shopping_order);
if(!$full){
if (! $full) {
return $dir.$filename;
}
return \Storage::disk('public')->path($dir.$filename);
}
public static function sendInvoiceMail($shopping_order){
public static function sendInvoiceMail($shopping_order)
{
$bcc = [];
$billing_email = $shopping_order->shopping_user->billing_email;
if(!$billing_email){
if($shopping_order->mode === 'test'){
if (! $billing_email) {
if ($shopping_order->mode === 'test') {
$billing_email = config('app.checkout_test_mail');
}else{
} else {
$billing_email = config('app.checkout_mail');
}
}
if($shopping_order->mode === 'test'){
if ($shopping_order->mode === 'test') {
$bcc[] = config('app.checkout_test_mail');
}else{
} else {
$bcc[] = config('app.checkout_mail');
}
Mail::to($billing_email)->bcc($bcc)->send(new MailInvoice($shopping_order));
}
public static function sendLogisticMail(ShoppingOrder $shopping_order){
$to = [config('app.logistic_mail')]; //['versand@aloe-vera.bio'];
public static function sendLogisticMail(ShoppingOrder $shopping_order)
{
$to = [config('app.logistic_mail')]; // ['versand@aloe-vera.bio'];
Mail::to($to)->send(new MailLogistic($shopping_order));
}
}

View file

@ -2,11 +2,10 @@
namespace App\Services;
use App\Mail\PaymentReminderEmail;
use App\Models\Logger;
use App\Models\PaymentReminder;
use App\Models\ShoppingPayment;
use App\Mail\PaymentReminderEmail;
use App\Services\Util;
use Carbon\Carbon;
use Illuminate\Support\Facades\Mail;
@ -31,19 +30,23 @@ class PaymentReminderService
'action' => $action,
'channel' => 'payment_reminder',
'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()
{
$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;
}
@ -64,7 +67,7 @@ class PaymentReminderService
->where('shopping_payments.created_at', '<', $date)
->where('shopping_payments.amount', '>', 0)
->whereNull('shopping_orders.deleted_at')
->whereIn('shopping_payments.id', function($query) use ($clearingtype, $date) {
->whereIn('shopping_payments.id', function ($query) use ($clearingtype, $date) {
$query->selectRaw('MAX(shopping_payments.id)')
->from('shopping_payments')
->join('shopping_orders', 'shopping_payments.shopping_order_id', '=', 'shopping_orders.id')
@ -79,7 +82,6 @@ class PaymentReminderService
->select('shopping_payments.*')
->get();
return $payments;
}
@ -91,7 +93,7 @@ class PaymentReminderService
$intervals = $this->getActiveIntervals();
$results = [];
foreach($intervals as $clearingtype => $interval){
foreach ($intervals as $clearingtype => $interval) {
$date = Carbon::now()->subDays($interval);
$payments = $this->getOpenPaymentsForClearingType($clearingtype, $interval);
@ -99,7 +101,7 @@ class PaymentReminderService
'interval' => $interval,
'date_limit' => $date,
'payments' => $payments,
'count' => $payments->count()
'count' => $payments->count(),
];
}
@ -114,13 +116,13 @@ class PaymentReminderService
$intervals = $this->getActiveIntervals();
$detailedData = [];
foreach($intervals as $clearingtype => $interval){
foreach ($intervals as $clearingtype => $interval) {
$date = Carbon::now()->subDays($interval);
$payments = $this->getOpenPaymentsForClearingType($clearingtype, $interval);
foreach($payments as $payment){
$name = !isset($payment->shopping_order->shopping_user) ? 'Kein Name' : $payment->shopping_order->shopping_user->billing_firstname.' '.$payment->shopping_order->shopping_user->billing_lastname;
$email = !isset($payment->shopping_order->shopping_user) ? 'Keine Email' : $payment->shopping_order->shopping_user->billing_email;
foreach ($payments as $payment) {
$name = ! isset($payment->shopping_order->shopping_user) ? 'Kein Name' : $payment->shopping_order->shopping_user->billing_firstname.' '.$payment->shopping_order->shopping_user->billing_lastname;
$email = ! isset($payment->shopping_order->shopping_user) ? 'Keine Email' : $payment->shopping_order->shopping_user->billing_email;
$shipped = '<span class="badge badge-pill badge-'.$payment->shopping_order->getShippedColor().'">'.$payment->shopping_order->getShippedType().'</span>';
// Countdown für nächste Erinnerung berechnen
@ -158,31 +160,30 @@ class PaymentReminderService
public function sendReminder($payment)
{
//holen der nächsten zahlungserinnerung
// holen der nächsten zahlungserinnerung
$payment_reminder = $this->getReminder((int) $payment->reminder, $payment->clearingtype);
if(!$payment_reminder){
if (! $payment_reminder) {
return false;
}
//zahlungserinnerung Platzhalter ersetzen.
// zahlungserinnerung Platzhalter ersetzen.
$payment_reminder = $this->replacePlaceholder($payment, $payment_reminder);
//zahlungserinnerung senden
// zahlungserinnerung senden
$emailSent = $this->sendReminderEmail($payment, $payment_reminder);
if ($emailSent) {
$this->createLog(
'email_sent',
"Zahlungserinnerung E-Mail gesendet an: {$payment->shopping_order->shopping_user->billing_email}, Subject: {$payment_reminder->subject}",
'ShoppingOrder',
'ShoppingOrder',
$payment->shopping_order_id,
3
);
}
//action ausführen
if($payment_reminder->action === 'set_order_status_cancelled'){
// action ausführen
if ($payment_reminder->action === 'set_order_status_cancelled') {
$this->setNoNPayment($payment);
$payment->shopping_order->shipped = 10;
$payment->shopping_order->save();
@ -195,9 +196,9 @@ class PaymentReminderService
);
}
//reminder setzen +1
$payment->reminder = (int) $payment->reminder + 1;
$payment->reminder_date = Carbon::now();
// reminder setzen +1
$payment->reminder = (int) $payment->reminder + 1; // anzahl der Reminder
$payment->reminder_date = Carbon::now(); // wann wurde der letzte reminder gesendet?
$payment->save();
$this->createLog(
@ -225,7 +226,7 @@ class PaymentReminderService
public function getClearingtype($clearingtype)
{
return isset($this->clearingtypes[$clearingtype]) ? $this->clearingtypes[$clearingtype] : $clearingtype;
return isset($this->clearingtypes[$clearingtype]) ? $this->clearingtypes[$clearingtype] : $clearingtype;
}
public function getReminder($reminder, $clearingtype)
@ -235,13 +236,14 @@ class PaymentReminderService
->orderBy('interval', 'asc')
->get();
if($payment_reminders->isEmpty()) {
if ($payment_reminders->isEmpty()) {
return false;
}
// Wenn reminder größer ist als Anzahl der Erinnerungen
if($reminder >= $payment_reminders->count()) {
if ($reminder >= $payment_reminders->count()) {
return false;
}
// Hole die Erinnerung an Position $reminder (0,1,2,3...)
return $payment_reminders[$reminder];
}
@ -256,7 +258,7 @@ class PaymentReminderService
'{billing_last_name}' => $shopping_user->billing_lastname,
'{order_number}' => '<b>'.$shopping_order->getLastShoppingPayment('reference').'</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(
@ -281,13 +283,13 @@ class PaymentReminderService
$subject = $payment_reminder->subject;
$message = $payment_reminder->message;
if(Util::isTestSystem()){
if (Util::isTestSystem()) {
$email = config('app.checkout_test_mail');
}
if($payment->shopping_order->mode === 'test' || Util::isTestSystem()){
if ($payment->shopping_order->mode === 'test' || Util::isTestSystem()) {
$bcc[] = config('app.checkout_test_mail');
}else{
} else {
$bcc[] = config('app.checkout_mail');
}
@ -295,14 +297,15 @@ class PaymentReminderService
return true;
} catch (\Exception $e) {
\Log::error('Fehler beim E-Mail-Versand: ' . $e->getMessage());
\Log::error('Fehler beim E-Mail-Versand: '.$e->getMessage());
$this->createLog(
'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',
$payment->shopping_order_id,
5
);
return false;
}
}
@ -332,7 +335,7 @@ class PaymentReminderService
return [
'type' => 'completed',
'message' => 'Alle Erinnerungen gesendet',
'days_left' => 0
'days_left' => 0,
];
}
@ -355,16 +358,16 @@ class PaymentReminderService
'type' => 'overdue',
'message' => 'Nächste Erinnerung fällig',
'days_left' => 0,
'next_reminder_date' => $next_reminder_date
'next_reminder_date' => $next_reminder_date,
];
}
return [
'type' => 'countdown',
'message' => 'Nächste Erinnerung in ' . $days_left . ' Tagen',
'message' => 'Nächste Erinnerung in '.$days_left.' Tagen',
'days_left' => $days_left,
'next_reminder_date' => $next_reminder_date,
'next_reminder_interval' => $interval_difference
'next_reminder_interval' => $interval_difference,
];
}
@ -374,7 +377,7 @@ class PaymentReminderService
public function getPaymentReminderLogs($limit = 100, $paymentId = null, $action = null)
{
$query = Logger::where('channel', 'payment_reminder')
->orderBy('created_at', 'desc');
->orderBy('created_at', 'desc');
if ($paymentId) {
$query->where('model_id', $paymentId);
@ -393,9 +396,9 @@ class PaymentReminderService
public function getLogsForPayment($orderId)
{
return Logger::where('channel', 'payment_reminder')
->where('model_id', $orderId)
->orderBy('created_at', 'desc')
->get();
->where('model_id', $orderId)
->orderBy('created_at', 'desc')
->get();
}
/**
@ -404,9 +407,9 @@ class PaymentReminderService
public function getLogsForDateRange($startDate, $endDate)
{
return Logger::where('channel', 'payment_reminder')
->whereBetween('created_at', [$startDate, $endDate])
->orderBy('created_at', 'desc')
->get();
->whereBetween('created_at', [$startDate, $endDate])
->orderBy('created_at', 'desc')
->get();
}
/**
@ -417,37 +420,36 @@ class PaymentReminderService
$startDate = Carbon::now()->subDays($days);
$stats = Logger::where('channel', 'payment_reminder')
->where('created_at', '>=', $startDate)
->selectRaw('action, level, COUNT(*) as count')
->groupBy('action', 'level')
->get();
->where('created_at', '>=', $startDate)
->selectRaw('action, level, COUNT(*) as count')
->groupBy('action', 'level')
->get();
$summary = [
'total_logs' => Logger::where('channel', 'payment_reminder')
->where('created_at', '>=', $startDate)
->count(),
->where('created_at', '>=', $startDate)
->count(),
'emails_sent' => Logger::where('channel', 'payment_reminder')
->where('action', 'email_sent')
->where('created_at', '>=', $startDate)
->count(),
->where('action', 'email_sent')
->where('created_at', '>=', $startDate)
->count(),
'emails_failed' => Logger::where('channel', 'payment_reminder')
->where('action', 'email_exception')
->where('created_at', '>=', $startDate)
->count(),
->where('action', 'email_exception')
->where('created_at', '>=', $startDate)
->count(),
'reminders_completed' => Logger::where('channel', 'payment_reminder')
->where('action', 'reminder_completed')
->where('created_at', '>=', $startDate)
->count(),
->where('action', 'reminder_completed')
->where('created_at', '>=', $startDate)
->count(),
'actions_executed' => Logger::where('channel', 'payment_reminder')
->where('action', 'action_completed')
->where('created_at', '>=', $startDate)
->count(),
->where('action', 'action_completed')
->where('created_at', '>=', $startDate)
->count(),
];
return [
'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;
use App\Mail\MailResetPassword;
use App\Models\File;
use App\Models\LeadType;
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 Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Collection;
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 Laravel\Passport\Client;
use Laravel\Passport\HasApiTokens;
use Laravel\Passport\Token;
use Util;
/**
* App\User
*
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
* @property-read DatabaseNotificationCollection|DatabaseNotification[] $notifications
* @property-read int|null $notifications_count
*
* @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User query()
*
* @property int $id
* @property string $email
* @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 $updated_at
* @property string|null $deleted_at
*
* @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 whereAccountPayment($value)
@ -84,37 +101,44 @@ use Util;
* @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 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 \Illuminate\Database\Eloquent\Collection|\App\Models\UserHistory[] $user_histories
* @property-read Collection|UserHistory[] $user_histories
* @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 \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 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)
*
* @property string|null $payment_credit
*
* @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 \Illuminate\Database\Eloquent\Collection|\Laravel\Passport\Client[] $clients
* @property-read Collection|Client[] $clients
* @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 \App\Models\UserShop|null $shop
* @property-read UserShop|null $shop
* @property int|null $lead_type_id
* @property-read \App\Models\LeadType|null $lead_type
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\UserWhitelabelProduct> $whitelabel_products
* @property-read LeadType|null $lead_type
* @property-read Collection<int, UserWhitelabelProduct> $whitelabel_products
* @property-read int|null $whitelabel_products_count
*
* @method static \Illuminate\Database\Eloquent\Builder|User whereLeadTypeId($value)
*
* @mixin \Eloquent
*/
class User extends Authenticatable
{
use Notifiable, HasApiTokens;
use HasApiTokens, Notifiable;
/**
* The attributes that are mass assignable.
@ -142,13 +166,15 @@ class User extends Authenticatable
protected $casts = [
'email_verified_at' => 'datetime',
'settings' => 'array',
'payment_methods' => 'array'
'payment_methods' => 'array',
];
private $userImage = false;
private $userImageLink = false;
public function account(){
public function account()
{
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');
}
public function user_level(){
public function user_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');
}
public function user_sponsor(){
public function user_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');
}
public function sponsorHasCommisson(){
if($this->user_sponsor && $this->user_sponsor->user_level && $this->user_sponsor->user_level->partner_provision){
public function sponsorHasCommisson()
{
if ($this->user_sponsor && $this->user_sponsor->user_level && $this->user_sponsor->user_level->partner_provision) {
return true;
}
return false;
}
public function files(){
public function files()
{
return $this->hasMany('App\Models\File', 'user_id', '');
}
public function user_histories(){
public function user_histories()
{
return $this->hasMany('App\Models\UserHistory', 'user_id', '');
}
@ -202,61 +237,66 @@ class User extends Authenticatable
return $this->hasMany('App\Models\UserWhitelabelProduct', 'user_id', 'id');
}
public function getMUserSponsor(){
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;
public function getMUserSponsor()
{
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;
}
}
public function getFullName($email=true){
$ret = "";
if($this->account){
$ret = $this->account->first_name." ".$this->account->last_name;
public function getFullName($email = true)
{
$ret = '';
if ($this->account) {
$ret = $this->account->first_name.' '.$this->account->last_name;
}
if($email && $this->id > 1){
$ret .= " | ".$this->email;
if ($email && $this->id > 1) {
$ret .= ' | '.$this->email;
}
return $ret;
}
public function getFullAddress($email=true){
$ret = "";
if($this->account){
$ret .= $this->account->first_name." ".$this->account->last_name."\n";
public function getFullAddress($email = true)
{
$ret = '';
if ($this->account) {
$ret .= $this->account->first_name.' '.$this->account->last_name."\n";
$ret .= $this->account->address."\n";
$ret .= $this->account->address_2 ? $this->account->address_2."\n" : "";
$ret .= $this->account->zipcode." ".$this->account->city."\n";
$ret .= $email ? $this->email."\n" : "";
$ret .= $this->account->address_2 ? $this->account->address_2."\n" : '';
$ret .= $this->account->zipcode.' '.$this->account->city."\n";
$ret .= $email ? $this->email."\n" : '';
$pre = $this->account->pre_phone_id != "" ? $this->account->pre_phone->phone." " : "";
$ret .= $this->account->phone ? $pre.$this->account->phone."\n" : "";
$pre = $this->account->pre_phone_id != '' ? $this->account->pre_phone->phone.' ' : '';
$ret .= $this->account->phone ? $pre.$this->account->phone."\n" : '';
$pre = $this->account->pre_mobil_id != "" ? $this->account->pre_mobil->phone." " : "";
$ret .= $this->account->mobil ? $pre.$this->account->mobil."\n" : "";
$pre = $this->account->pre_mobil_id != '' ? $this->account->pre_mobil->phone.' ' : '';
$ret .= $this->account->mobil ? $pre.$this->account->mobil."\n" : '';
}
return $ret;
}
/**
/**
* @return bool
*/
public function isCopyReader()
{
if($this->admin >= 1){
if ($this->admin >= 1) {
return true;
}
return false;
}
/**
* @return bool
*/
public function isPasswort(){
if($this->password == env('APP_KEY')){
public function isPasswort(): bool
{
if (Hash::check(config('app.key'), $this->password)) {
return false;
}
return true;
}
@ -265,9 +305,10 @@ class User extends Authenticatable
*/
public function isAdmin()
{
if($this->admin >= 7){
if ($this->admin >= 7) {
return true;
}
return false;
}
@ -276,9 +317,10 @@ class User extends Authenticatable
*/
public function isSuperAdmin()
{
if($this->admin >= 8){
if ($this->admin >= 8) {
return true;
}
return false;
}
@ -287,9 +329,10 @@ class User extends Authenticatable
*/
public function isSySAdmin()
{
if($this->admin >= 9){
if ($this->admin >= 9) {
return true;
}
return false;
}
@ -298,9 +341,10 @@ class User extends Authenticatable
*/
public function isApiUser()
{
if($this->admin >= 10){
if ($this->admin >= 10) {
return true;
}
return false;
}
@ -312,195 +356,246 @@ class User extends Authenticatable
return $this->test_mode ? true : false;
}
/**
* @return bool
*/
public function showSideNav()
{
if($this->active == 1 && $this->blocked == 0 && $this->wizard >= 10){
if ($this->active == 1 && $this->blocked == 0 && $this->wizard >= 10) {
return true;
}
return false;
}
public function isActive(){
public function isActive()
{
return ($this->active == 1 && $this->blocked == 0) ? true : false;
}
public function isActiveAccount(){
if($this->isActive() && $this->payment_account){
public function isActiveAccount()
{
if ($this->isActive() && $this->payment_account) {
return Carbon::parse($this->payment_account)->gt(Carbon::now());
}
return false;
}
public function isRenewalAccount(){
public function isRenewalAccount()
{
if ($this->payment_account) {
return Carbon::parse($this->payment_account)->modify('-'.(config('main.renewal_days')).' days')->lt(Carbon::now());
}
return false;
}
public function nextRenewalAccount(){
return $this->payment_account ? Carbon::parse($this->payment_account)->modify('-'.config('main.renewal_days').' days')->format(\Util::formatDateTimeDB()) : false ;
public function nextRenewalAccount()
{
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);
}
public function modifyActiveAccount($add = "1 year"){
return Carbon::parse($this->payment_account)->modify($add)->format(\Util::formatDateTimeDB());
public function modifyActiveAccount($add = '1 year')
{
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));
}
public function daysActiveShop(){
public function daysActiveShop()
{
return Carbon::now()->diffInDays(Carbon::parse($this->payment_shop), false);
}
public function modifyActiveShop($add = "1 year"){
return Carbon::parse($this->payment_shop)->modify($add)->format(\Util::formatDateTimeDB());
public function modifyActiveShop($add = '1 year')
{
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));
}
public function isAboOption(){
public function isAboOption()
{
return false;
}
/**
* @return string
*/
public function getConfirmationDateFormat(){
if(!$this->attributes['confirmation_date']){ return ""; }
return Carbon::parse($this->attributes['confirmation_date'])->format(\Util::formatDateTimeDB());
public function getConfirmationDateFormat()
{
if (! $this->attributes['confirmation_date']) {
return '';
}
return Carbon::parse($this->attributes['confirmation_date'])->format(Util::formatDateTimeDB());
}
/**
* @return string
*/
public function getActiveDateFormat(){
if(!$this->attributes['active_date']){ return ""; }
return Carbon::parse($this->attributes['active_date'])->format(\Util::formatDateTimeDB());
public function getActiveDateFormat()
{
if (! $this->attributes['active_date']) {
return '';
}
return Carbon::parse($this->attributes['active_date'])->format(Util::formatDateTimeDB());
}
/**
* @return string
*/
public function getAgreementFormat(){
if(!$this->attributes['agreement']){ return ""; }
return Carbon::parse($this->attributes['agreement'])->format(\Util::formatDateTimeDB());
public function getAgreementFormat()
{
if (! $this->attributes['agreement']) {
return '';
}
return Carbon::parse($this->attributes['agreement'])->format(Util::formatDateTimeDB());
}
public function getPaymentAccountDateFormat($time = true){
if(!$this->attributes['payment_account']){ return ""; }
if(!$time){
return Carbon::parse($this->attributes['payment_account'])->format(\Util::formatDateDB());
public function getPaymentAccountDateFormat($time = true)
{
if (! $this->attributes['payment_account']) {
return '';
}
return Carbon::parse($this->attributes['payment_account'])->format(\Util::formatDateTimeDB());
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){
if(!$this->attributes['payment_shop']){ return ""; }
if(!$time){
return Carbon::parse($this->attributes['payment_shop'])->format(\Util::formatDateDB());
public function getPaymentShopDateFormat($time = true)
{
if (! $this->attributes['payment_shop']) {
return '';
}
return Carbon::parse($this->attributes['payment_shop'])->format(\Util::formatDateTimeDB());
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());
public function getReleaseAccountFormat($time = true)
{
if (! $this->attributes['release_account']) {
return '';
}
return Carbon::parse($this->attributes['release_account'])->format(\Util::formatDateTimeDB());
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()
{
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){
if(!$this->settings){
public function setSetting(array $revisions, bool $save = true)
{
if (! $this->settings) {
$this->settings = [];
}
$this->settings = array_merge($this->settings, $revisions);
if ($save) {
$this->save();
}
return $this;
}
public function getSetting($key, $default = null){
public function getSetting($key, $default = null)
{
return isset($this->settings[$key]) ? $this->settings[$key] : $default;
}
public function getPaymentMethodsShort(){
$ret = "";
if($this->payment_methods !== null){
foreach ($this->payment_methods as $payment_method){
if($find = PaymentMethod::find($payment_method)){
$ret .= $find->short." | ";
public function getPaymentMethodsShort()
{
$ret = '';
if ($this->payment_methods !== null) {
foreach ($this->payment_methods as $payment_method) {
if ($find = PaymentMethod::find($payment_method)) {
$ret .= $find->short.' | ';
}
}
$ret = rtrim($ret, " | ");
$ret = rtrim($ret, ' | ');
}
return $ret;
}
/**
* @return string
*/
public function getLandByCountry(){
if($this->account && $this->account->country_id){
public function getLandByCountry()
{
if ($this->account && $this->account->country_id) {
$code = $this->account->country->code;
if($code == "FR"){
if ($code == 'FR') {
return 'fr';
}
if($code == "CH"){
if ($code == 'CH') {
return 'de';
}
if($code == "NL"){
if ($code == 'NL') {
return 'nl';
}
if($code == "DE"){
if ($code == 'DE') {
return 'de';
}
}
return "de";
return 'de';
}
public function getBirthdayFormat($format = "d.m.Y"){
if($this->account && $this->account->getBirthdayRaw()){
public function getBirthdayFormat($format = 'd.m.Y')
{
if ($this->account && $this->account->getBirthdayRaw()) {
return (int) Carbon::parse($this->account->getBirthdayRaw())->format($format);
}
return null;
}
public function hasProfileImage(){
public function hasProfileImage()
{
if($this->userImage){
if ($this->userImage) {
return $this->userImage;
}
if(\Storage::disk('user')->has($this->id.'/avatar.jpg')){
if (\Storage::disk('user')->has($this->id.'/avatar.jpg')) {
$this->userImage = $this->id.'/avatar.jpg';
}
if(\Storage::disk('user')->has($this->id.'/avatar.png')){
if (\Storage::disk('user')->has($this->id.'/avatar.png')) {
$this->userImage = $this->id.'/avatar.png';
}
return $this->userImage;
}
public function getProfileImage(){
if($this->hasProfileImage()){
public function getProfileImage()
{
if ($this->hasProfileImage()) {
return str_replace('/', '_', $this->userImage);
}
return null;
}

View file

@ -32,16 +32,17 @@
"srmklive/paypal": "~3.0"
},
"require-dev": {
"spatie/laravel-ignition": "^2.0",
"nunomaduro/collision": "^8.1",
"barryvdh/laravel-debugbar": "^3.13",
"barryvdh/laravel-ide-helper": "^3.0",
"fakerphp/faker": "^1.23",
"laravel/boost": "^1.0",
"laravel/pint": "^1.0",
"laravel/sail": "^1.26",
"mockery/mockery": "^1.6.2",
"nunomaduro/collision": "^8.1",
"pestphp/pest": "^2.0",
"pestphp/pest-plugin-laravel": "^2.0",
"barryvdh/laravel-debugbar": "^3.13",
"barryvdh/laravel-ide-helper": "^3.0"
"spatie/laravel-ignition": "^2.0"
},
"config": {
"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
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCountriesTable extends Migration
{
@ -35,14 +35,11 @@ class CreateCountriesTable extends Migration
$table->boolean('currency_calc')->default(false);
$table->decimal('currency_faktor', 4, 2)->nullable();
$table->boolean('active')->default(true);
$table->text('trans_name')->nullable();
$table->text('attr')->nullable();
$table->timestamps();
});
}

View file

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

View file

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

View file

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

View file

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

View file

@ -25,14 +25,24 @@ class CreateAttributeTypesTable extends Migration
$table->string('slug')->unique()->index();
$table->timestamps();
$table->foreign('parent_id')
->references('id')
->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()
{
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');
}
}

View file

@ -36,6 +36,13 @@ class CreateUserWhitelabelProductsTable extends Migration
->on('products')
->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()
{
Schema::table('product_images', function (Blueprint $table) {
$table->dropForeign(['user_wl_product_id']);
});
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