Merge branch 'ManualInvoice/create-test-script' into 'master'
created test creation invoice script for mock data See merge request fronk/thetool!2058
This commit is contained in:
@@ -1,314 +0,0 @@
|
||||
#!/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";
|
||||
464
scripts/ManualInvoice/create-test-invoices.php
Normal file
464
scripts/ManualInvoice/create-test-invoices.php
Normal file
@@ -0,0 +1,464 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
require(__DIR__ . "/../../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);
|
||||
|
||||
$db = FronkDB::singleton();
|
||||
|
||||
echo "========================================\n";
|
||||
echo "Manual Invoice Test Data Generator\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
echo "Do you want to truncate ManualInvoice tables first? (y/n): ";
|
||||
$handle = fopen("php://stdin", "r");
|
||||
$answer = trim(fgets($handle));
|
||||
fclose($handle);
|
||||
|
||||
if (strtolower($answer) === 'y') {
|
||||
echo "Truncating tables...\n";
|
||||
$db->query("SET FOREIGN_KEY_CHECKS = 0");
|
||||
$db->query("TRUNCATE TABLE ManualInvoiceJournal");
|
||||
$db->query("TRUNCATE TABLE ManualInvoiceposition");
|
||||
$db->query("TRUNCATE TABLE ManualInvoice");
|
||||
$db->query("SET FOREIGN_KEY_CHECKS = 1");
|
||||
echo "Tables truncated.\n\n";
|
||||
}
|
||||
|
||||
$vatgroups = [];
|
||||
$res = $db->query("SELECT * FROM Vatgroup WHERE id IN (2, 3) ORDER BY id");
|
||||
while ($row = $db->fetch_object($res)) {
|
||||
$vatgroups[$row->id] = $row;
|
||||
}
|
||||
|
||||
if (empty($vatgroups)) {
|
||||
echo "ERROR: No VAT groups found!\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Found " . count($vatgroups) . " VAT group(s)\n";
|
||||
|
||||
$vatrates = [];
|
||||
$res = $db->query("SELECT * FROM Vatrate WHERE vatgroup_id IN (2, 3)");
|
||||
while ($row = $db->fetch_object($res)) {
|
||||
$vatrates[$row->vatgroup_id][$row->area] = $row;
|
||||
}
|
||||
|
||||
$articlesByVatgroup = [];
|
||||
$res = $db->query("SELECT * FROM WarehouseArticle WHERE vatgroup_id IN (2, 3) AND (isEndOfLife = 0 OR isEndOfLife IS NULL)");
|
||||
while ($row = $db->fetch_object($res)) {
|
||||
if (!isset($articlesByVatgroup[$row->vatgroup_id])) {
|
||||
$articlesByVatgroup[$row->vatgroup_id] = [];
|
||||
}
|
||||
$articlesByVatgroup[$row->vatgroup_id][] = $row;
|
||||
}
|
||||
|
||||
$totalArticles = array_sum(array_map('count', $articlesByVatgroup));
|
||||
if ($totalArticles == 0) {
|
||||
echo "ERROR: No warehouse articles found!\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Found $totalArticles warehouse articles\n";
|
||||
|
||||
function getCustomersByRegion($db, $region) {
|
||||
$customers = [];
|
||||
switch ($region) {
|
||||
case 'domestic':
|
||||
$sql = "SELECT a.* FROM Address a
|
||||
LEFT JOIN Country c ON a.country_id = c.id
|
||||
WHERE a.customer_number > 0
|
||||
AND (a.company IS NOT NULL OR a.firstname IS NOT NULL)
|
||||
AND a.street IS NOT NULL AND a.zip IS NOT NULL AND a.city IS NOT NULL
|
||||
AND (c.isocode = 'AT' OR a.uid LIKE 'ATU%' OR a.country_id IS NULL)
|
||||
ORDER BY RAND() LIMIT 20";
|
||||
break;
|
||||
case 'eu':
|
||||
$sql = "SELECT a.* FROM Address a
|
||||
JOIN Country c ON a.country_id = c.id
|
||||
WHERE a.customer_number > 0
|
||||
AND (a.company IS NOT NULL OR a.firstname IS NOT NULL)
|
||||
AND a.street IS NOT NULL AND a.zip IS NOT NULL AND a.city IS NOT NULL
|
||||
AND c.is_eu = 1 AND c.isocode != 'AT'
|
||||
AND (a.uid IS NULL OR a.uid NOT LIKE 'ATU%')
|
||||
ORDER BY RAND() LIMIT 20";
|
||||
break;
|
||||
case 'other':
|
||||
$sql = "SELECT a.* FROM Address a
|
||||
JOIN Country c ON a.country_id = c.id
|
||||
WHERE a.customer_number > 0
|
||||
AND (a.company IS NOT NULL OR a.firstname IS NOT NULL)
|
||||
AND a.street IS NOT NULL AND a.zip IS NOT NULL AND a.city IS NOT NULL
|
||||
AND c.is_eu = 0
|
||||
ORDER BY RAND() LIMIT 20";
|
||||
break;
|
||||
}
|
||||
$res = $db->query($sql);
|
||||
while ($row = $db->fetch_object($res)) {
|
||||
$customers[] = $row;
|
||||
}
|
||||
return $customers;
|
||||
}
|
||||
|
||||
$domesticCustomers = getCustomersByRegion($db, 'domestic');
|
||||
$euCustomers = getCustomersByRegion($db, 'eu');
|
||||
$nonEuCustomers = getCustomersByRegion($db, 'other');
|
||||
|
||||
echo "Found customers: " . count($domesticCustomers) . " domestic, " . count($euCustomers) . " EU, " . count($nonEuCustomers) . " non-EU\n\n";
|
||||
|
||||
function getVatArea($address, $db) {
|
||||
$uid = preg_replace('/[^a-z0-9]/i', '', $address->uid ?? '');
|
||||
if (strtolower(substr($uid, 0, 3)) === 'atu') return 'domestic';
|
||||
if ($address->country_id) {
|
||||
$res = $db->query("SELECT * FROM Country WHERE id = " . intval($address->country_id));
|
||||
if ($country = $db->fetch_object($res)) {
|
||||
if ($country->isocode === TT_HOMECOUNTRY_ISOCODE) return 'domestic';
|
||||
if ($country->is_eu) return 'eu';
|
||||
return 'other';
|
||||
}
|
||||
}
|
||||
return 'domestic';
|
||||
}
|
||||
|
||||
function getArticlePrice($article) {
|
||||
if ($article->cheapestSellPrice) {
|
||||
$prices = json_decode($article->cheapestSellPrice, true);
|
||||
if (is_array($prices)) {
|
||||
foreach ($prices as $p) {
|
||||
if (isset($p['price']) && $p['price'] > 0) return floatval($p['price']);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rand(10, 500);
|
||||
}
|
||||
|
||||
function createInvoice($db, $data) {
|
||||
$invoiceNumber = ManualInvoiceModel::getNextInvoiceNumber();
|
||||
$now = time();
|
||||
$sql = "INSERT INTO ManualInvoice (
|
||||
invoice_number, invoice_date, performance_period, introductory_text, external_reference, total_discount,
|
||||
owner_id, billingaddress_id, customer_number, fibu_account_number, fibu_payment_due, fibu_payment_skonto, fibu_payment_skonto_rate,
|
||||
sepa_date, fibu_cost_area, fibu_cost_account, fibu_cost_account_legacy, fibu_taxcode, tax_text,
|
||||
company, firstname, lastname, street, zip, city, country, email, uid,
|
||||
billing_type, bank_account_bank, bank_account_owner, bank_account_iban, bank_account_bic,
|
||||
total, total_gross, vatgroup_id, status, `lock`, exported, create_by, edit_by, `create`, edit
|
||||
) VALUES (
|
||||
'{$db->escape($invoiceNumber)}', " . intval($data['invoice_date']) . ",
|
||||
'{$db->escape($data['performance_period'])}', '{$db->escape($data['introductory_text'])}',
|
||||
'{$db->escape($data['external_reference'])}', " . floatval($data['total_discount']) . ",
|
||||
" . intval($data['owner_id']) . ", " . intval($data['billingaddress_id']) . ", " . intval($data['customer_number']) . ",
|
||||
" . ($data['fibu_account_number'] ? intval($data['fibu_account_number']) : 'NULL') . ",
|
||||
" . ($data['fibu_payment_due'] ? intval($data['fibu_payment_due']) : 'NULL') . ",
|
||||
" . intval($data['fibu_payment_skonto']) . ", " . intval($data['fibu_payment_skonto_rate']) . ",
|
||||
" . ($data['sepa_date'] ? "'{$db->escape($data['sepa_date'])}'" : 'NULL') . ",
|
||||
" . ($data['fibu_cost_area'] ? "'{$db->escape($data['fibu_cost_area'])}'" : 'NULL') . ",
|
||||
" . ($data['fibu_cost_account'] ? intval($data['fibu_cost_account']) : 'NULL') . ",
|
||||
" . ($data['fibu_cost_account_legacy'] ? intval($data['fibu_cost_account_legacy']) : 'NULL') . ",
|
||||
" . ($data['fibu_taxcode'] ? intval($data['fibu_taxcode']) : 'NULL') . ",
|
||||
" . ($data['tax_text'] ? "'{$db->escape($data['tax_text'])}'" : 'NULL') . ",
|
||||
'{$db->escape($data['company'])}', '{$db->escape($data['firstname'])}', '{$db->escape($data['lastname'])}',
|
||||
'{$db->escape($data['street'])}', '{$db->escape($data['zip'])}', '{$db->escape($data['city'])}',
|
||||
'{$db->escape($data['country'])}', '{$db->escape($data['email'])}', '{$db->escape($data['uid'])}',
|
||||
'{$db->escape($data['billing_type'])}',
|
||||
'{$db->escape($data['bank_account_bank'])}', '{$db->escape($data['bank_account_owner'])}',
|
||||
'{$db->escape($data['bank_account_iban'])}', '{$db->escape($data['bank_account_bic'])}',
|
||||
0, 0, " . intval($data['vatgroup_id']) . ", 'erstellt', 0, 0, 1, 1, {$now}, {$now}
|
||||
)";
|
||||
$db->query($sql);
|
||||
return ['id' => $db->link->insert_id, 'invoice_number' => $invoiceNumber];
|
||||
}
|
||||
|
||||
function createPosition($db, $invoiceId, $pos) {
|
||||
$now = time();
|
||||
$fibuAccount = $pos['fibu_cost_account'] ? intval($pos['fibu_cost_account']) : 'NULL';
|
||||
$fibuAccountLegacy = $pos['fibu_cost_account_legacy'] ? intval($pos['fibu_cost_account_legacy']) : 'NULL';
|
||||
$fibuTaxcode = $pos['fibu_taxcode'] ? intval($pos['fibu_taxcode']) : 'NULL';
|
||||
$sql = "INSERT INTO ManualInvoiceposition (
|
||||
manualinvoice_id, warehousearticle_id, warehousearticle_name, product_info,
|
||||
amount, unit, price, discount, vatrate, price_total, price_gross,
|
||||
fibu_cost_account, fibu_cost_account_legacy, fibu_taxcode, create_by, edit_by, `create`, edit
|
||||
) VALUES (
|
||||
$invoiceId, " . intval($pos['warehousearticle_id']) . ", '{$db->escape($pos['warehousearticle_name'])}',
|
||||
'{$db->escape($pos['product_info'])}', {$pos['amount']}, '{$db->escape($pos['unit'])}',
|
||||
{$pos['price']}, {$pos['discount']}, {$pos['vatrate']},
|
||||
" . round($pos['price_total'], 2) . ", " . round($pos['price_gross'], 2) . ",
|
||||
$fibuAccount, $fibuAccountLegacy, $fibuTaxcode, 1, 1, $now, $now
|
||||
)";
|
||||
$db->query($sql);
|
||||
}
|
||||
|
||||
$regions = [
|
||||
'domestic' => ['name' => 'Domestic (AT)', 'customers' => $domesticCustomers],
|
||||
'eu' => ['name' => 'EU', 'customers' => $euCustomers],
|
||||
'other' => ['name' => 'Non-EU', 'customers' => $nonEuCustomers]
|
||||
];
|
||||
|
||||
$totalCreated = 0;
|
||||
$errors = 0;
|
||||
|
||||
echo "Generating invoices...\n";
|
||||
echo "----------------------------------------\n";
|
||||
|
||||
foreach ($vatgroups as $vatgroupId => $vatgroup) {
|
||||
$vgArticles = $articlesByVatgroup[$vatgroupId] ?? [];
|
||||
if (empty($vgArticles)) {
|
||||
echo "[Vatgroup: {$vatgroup->name}] SKIPPED - No articles\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($regions as $regionKey => $regionData) {
|
||||
$customers = $regionData['customers'];
|
||||
if (empty($customers)) {
|
||||
echo "[Vatgroup: {$vatgroup->name}] [Region: {$regionData['name']}] SKIPPED - No customers\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= 4; $i++) {
|
||||
$customer = $customers[array_rand($customers)];
|
||||
$address = new Address($customer->id);
|
||||
if (!$address || !$address->id) { $errors++; continue; }
|
||||
|
||||
$vatarea = getVatArea($address, $db);
|
||||
$vatrate = $vatrates[$vatgroupId][$vatarea] ?? null;
|
||||
$vatrateValue = $vatrate ? floatval($vatrate->rate) : 20.0;
|
||||
$fibuCostAccount = $vatrate ? intval($vatrate->account) : null;
|
||||
$fibuCostAccountLegacy = $vatrate ? ($vatrate->legacy_account ? intval($vatrate->legacy_account) : null) : null;
|
||||
$fibuTaxcode = $vatrate ? ($vatrate->taxcode ? intval($vatrate->taxcode) : null) : null;
|
||||
$taxText = $vatrate ? ($vatrate->invoice_text ?? '') : '';
|
||||
|
||||
$numPositions = rand(1, 5);
|
||||
shuffle($vgArticles);
|
||||
$selectedArticles = array_slice($vgArticles, 0, min($numPositions, count($vgArticles)));
|
||||
$totalDiscount = rand(0, 1) ? rand(0, 10) : 0;
|
||||
|
||||
$positions = [];
|
||||
foreach ($selectedArticles as $article) {
|
||||
$amount = rand(1, 10);
|
||||
$price = getArticlePrice($article);
|
||||
$discount = rand(0, 1) ? rand(0, 15) : 0;
|
||||
$priceTotal = $amount * $price * (1 - $discount / 100);
|
||||
$priceTotalAfterDiscount = $priceTotal * (1 - $totalDiscount / 100);
|
||||
$priceGross = $priceTotalAfterDiscount * (1 + $vatrateValue / 100);
|
||||
|
||||
$positions[] = [
|
||||
'warehousearticle_id' => $article->id,
|
||||
'warehousearticle_name' => $article->articleNumber . ' | ' . $article->title,
|
||||
'product_info' => $article->description ?? '',
|
||||
'amount' => $amount,
|
||||
'unit' => $article->unit ?: 'Stk.',
|
||||
'price' => $price,
|
||||
'discount' => $discount,
|
||||
'vatrate' => $vatrateValue,
|
||||
'price_total' => $priceTotal,
|
||||
'price_gross' => $priceGross,
|
||||
'fibu_cost_account' => $fibuCostAccount,
|
||||
'fibu_cost_account_legacy' => $fibuCostAccountLegacy,
|
||||
'fibu_taxcode' => $fibuTaxcode
|
||||
];
|
||||
}
|
||||
|
||||
$randomDaysAgo = rand(0, 60);
|
||||
$invoiceDate = strtotime("-{$randomDaysAgo} days");
|
||||
|
||||
$invoiceData = [
|
||||
'invoice_date' => $invoiceDate,
|
||||
'owner_id' => $address->id,
|
||||
'billingaddress_id' => $address->id,
|
||||
'customer_number' => $address->customer_number ?: 0,
|
||||
'company' => $address->company ?: '',
|
||||
'firstname' => $address->firstname ?: '',
|
||||
'lastname' => $address->lastname ?: '',
|
||||
'street' => $address->street ?: '',
|
||||
'zip' => $address->zip ?: '',
|
||||
'city' => $address->city ?: '',
|
||||
'country' => 'Österreich',
|
||||
'email' => $address->email ?: '',
|
||||
'uid' => $address->uid ?: '',
|
||||
'fibu_account_number' => $address->fibu_account_number ?: 0,
|
||||
'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,
|
||||
'fibu_cost_area' => $vatarea,
|
||||
'fibu_cost_account' => $fibuCostAccount,
|
||||
'fibu_cost_account_legacy' => $fibuCostAccountLegacy,
|
||||
'fibu_taxcode' => $fibuTaxcode,
|
||||
'billing_type' => $address->billing_type ?: 'invoice',
|
||||
'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,
|
||||
'performance_period' => date('m/Y', $invoiceDate),
|
||||
'introductory_text' => "Test invoice for '{$vatgroup->name}' in '{$regionData['name']}'",
|
||||
'external_reference' => "TEST-VG{$vatgroupId}-{$regionKey}-{$i}",
|
||||
'total_discount' => $totalDiscount,
|
||||
'vatgroup_id' => $vatgroupId,
|
||||
'tax_text' => $taxText
|
||||
];
|
||||
|
||||
$result = createInvoice($db, $invoiceData);
|
||||
$invoiceId = $result['id'];
|
||||
$invoiceNumber = $result['invoice_number'];
|
||||
|
||||
if (!$invoiceId) { echo "[Vatgroup: {$vatgroup->name}] [Region: {$regionData['name']}] Invoice #{$i}: ERROR\n"; $errors++; continue; }
|
||||
|
||||
$total = 0;
|
||||
$totalGross = 0;
|
||||
foreach ($positions as $pos) {
|
||||
createPosition($db, $invoiceId, $pos);
|
||||
$total += $pos['price_total'];
|
||||
$totalGross += $pos['price_gross'];
|
||||
}
|
||||
|
||||
$totalAfterRabatt = $total * (1 - $totalDiscount / 100);
|
||||
$db->query("UPDATE ManualInvoice SET total = " . round($totalAfterRabatt, 2) . ", total_gross = " . round($totalGross, 2) . " WHERE id = $invoiceId");
|
||||
|
||||
$journalText = $db->escape("Test invoice created (Vatgroup: {$vatgroup->name}, Region: {$regionData['name']})");
|
||||
$db->query("INSERT INTO ManualInvoiceJournal (manualinvoiceId, text, statusChange, createBy, `create`) VALUES ($invoiceId, '$journalText', 'erstellt', 1, " . time() . ")");
|
||||
|
||||
$customerName = trim(($address->company ?: '') . ' ' . ($address->firstname ?: '') . ' ' . ($address->lastname ?: '')) ?: 'Unknown';
|
||||
echo "[{$vatgroup->name}] [{$regionData['name']}] #{$i}: {$invoiceNumber} - {$customerName} - " . count($positions) . " pos - " . number_format($totalAfterRabatt, 2) . " EUR\n";
|
||||
$totalCreated++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n----------------------------------------\n";
|
||||
echo "Creating mixed VAT group invoices...\n";
|
||||
echo "----------------------------------------\n";
|
||||
|
||||
foreach ($regions as $regionKey => $regionData) {
|
||||
$customers = $regionData['customers'];
|
||||
if (empty($customers)) { echo "[Mixed] [{$regionData['name']}] SKIPPED - No customers\n"; continue; }
|
||||
|
||||
for ($i = 1; $i <= 4; $i++) {
|
||||
$customer = $customers[array_rand($customers)];
|
||||
$address = new Address($customer->id);
|
||||
if (!$address || !$address->id) { $errors++; continue; }
|
||||
|
||||
$vatarea = getVatArea($address, $db);
|
||||
$positions = [];
|
||||
$usedVatgroups = [];
|
||||
|
||||
foreach ($articlesByVatgroup as $vgId => $vgArticles) {
|
||||
if (empty($vgArticles)) continue;
|
||||
$article = $vgArticles[array_rand($vgArticles)];
|
||||
$articleVatrate = $vatrates[$vgId][$vatarea] ?? null;
|
||||
$posVatrateValue = $articleVatrate ? floatval($articleVatrate->rate) : 20.0;
|
||||
$posFibuCostAccount = $articleVatrate ? intval($articleVatrate->account) : null;
|
||||
$posFibuCostAccountLegacy = $articleVatrate ? ($articleVatrate->legacy_account ? intval($articleVatrate->legacy_account) : null) : null;
|
||||
$posFibuTaxcode = $articleVatrate ? ($articleVatrate->taxcode ? intval($articleVatrate->taxcode) : null) : null;
|
||||
|
||||
$amount = rand(1, 5);
|
||||
$price = getArticlePrice($article);
|
||||
$discount = rand(0, 1) ? rand(0, 10) : 0;
|
||||
$priceTotal = $amount * $price * (1 - $discount / 100);
|
||||
$priceGross = $priceTotal * (1 + $posVatrateValue / 100);
|
||||
|
||||
$positions[] = [
|
||||
'warehousearticle_id' => $article->id,
|
||||
'warehousearticle_name' => $article->articleNumber . ' | ' . $article->title,
|
||||
'product_info' => $article->description ?? '',
|
||||
'amount' => $amount,
|
||||
'unit' => $article->unit ?: 'Stk.',
|
||||
'price' => $price,
|
||||
'discount' => $discount,
|
||||
'vatrate' => $posVatrateValue,
|
||||
'price_total' => $priceTotal,
|
||||
'price_gross' => $priceGross,
|
||||
'fibu_cost_account' => $posFibuCostAccount,
|
||||
'fibu_cost_account_legacy' => $posFibuCostAccountLegacy,
|
||||
'fibu_taxcode' => $posFibuTaxcode
|
||||
];
|
||||
$usedVatgroups[] = $vgId;
|
||||
}
|
||||
|
||||
if (count($positions) < 2) { echo "[Mixed] [{$regionData['name']}] #{$i}: SKIPPED - Not enough vatgroups\n"; continue; }
|
||||
|
||||
$primaryVatgroupId = $usedVatgroups[0];
|
||||
$primaryVatrate = $vatrates[$primaryVatgroupId][$vatarea] ?? null;
|
||||
$fibuCostAccount = $primaryVatrate ? intval($primaryVatrate->account) : null;
|
||||
$fibuCostAccountLegacy = $primaryVatrate ? ($primaryVatrate->legacy_account ? intval($primaryVatrate->legacy_account) : null) : null;
|
||||
$fibuTaxcode = $primaryVatrate ? ($primaryVatrate->taxcode ? intval($primaryVatrate->taxcode) : null) : null;
|
||||
$taxText = $primaryVatrate ? ($primaryVatrate->invoice_text ?? '') : '';
|
||||
|
||||
$randomDaysAgo = rand(0, 60);
|
||||
$invoiceDate = strtotime("-{$randomDaysAgo} days");
|
||||
$totalDiscount = 0;
|
||||
|
||||
$vatgroupNames = array_map(fn($vgId) => $vatgroups[$vgId]->name ?? "VG$vgId", $usedVatgroups);
|
||||
|
||||
$invoiceData = [
|
||||
'invoice_date' => $invoiceDate,
|
||||
'owner_id' => $address->id,
|
||||
'billingaddress_id' => $address->id,
|
||||
'customer_number' => $address->customer_number ?: 0,
|
||||
'company' => $address->company ?: '',
|
||||
'firstname' => $address->firstname ?: '',
|
||||
'lastname' => $address->lastname ?: '',
|
||||
'street' => $address->street ?: '',
|
||||
'zip' => $address->zip ?: '',
|
||||
'city' => $address->city ?: '',
|
||||
'country' => 'Österreich',
|
||||
'email' => $address->email ?: '',
|
||||
'uid' => $address->uid ?: '',
|
||||
'fibu_account_number' => $address->fibu_account_number ?: 0,
|
||||
'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,
|
||||
'fibu_cost_area' => $vatarea,
|
||||
'fibu_cost_account' => $fibuCostAccount,
|
||||
'fibu_cost_account_legacy' => $fibuCostAccountLegacy,
|
||||
'fibu_taxcode' => $fibuTaxcode,
|
||||
'billing_type' => $address->billing_type ?: 'invoice',
|
||||
'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,
|
||||
'performance_period' => date('m/Y', $invoiceDate),
|
||||
'introductory_text' => "Mixed VAT group test (" . implode(' + ', $vatgroupNames) . ") in '{$regionData['name']}'",
|
||||
'external_reference' => "TEST-MIXED-{$regionKey}-{$i}",
|
||||
'total_discount' => $totalDiscount,
|
||||
'vatgroup_id' => $primaryVatgroupId,
|
||||
'tax_text' => $taxText
|
||||
];
|
||||
|
||||
$result = createInvoice($db, $invoiceData);
|
||||
$invoiceId = $result['id'];
|
||||
$invoiceNumber = $result['invoice_number'];
|
||||
|
||||
if (!$invoiceId) { echo "[Mixed] [{$regionData['name']}] #{$i}: ERROR\n"; $errors++; continue; }
|
||||
|
||||
$total = 0;
|
||||
$totalGross = 0;
|
||||
foreach ($positions as $pos) {
|
||||
createPosition($db, $invoiceId, $pos);
|
||||
$total += $pos['price_total'];
|
||||
$totalGross += $pos['price_gross'];
|
||||
}
|
||||
|
||||
$db->query("UPDATE ManualInvoice SET total = " . round($total, 2) . ", total_gross = " . round($totalGross, 2) . " WHERE id = $invoiceId");
|
||||
|
||||
$journalText = $db->escape("Mixed VAT group test invoice (" . implode(', ', $vatgroupNames) . ", {$regionData['name']})");
|
||||
$db->query("INSERT INTO ManualInvoiceJournal (manualinvoiceId, text, statusChange, createBy, `create`) VALUES ($invoiceId, '$journalText', 'erstellt', 1, " . time() . ")");
|
||||
|
||||
$customerName = trim(($address->company ?: '') . ' ' . ($address->firstname ?: '') . ' ' . ($address->lastname ?: '')) ?: 'Unknown';
|
||||
echo "[Mixed] [{$regionData['name']}] #{$i}: {$invoiceNumber} - {$customerName} - " . count($positions) . " pos - " . number_format($total, 2) . " EUR\n";
|
||||
$totalCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n========================================\n";
|
||||
echo "Complete! Created: {$totalCreated} invoices, Errors: {$errors}\n";
|
||||
echo "========================================\n";
|
||||
Reference in New Issue
Block a user