315 lines
12 KiB
PHP
315 lines
12 KiB
PHP
#!/usr/bin/php
|
|
<?php
|
|
require("../../config/config.php");
|
|
|
|
define('FRONKDB_SQLDEBUG', false);
|
|
error_reporting(E_ALL & ~(E_NOTICE | E_STRICT | E_DEPRECATED));
|
|
|
|
require_once(LIBDIR."/mvcfronk/mfRouter/mfRouter.php");
|
|
require_once(LIBDIR."/mvcfronk/mfBase/mfBaseModel.php");
|
|
require_once(LIBDIR."/mvcfronk/mfBase/mfBaseController.php");
|
|
|
|
$layout = \Layout::singleton();
|
|
|
|
$me = new User(1);
|
|
define("INTERNAL_USER_ID", $me->id);
|
|
define("INTERNAL_USER_USERNAME", $me->username);
|
|
|
|
echo "========================================\n";
|
|
echo "Creating 20 mock manual invoices...\n";
|
|
echo "========================================\n\n";
|
|
|
|
$db = FronkDB::singleton();
|
|
|
|
// Get random customers with valid data for invoicing
|
|
$customerSql = "SELECT * FROM Address
|
|
WHERE customer_number IS NOT NULL
|
|
AND customer_number > 0
|
|
AND (company IS NOT NULL OR firstname IS NOT NULL)
|
|
AND street IS NOT NULL
|
|
AND zip IS NOT NULL
|
|
AND city IS NOT NULL
|
|
ORDER BY RAND()
|
|
LIMIT 50";
|
|
$customerRes = $db->query($customerSql);
|
|
$customers = [];
|
|
while ($row = $db->fetch_object($customerRes)) {
|
|
$customers[] = $row;
|
|
}
|
|
|
|
if (empty($customers)) {
|
|
echo "ERROR: No valid customers found in database!\n";
|
|
exit(1);
|
|
}
|
|
|
|
echo "Found " . count($customers) . " random customers to use.\n\n";
|
|
|
|
// Get last 20 shipping notes for position data
|
|
$sql = "SELECT * FROM WarehouseShippingNote ORDER BY `create` DESC LIMIT 20";
|
|
$res = $db->query($sql);
|
|
|
|
$shippingNotes = [];
|
|
while ($row = $db->fetch_object($res)) {
|
|
$shippingNotes[] = new WarehouseShippingNote($row);
|
|
}
|
|
|
|
$count = 0;
|
|
$errors = 0;
|
|
|
|
foreach ($shippingNotes as $index => $shippingNote) {
|
|
echo "Processing shipping note #{$shippingNote->id}...\n";
|
|
|
|
// Pick a random customer
|
|
$customer = $customers[array_rand($customers)];
|
|
$address = new Address($customer->id);
|
|
|
|
if (!$address || !$address->id) {
|
|
echo " - Skipping: Could not load customer address\n";
|
|
$errors++;
|
|
continue;
|
|
}
|
|
|
|
// Build positions from shipping note
|
|
$positions = json_decode($shippingNote->positions, true) ?: [];
|
|
$enrichedPositions = [];
|
|
|
|
foreach ($positions as $position) {
|
|
if (isset($position['article'])) {
|
|
$article = WarehouseArticleModel::get($position['article']);
|
|
if (!$article) continue;
|
|
|
|
$prices = json_decode($article->cheapestSellPrice, true) ?: [];
|
|
$price = 0;
|
|
foreach ($prices as $p) {
|
|
if (isset($p['price'])) {
|
|
$price = $p['price'];
|
|
break;
|
|
}
|
|
}
|
|
// Use random price if no price found
|
|
if ($price == 0) {
|
|
$price = rand(10, 500);
|
|
}
|
|
|
|
$enrichedPositions[] = [
|
|
'product_name' => $article->articleNumber . " | " . $article->title,
|
|
'product_info' => $article->description ?: '',
|
|
'amount' => $position['amount'] ?: 1,
|
|
'unit' => $article->unit ?: 'Stk.',
|
|
'price' => $price,
|
|
'discount' => 0,
|
|
'vatrate' => 20,
|
|
'article_id' => $article->id
|
|
];
|
|
} elseif (isset($position['articlePacket'])) {
|
|
$packet = WarehouseArticlePacketModel::get($position['articlePacket']);
|
|
if (!$packet) continue;
|
|
|
|
$enrichedPositions[] = [
|
|
'product_name' => $packet->title,
|
|
'product_info' => $packet->description ?? '',
|
|
'amount' => $position['amount'] ?: 1,
|
|
'unit' => 'Pau.',
|
|
'price' => rand(50, 300),
|
|
'discount' => 0,
|
|
'vatrate' => 20
|
|
];
|
|
} elseif (isset($position['articleText'])) {
|
|
$enrichedPositions[] = [
|
|
'product_name' => $position['articleText'],
|
|
'product_info' => '',
|
|
'amount' => $position['amount'] ?? 1,
|
|
'unit' => 'Stk.',
|
|
'price' => rand(10, 100),
|
|
'discount' => 0,
|
|
'vatrate' => 20
|
|
];
|
|
}
|
|
}
|
|
|
|
// Add hours entries
|
|
$hoursEntries = json_decode($shippingNote->hoursEntries, true) ?: [];
|
|
foreach ($hoursEntries as $hoursEntry) {
|
|
$hourCount = floatval(str_replace(",", ".", $hoursEntry['hourCount'] ?? 0));
|
|
if ($hourCount <= 0) continue;
|
|
|
|
$userName = 'Mitarbeiter';
|
|
if (!empty($hoursEntry['userId']) && is_numeric($hoursEntry['userId'])) {
|
|
$user = UserModel::getOne($hoursEntry['userId']);
|
|
$userName = $user ? $user->name : 'Mitarbeiter';
|
|
} elseif (!empty($hoursEntry['userId_text'])) {
|
|
$userName = $hoursEntry['userId_text'];
|
|
}
|
|
|
|
$enrichedPositions[] = [
|
|
'product_name' => 'Arbeitsstunden - ' . $userName,
|
|
'product_info' => 'Datum: ' . (isset($hoursEntry['date']) ? date('d.m.Y', strtotime($hoursEntry['date'])) : date('d.m.Y')),
|
|
'amount' => $hourCount,
|
|
'unit' => 'h',
|
|
'price' => 60,
|
|
'discount' => 0,
|
|
'vatrate' => 20
|
|
];
|
|
}
|
|
|
|
// If still no positions, create some mock positions
|
|
if (empty($enrichedPositions)) {
|
|
$mockPositions = [
|
|
['name' => 'Beratungsleistung', 'unit' => 'h', 'price' => 85],
|
|
['name' => 'Installationsarbeiten', 'unit' => 'Pau.', 'price' => 250],
|
|
['name' => 'Netzwerkkabel Cat6', 'unit' => 'm', 'price' => 3.50],
|
|
['name' => 'Router TP-Link', 'unit' => 'Stk.', 'price' => 89.90],
|
|
['name' => 'Montage vor Ort', 'unit' => 'h', 'price' => 65],
|
|
];
|
|
|
|
// Add 1-3 random mock positions
|
|
$numPositions = rand(1, 3);
|
|
for ($i = 0; $i < $numPositions; $i++) {
|
|
$mock = $mockPositions[array_rand($mockPositions)];
|
|
$enrichedPositions[] = [
|
|
'product_name' => $mock['name'],
|
|
'product_info' => 'Mock-Position für Testzwecke',
|
|
'amount' => rand(1, 10),
|
|
'unit' => $mock['unit'],
|
|
'price' => $mock['price'],
|
|
'discount' => rand(0, 1) ? rand(5, 15) : 0,
|
|
'vatrate' => 20
|
|
];
|
|
}
|
|
}
|
|
|
|
// Use random invoice date within last 90 days
|
|
$randomDaysAgo = rand(0, 90);
|
|
$invoiceDate = strtotime("-{$randomDaysAgo} days");
|
|
|
|
// Create invoice data
|
|
$invoiceData = [
|
|
'invoice_number' => ManualInvoiceModel::getNextInvoiceNumber(),
|
|
'invoice_date' => $invoiceDate,
|
|
'owner_id' => $address->id,
|
|
'billingaddress_id' => $address->id,
|
|
'customer_number' => $address->customer_number,
|
|
'company' => $address->company,
|
|
'firstname' => $address->firstname,
|
|
'lastname' => $address->lastname,
|
|
'street' => $address->street,
|
|
'zip' => $address->zip,
|
|
'city' => $address->city,
|
|
'country' => $address->country ?: 'Österreich',
|
|
'email' => $address->email,
|
|
'uid' => $address->uid,
|
|
'fibu_account_number' => $address->fibu_account_number,
|
|
'fibu_payment_due' => $address->fibu_payment_due ?: 14,
|
|
'fibu_payment_skonto' => $address->fibu_payment_skonto ?: 0,
|
|
'fibu_payment_skonto_rate' => $address->fibu_payment_skonto_rate ?: 0,
|
|
'billing_type' => $address->billing_type ?: 'invoice',
|
|
'billing_delivery' => $address->billing_delivery ?: 'email',
|
|
'bank_account_bank' => $address->bank_account_bank,
|
|
'bank_account_owner' => $address->bank_account_owner,
|
|
'bank_account_iban' => $address->bank_account_iban,
|
|
'bank_account_bic' => $address->bank_account_bic,
|
|
'sepa_date' => $address->sepa_date ? (is_numeric($address->sepa_date) ? date('Y-m-d', $address->sepa_date) : $address->sepa_date) : null,
|
|
'leistungszeitraum' => date('m/Y', $invoiceDate),
|
|
'einleitender_text' => 'Testrechnung basierend auf Lieferschein #' . $shippingNote->id,
|
|
'externe_referenz' => 'TEST-LS-' . $shippingNote->id,
|
|
'gesamtrabatt' => rand(0, 1) ? rand(0, 10) : 0,
|
|
'total' => 0,
|
|
'total_gross' => 0,
|
|
'vatgroup_id' => rand(1, 3),
|
|
'status' => 'erstellt',
|
|
'lock' => 0,
|
|
'exported' => 0,
|
|
'create_by' => 1,
|
|
'edit_by' => 1,
|
|
'create' => time(),
|
|
'edit' => time()
|
|
];
|
|
|
|
// Create the invoice
|
|
$invoiceId = ManualInvoiceModel::create($invoiceData);
|
|
|
|
if (!$invoiceId) {
|
|
echo " - Error creating invoice\n";
|
|
$errors++;
|
|
continue;
|
|
}
|
|
|
|
// Create positions
|
|
$total = 0;
|
|
$totalGross = 0;
|
|
$gesamtrabatt = floatval($invoiceData['gesamtrabatt']);
|
|
|
|
foreach ($enrichedPositions as $pos) {
|
|
$amount = floatval($pos['amount']);
|
|
$price = floatval($pos['price']);
|
|
$discount = floatval($pos['discount'] ?? 0);
|
|
$vatrate = floatval($pos['vatrate'] ?? 20);
|
|
|
|
// Validate amount is within reasonable bounds
|
|
if ($amount <= 0 || $amount > 999999) {
|
|
$amount = 1;
|
|
}
|
|
if ($price < 0 || $price > 999999) {
|
|
$price = 0;
|
|
}
|
|
|
|
$priceTotal = $amount * $price * (1 - $discount / 100);
|
|
$priceTotalAfterGesamtrabatt = $priceTotal * (1 - $gesamtrabatt / 100);
|
|
$priceGross = $priceTotalAfterGesamtrabatt * (1 + $vatrate / 100);
|
|
|
|
// Use direct SQL to bypass model validation for mock data
|
|
$posProduct = $db->escape($pos['product_name']);
|
|
$posInfo = $db->escape($pos['product_info'] ?? '');
|
|
$posProductId = intval($pos['article_id'] ?? 0);
|
|
$posUnit = $db->escape($pos['unit'] ?? 'Stk.');
|
|
$posTime = time();
|
|
|
|
// Ensure values are numeric and within DB limits
|
|
$amount = round($amount, 2);
|
|
$price = round($price, 2);
|
|
$priceTotal = round($priceTotal, 2);
|
|
$priceGross = round($priceGross, 2);
|
|
|
|
$insertSql = "INSERT INTO ManualInvoiceposition
|
|
(manualinvoice_id, position_group, product_id, product_name, product_info, amount, unit, price, discount, vatrate, price_total, price_gross, matchcode, fibu_cost_account, fibu_cost_account_legacy, fibu_taxcode, contract_id, billing_id, create_by, edit_by, `create`, edit)
|
|
VALUES
|
|
($invoiceId, NULL, $posProductId, '$posProduct', '$posInfo', $amount, '$posUnit', $price, $discount, $vatrate, $priceTotal, $priceGross, NULL, NULL, NULL, NULL, 0, NULL, 1, 1, $posTime, $posTime)";
|
|
|
|
try {
|
|
$db->query($insertSql);
|
|
} catch (Throwable $e) {
|
|
echo " Warning: Position skipped (amount=$amount, price=$price): " . $e->getMessage() . "\n";
|
|
continue;
|
|
}
|
|
|
|
$total += $priceTotal;
|
|
$totalGross += $priceGross;
|
|
}
|
|
|
|
// Apply gesamtrabatt to total
|
|
$totalAfterRabatt = $total * (1 - $gesamtrabatt / 100);
|
|
|
|
// Update invoice totals using direct SQL (bypass model validation)
|
|
$db->query("UPDATE ManualInvoice SET total = " . floatval($totalAfterRabatt) . ", total_gross = " . floatval($totalGross) . " WHERE id = " . intval($invoiceId));
|
|
|
|
// Create journal entry using direct SQL
|
|
$journalText = $db->escape('Mock-Rechnung erstellt (basierend auf LS #' . $shippingNote->id . ')');
|
|
$journalTime = time();
|
|
$db->query("INSERT INTO ManualInvoiceJournal (manualinvoiceId, text, statusChange, createBy, `create`)
|
|
VALUES ($invoiceId, '$journalText', 'erstellt', 1, $journalTime)");
|
|
|
|
$invoiceNumber = $invoiceData['invoice_number'];
|
|
$customerName = trim(($address->company ?: '') . ' ' . $address->firstname . ' ' . $address->lastname);
|
|
echo " - Created invoice #{$invoiceId} ({$invoiceNumber})\n";
|
|
echo " Customer: {$customerName}\n";
|
|
echo " Positions: " . count($enrichedPositions) . ", Total: €" . number_format($totalAfterRabatt, 2) . "\n";
|
|
$count++;
|
|
}
|
|
|
|
echo "\n========================================\n";
|
|
echo "Mock invoice creation complete!\n";
|
|
echo "Created: {$count} invoices\n";
|
|
echo "Errors/Skipped: {$errors}\n";
|
|
echo "========================================\n";
|
|
echo "\nNOTE: No emails were sent. These are test invoices only.\n";
|