Stock Transfer list showed 0.00 (read in instead of out); stock-out/transfer forms show the location quantity; dates display as YYYY-MM-DD HH:mm:ss. Demo seeds map product accounts, use product names and supplier batches, and add demo_seed_assets.php.
375 lines
21 KiB
PHP
375 lines
21 KiB
PHP
<?php
|
||
/**
|
||
* Demo Data Seeder — Asset Management
|
||
* Run after demo_seed.php and demo_seed_transactions.php: php demo_seed_assets.php
|
||
*
|
||
* Seeds the Asset Management app through the same classes its pages and the
|
||
* Batch GL Entries page use:
|
||
* 1. GL accounts for fixed assets, and asset categories (AssetCategoryManager)
|
||
* 2. A purchase of a laptop fleet + an ERP licence: PO -> confirm -> purchase
|
||
* invoice -> issue -> GL post (PurchaseOrderManager, InvoiceManager), the
|
||
* only way a purchase invoice exists in the app
|
||
* 3. Assets capitalized from those invoice lines, plus two vehicles bought
|
||
* before the system was used and already on the books (AssetManager)
|
||
* 4. Depreciation runs up to last month, the sale of the forklift today, then
|
||
* this month's depreciation and amortization runs (AssetRunManager)
|
||
* 5. GL posting of the purchase invoice, capitalizations, the disposal and every
|
||
* run before this month (AssetPosting + GlManager, as
|
||
* accounting/api/engine/post_gl_entry.php does) — this month's runs stay
|
||
* unposted so Batch GL Entries has Asset Management work to show.
|
||
*
|
||
* Dates are relative to today: the app stamps documents with the current date.
|
||
*
|
||
* Safe to re-run: every step checks what already exists.
|
||
*/
|
||
|
||
if (PHP_SAPI !== 'cli') {
|
||
http_response_code(403);
|
||
exit('Run via CLI only: php demo_seed_assets.php');
|
||
}
|
||
|
||
$_SESSION = [];
|
||
|
||
require_once __DIR__ . '/app/config.php';
|
||
require_once __DIR__ . '/app/dbconn.php';
|
||
require_once __DIR__ . '/app/assets/utils/db_helpers.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes/WarehouseManager.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes/StockManager.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes/ProductManager.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes/PurchaseOrderManager.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes/InvoiceManager.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes_ac/ChartOfAccounts.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes_ac/PostingWindowGuard.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes_ac/GlManager.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes_ac/posting/BasePosting.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes_ac/posting/PurchaseInvoicePosting.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes_as/AssetManager.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes_as/AssetRunManager.php';
|
||
require_once __DIR__ . '/app/assets/utils/classes_as/AssetPosting.php';
|
||
|
||
function info(string $msg): void { echo "\033[36m •\033[0m {$msg}\n"; }
|
||
function ok(string $msg): void { echo "\033[32m ✓\033[0m {$msg}\n"; }
|
||
function skip(string $msg): void { echo "\033[33m –\033[0m {$msg}\n"; }
|
||
|
||
echo "\n\033[1m=== brnwms Demo Asset Management Seeder ===\033[0m\n\n";
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 0. Resolve what the earlier seeders created
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
$sth = $pdo1->prepare("SELECT company_id FROM company_list WHERE channel_name = 'demo' LIMIT 1");
|
||
$sth->execute();
|
||
$company_id = (int)($sth->fetchColumn() ?: 0);
|
||
if (!$company_id) exit("ERROR: demo company not found — run demo_seed.php first.\n");
|
||
|
||
$sth = $pdo1->prepare("SELECT user_id FROM user WHERE username = 'admin' LIMIT 1");
|
||
$sth->execute();
|
||
$owner_user_id = (int)($sth->fetchColumn() ?: 0);
|
||
|
||
$lookup = function (string $sql, array $params) use ($pdo2): int {
|
||
$sth = $pdo2->prepare($sql);
|
||
$sth->execute($params);
|
||
return (int)($sth->fetchColumn() ?: 0);
|
||
};
|
||
|
||
$main_wh = $lookup("SELECT id FROM md_warehouse WHERE company_id = :c AND warehouse_name = 'Main Warehouse' LIMIT 1", [':c' => $company_id]);
|
||
$supplier_id = $lookup("SELECT id FROM md_contact WHERE company_id = :c AND contact_name = 'Asia Electronics Wholesale' LIMIT 1", [':c' => $company_id]);
|
||
$wh_dept_id = $lookup("SELECT id FROM md_department WHERE company_id = :c AND dept_code = 'WH' LIMIT 1", [':c' => $company_id]);
|
||
if (!$main_wh || !$supplier_id || !$wh_dept_id) {
|
||
exit("ERROR: warehouse, supplier or WH department missing — run demo_seed.php and demo_seed_transactions.php first.\n");
|
||
}
|
||
|
||
$coa = new ChartOfAccounts($pdo2, $company_id);
|
||
foreach (['1000', '4000', '5000'] as $code) {
|
||
if (!$coa->getByCode($code)) exit("ERROR: account {$code} missing — run demo_seed_transactions.php first.\n");
|
||
}
|
||
|
||
$logging = ['user_id' => $owner_user_id, 'dt' => date('Y-m-d H:i:s'), 'login' => null, 'action' => 'seed_assets'];
|
||
$today = date('Y-m-d');
|
||
$this_period = date('Y-m');
|
||
$last_period = DepreciationCalculator::addMonths($this_period, -1);
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 1. Accounts + asset categories
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
echo "--- Asset accounts ---\n";
|
||
|
||
$account_defs = [
|
||
['code' => '1500', 'name' => 'Equipment and Vehicles', 'type' => 'asset'],
|
||
['code' => '1590', 'name' => 'Accumulated Depreciation', 'type' => 'asset'],
|
||
['code' => '1600', 'name' => 'Software and Licences', 'type' => 'asset'],
|
||
['code' => '1690', 'name' => 'Accumulated Amortization', 'type' => 'asset'],
|
||
['code' => '4900', 'name' => 'Gain on Asset Disposal', 'type' => 'revenue'],
|
||
['code' => '6100', 'name' => 'Depreciation Expense', 'type' => 'expense'],
|
||
['code' => '6200', 'name' => 'Amortization Expense', 'type' => 'expense'],
|
||
['code' => '6900', 'name' => 'Loss on Asset Disposal', 'type' => 'expense'],
|
||
];
|
||
foreach ($account_defs as $a) {
|
||
if ($coa->getByCode($a['code'])) {
|
||
skip("Account {$a['code']} — {$a['name']} already exists");
|
||
continue;
|
||
}
|
||
$coa->create([
|
||
'account_code' => $a['code'], 'account_name' => $a['name'], 'account_type' => $a['type'],
|
||
'account_category' => '', 'parent_code' => null, 'is_posting' => 1, 'status' => 1,
|
||
]);
|
||
ok("Created account {$a['code']} — {$a['name']} ({$a['type']})");
|
||
}
|
||
|
||
echo "\n--- Asset categories ---\n";
|
||
|
||
$category_defs = [
|
||
['code' => 'IT', 'name' => 'Computer Equipment', 'class' => 'tangible', 'life' => 36, 'cost' => '1500', 'accum' => '1590', 'expense' => '6100'],
|
||
['code' => 'VEH', 'name' => 'Vehicles and Forklifts', 'class' => 'tangible', 'life' => 60, 'cost' => '1500', 'accum' => '1590', 'expense' => '6100'],
|
||
['code' => 'SW', 'name' => 'Software Licences', 'class' => 'intangible', 'life' => 24, 'cost' => '1600', 'accum' => '1690', 'expense' => '6200'],
|
||
];
|
||
$category_ids = [];
|
||
foreach ($category_defs as $c) {
|
||
$id = $lookup("SELECT id FROM md_asset_category WHERE company_id = :c AND category_code = :code LIMIT 1", [':c' => $company_id, ':code' => $c['code']]);
|
||
if ($id) {
|
||
skip("Category {$c['code']} already exists (id={$id})");
|
||
} else {
|
||
$id = (new AssetCategoryManager($pdo2, $company_id))->save([
|
||
'category_code' => $c['code'], 'category_name' => $c['name'], 'asset_class' => $c['class'],
|
||
'useful_life_months' => $c['life'], 'status' => 1, 'description' => '',
|
||
'cost_account_code' => $c['cost'], 'accum_account_code' => $c['accum'], 'expense_account_code' => $c['expense'],
|
||
'gain_account_code' => '4900', 'loss_account_code' => '6900',
|
||
]);
|
||
ok("Created category {$c['code']} — {$c['name']} ({$c['class']}, {$c['life']} months)");
|
||
}
|
||
$category_ids[$c['code']] = $id;
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 2. Purchase: PO -> confirm -> purchase invoice -> issue -> GL
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
echo "\n--- Asset purchase (PO -> purchase invoice) ---\n";
|
||
|
||
$prodMgmt = new ProductManager($pdo2, $company_id);
|
||
|
||
$cat_id = $lookup("SELECT id FROM md_product_category WHERE company_id = :c AND slug = 'fixed-assets' LIMIT 1", [':c' => $company_id]);
|
||
if (!$cat_id) {
|
||
dbTransaction($pdo2, fn($pdo) => (new ProductManager($pdo, $company_id))->saveCategory([
|
||
'id' => 0, 'category' => 'Fixed Assets', 'slug' => 'fixed-assets', 'description' => '', 'status' => 1,
|
||
], $logging));
|
||
$cat_id = $lookup("SELECT id FROM md_product_category WHERE company_id = :c AND slug = 'fixed-assets' LIMIT 1", [':c' => $company_id]);
|
||
ok("Created product category 'Fixed Assets' (id={$cat_id})");
|
||
}
|
||
|
||
$asset_product_defs = [
|
||
['sku' => 'FA-LAPTOP', 'name' => 'Laptop - Dell Latitude 5440', 'price' => 32000],
|
||
['sku' => 'FA-ERP', 'name' => 'ERP Software Licence (2 years)', 'price' => 48000],
|
||
];
|
||
$products = [];
|
||
foreach ($asset_product_defs as $p) {
|
||
$id = $lookup("SELECT id FROM md_product WHERE company_id = :c AND sku = :s LIMIT 1", [':c' => $company_id, ':s' => $p['sku']]);
|
||
if ($id) {
|
||
skip("Product {$p['sku']} already exists");
|
||
} else {
|
||
dbTransaction($pdo2, fn($pdo) => (new ProductManager($pdo, $company_id))->saveProduct([
|
||
'id' => 0, 'product_name' => $p['name'], 'sku' => $p['sku'], 'barcode' => '', 'uom' => 'pcs',
|
||
'price' => $p['price'], 'cost_price' => $p['price'], 'min_stock' => 0, 'reorder_point' => 0,
|
||
'category' => $cat_id, 'description' => 'Bought as a fixed asset (demo seed)', 'status' => 1,
|
||
], $logging, ''));
|
||
$id = $lookup("SELECT id FROM md_product WHERE company_id = :c AND sku = :s LIMIT 1", [':c' => $company_id, ':s' => $p['sku']]);
|
||
// Same accounts as the other demo products: the purchase posts to 5000, and the
|
||
// capitalization entry reclassifies it from there to the asset account.
|
||
dbTransaction($pdo2, fn($pdo) => (new ProductManager($pdo, $company_id))->updateAccountMapping($id, '4000', '5000', $logging));
|
||
ok("Created product {$p['sku']} — {$p['name']} (sales 4000, purchase 5000)");
|
||
}
|
||
$products[$p['sku']] = $p;
|
||
}
|
||
|
||
$po_marker = 'Demo seed asset purchase';
|
||
$po_id = $lookup("SELECT id FROM td_purchase_order WHERE company_id = :c AND notes = :n LIMIT 1", [':c' => $company_id, ':n' => $po_marker]);
|
||
if ($po_id) {
|
||
skip("Asset purchase PO already exists (id={$po_id})");
|
||
} else {
|
||
$po_items = [];
|
||
foreach ([['FA-LAPTOP', 3], ['FA-ERP', 1]] as $i => [$sku, $qty]) {
|
||
$total = round($qty * $products[$sku]['price'], 4);
|
||
$po_items[] = [
|
||
'item_id' => $i + 1, 'product_sku' => $sku, 'product_name' => $products[$sku]['name'],
|
||
'quantity' => $qty, 'unit_price' => $products[$sku]['price'], 'total_price' => $total,
|
||
'tax_rate' => 7.0, 'tax_amount' => round($total * 0.07, 4),
|
||
];
|
||
}
|
||
$po_id = dbTransaction($pdo2, function ($pdo) use ($company_id, $supplier_id, $wh_dept_id, $main_wh, $po_items, $today, $po_marker, $logging) {
|
||
$poMgmt = new PurchaseOrderManager($pdo, $company_id);
|
||
$pid = $poMgmt->savePo([
|
||
'id' => 0, 'contact_id' => $supplier_id, 'department_id' => $wh_dept_id,
|
||
'po_date' => $today, 'expected_date' => date('Y-m-d', strtotime('+7 days')),
|
||
'warehouse_id' => $main_wh, 'items' => $po_items, 'discount' => 0, 'shipping_fee' => 0,
|
||
'notes' => $po_marker,
|
||
], $logging);
|
||
$poMgmt->confirmPo($pid, $logging);
|
||
return $pid;
|
||
});
|
||
ok("Created + confirmed PO for 3 laptops and 1 ERP licence (id={$po_id})");
|
||
}
|
||
|
||
$pi_id = $lookup(
|
||
"SELECT id FROM td_invoice WHERE company_id = :c AND source = 'po' AND source_id = :p AND doc_type = 'purchase_invoice' AND status <> 4 LIMIT 1",
|
||
[':c' => $company_id, ':p' => $po_id]
|
||
);
|
||
if ($pi_id) {
|
||
skip("Purchase invoice for the asset PO already exists (id={$pi_id})");
|
||
} else {
|
||
$pi_id = dbTransaction($pdo2, function ($pdo) use ($company_id, $po_id, $logging) {
|
||
$invMgmt = new InvoiceManager($pdo, $company_id);
|
||
$iid = $invMgmt->createFromPo($po_id, $logging);
|
||
$invMgmt->issueInvoice($iid, $logging, date('Y-m-d', strtotime('+30 days')));
|
||
return $iid;
|
||
});
|
||
ok("Created + issued purchase invoice from the asset PO (id={$pi_id})");
|
||
}
|
||
|
||
$gl = new GlManager($pdo2, $company_id, new PostingWindowGuard($pdo1, $company_id));
|
||
if ($gl->getBySource('purchase_invoice', $pi_id)) {
|
||
skip("GL already posted for purchase invoice #{$pi_id}");
|
||
} else {
|
||
// Same as order/api/engine/issue_invoice.php's auto-post.
|
||
$built = (new PurchaseInvoicePosting($pdo2, $company_id))->build($pi_id, null);
|
||
$pdo2->beginTransaction();
|
||
$gl->post('purchase_invoice', $pi_id, $built['formula_id'], $built['period'], $built['lines'], ['journal_date' => $built['doc_date']]);
|
||
$pdo2->commit();
|
||
ok("GL posted for purchase invoice #{$pi_id} (" . count($built['lines']) . " lines)");
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 3. Assets
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
echo "\n--- Assets ---\n";
|
||
|
||
$sth = $pdo2->prepare("SELECT product_sku, id FROM td_invoice_item WHERE company_id = :c AND invoice_id = :i");
|
||
$sth->execute([':c' => $company_id, ':i' => $pi_id]);
|
||
$invoice_items = $sth->fetchAll(PDO::FETCH_KEY_PAIR); // sku => td_invoice_item.id
|
||
|
||
$asset_defs = [
|
||
['key' => 'laptops', 'name' => 'Laptop fleet - Dell Latitude 5440 (3 units)', 'category' => 'IT', 'sku' => 'FA-LAPTOP', 'salvage' => 6000, 'life' => 36],
|
||
['key' => 'erp', 'name' => 'ERP software licence', 'category' => 'SW', 'sku' => 'FA-ERP', 'salvage' => 0, 'life' => 24],
|
||
['key' => 'truck', 'name' => 'Delivery truck - Isuzu D-Max', 'category' => 'VEH', 'acquired' => date('Y-m-15', strtotime('first day of -18 months')), 'cost' => 850000, 'salvage' => 150000, 'life' => 60],
|
||
['key' => 'forklift','name' => 'Forklift - Toyota 8FD25', 'category' => 'VEH', 'acquired' => date('Y-m-01', strtotime('first day of -30 months')), 'cost' => 420000, 'salvage' => 20000, 'life' => 48],
|
||
];
|
||
|
||
$asset_ids = [];
|
||
foreach ($asset_defs as $d) {
|
||
$sth = $pdo2->prepare("SELECT id, status FROM td_asset WHERE company_id = :c AND asset_name = :n AND status <> 4 LIMIT 1");
|
||
$sth->execute([':c' => $company_id, ':n' => $d['name']]);
|
||
$row = $sth->fetch(PDO::FETCH_ASSOC);
|
||
|
||
if ($row) {
|
||
skip("Asset '{$d['name']}' already exists (id={$row['id']})");
|
||
$id = (int)$row['id'];
|
||
} else {
|
||
$data = [
|
||
'asset_name' => $d['name'], 'category_id' => $category_ids[$d['category']],
|
||
'salvage_value' => $d['salvage'], 'useful_life_months' => $d['life'],
|
||
'department_id' => $wh_dept_id, 'description' => 'Demo seed',
|
||
];
|
||
if (!empty($d['sku'])) {
|
||
// Capitalized from the purchase invoice line, as asset/manage_asset.php?invoice_id=&item_id= does.
|
||
$item_id = (int)($invoice_items[$d['sku']] ?? 0);
|
||
$line = (new AssetManager($pdo2, $company_id))->getInvoiceLine($pi_id, $item_id);
|
||
$data += [
|
||
'invoice_id' => $pi_id, 'invoice_item_id' => $item_id,
|
||
'cost' => $line['remaining'], 'clearing_account_code' => $line['clearing_account_code'],
|
||
'acquisition_date' => $line['issued_date'], 'in_service_date' => $line['issued_date'],
|
||
];
|
||
} else {
|
||
// Bought before the system was used: already on the books, so no clearing account.
|
||
$data += ['cost' => $d['cost'], 'clearing_account_code' => '', 'acquisition_date' => $d['acquired'], 'in_service_date' => $d['acquired']];
|
||
}
|
||
$id = dbTransaction($pdo2, fn($pdo) => (new AssetManager($pdo, $company_id))->save($data, $owner_user_id));
|
||
ok("Created asset '{$d['name']}' (id={$id}), cost " . number_format((float)$data['cost'], 2) . ", in service {$data['in_service_date']}");
|
||
}
|
||
|
||
if ((int)($row['status'] ?? 0) === 0) {
|
||
dbTransaction($pdo2, fn($pdo) => (new AssetManager($pdo, $company_id))->activate($id));
|
||
ok("Activated '{$d['name']}'");
|
||
}
|
||
$asset_ids[$d['key']] = $id;
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 4. Runs and the forklift sale
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
echo "\n--- Depreciation / amortization runs ---\n";
|
||
|
||
$run_through = function (string $asset_class, string $period) use ($pdo2, $company_id, $owner_user_id) {
|
||
$label = AssetRunManager::labelFor($asset_class);
|
||
if (!(new AssetRunManager($pdo2, $company_id))->preview($asset_class, $period)) {
|
||
skip("{$label} already charged through {$period}");
|
||
return;
|
||
}
|
||
$created = dbTransaction($pdo2, fn($pdo) => (new AssetRunManager($pdo, $company_id))->run($asset_class, $period, $owner_user_id));
|
||
$first = $created[0]['run_number'];
|
||
$last = $created[count($created) - 1]['run_number'];
|
||
ok("{$label}: " . count($created) . " run(s) {$first}" . ($first !== $last ? " .. {$last}" : '')
|
||
. ", total " . number_format(array_sum(array_column($created, 'total')), 2));
|
||
};
|
||
|
||
$run_through('tangible', $last_period);
|
||
|
||
$forklift = (new AssetManager($pdo2, $company_id))->getById($asset_ids['forklift']);
|
||
if (in_array((int)$forklift['status'], [AssetManager::STATUS_ACTIVE, AssetManager::STATUS_FULLY], true)) {
|
||
dbTransaction($pdo2, fn($pdo) => (new AssetManager($pdo, $company_id))->dispose($asset_ids['forklift'], [
|
||
'disposal_date' => $today, 'sale_price' => 180000, 'proceeds_account_code' => '1000',
|
||
'disposal_notes' => 'Sold to a used-equipment dealer (demo seed)',
|
||
]));
|
||
ok("Sold '{$forklift['asset_name']}' today for 180,000.00 (book value " . number_format((float)$forklift['book_value'], 2) . ")");
|
||
} else {
|
||
skip("'{$forklift['asset_name']}' already disposed of");
|
||
}
|
||
|
||
$run_through('tangible', $this_period);
|
||
$run_through('intangible', $this_period);
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 5. GL posting (Batch GL Entries equivalent)
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
echo "\n--- GL posting ---\n";
|
||
|
||
$post_asset_gl = function (string $source_type, int $id) use ($pdo1, $pdo2, $company_id) {
|
||
$gl = new GlManager($pdo2, $company_id, new PostingWindowGuard($pdo1, $company_id));
|
||
if ($gl->getBySource($source_type, $id)) return null;
|
||
|
||
$built = (new AssetPosting($pdo2, $company_id))->build($source_type, $id);
|
||
$pdo2->beginTransaction();
|
||
try {
|
||
$gl->post($source_type, $id, 0, $built['period'], $built['lines'], [
|
||
'journal_date' => $built['doc_date'], 'reference' => $built['reference'], 'description' => $built['description'],
|
||
]);
|
||
$pdo2->commit();
|
||
} catch (Throwable $e) {
|
||
if ($pdo2->inTransaction()) $pdo2->rollBack();
|
||
throw $e;
|
||
}
|
||
return $built;
|
||
};
|
||
|
||
$queue = [];
|
||
$sth = $pdo2->prepare("SELECT id FROM td_asset WHERE company_id = :c AND status IN (1, 2, 3) AND clearing_account_code <> '' ORDER BY id");
|
||
$sth->execute([':c' => $company_id]);
|
||
foreach ($sth->fetchAll(PDO::FETCH_COLUMN) as $id) $queue[] = ['asset_capitalization', (int)$id];
|
||
|
||
$sth = $pdo2->prepare("SELECT id, asset_class FROM td_asset_run WHERE company_id = :c AND status = 1 AND period < :p ORDER BY period, id");
|
||
$sth->execute([':c' => $company_id, ':p' => $this_period]);
|
||
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $r) $queue[] = [AssetRunManager::sourceTypeFor($r['asset_class']), (int)$r['id']];
|
||
|
||
$sth = $pdo2->prepare("SELECT id FROM td_asset WHERE company_id = :c AND status = 3 ORDER BY id");
|
||
$sth->execute([':c' => $company_id]);
|
||
foreach ($sth->fetchAll(PDO::FETCH_COLUMN) as $id) $queue[] = ['asset_disposal', (int)$id];
|
||
|
||
$posted = 0;
|
||
foreach ($queue as [$source_type, $id]) {
|
||
$built = $post_asset_gl($source_type, $id);
|
||
if ($built) {
|
||
$posted++;
|
||
ok("Posted {$built['reference']} — {$built['description']}");
|
||
}
|
||
}
|
||
if (!$posted) skip("Asset GL entries already posted");
|
||
info("This month's runs are left unposted — post them from Accounting > Batch GL Entries.");
|
||
|
||
echo "\n\033[1m=== Done ===\033[0m\n\n";
|