added option to import payments from csv file
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
<?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::table('payments', function (Blueprint $table): void {
|
||||
// Add new decimal column next to amount_cents temporarily for safe backfill
|
||||
if (! Schema::hasColumn('payments', 'amount')) {
|
||||
$table->decimal('amount', 20, 4)->nullable()->after('account_id');
|
||||
}
|
||||
});
|
||||
|
||||
// Backfill amount from amount_cents if present
|
||||
if (Schema::hasColumn('payments', 'amount_cents')) {
|
||||
DB::statement('UPDATE payments SET amount = CASE WHEN amount_cents IS NOT NULL THEN amount_cents / 100.0 ELSE amount END');
|
||||
}
|
||||
|
||||
// Make amount non-null if desired; keeping nullable to avoid breaking existing rows
|
||||
// Drop amount_cents column
|
||||
Schema::table('payments', function (Blueprint $table): void {
|
||||
if (Schema::hasColumn('payments', 'amount_cents')) {
|
||||
$table->dropColumn('amount_cents');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Recreate amount_cents and backfill from amount, then drop amount
|
||||
Schema::table('payments', function (Blueprint $table): void {
|
||||
if (! Schema::hasColumn('payments', 'amount_cents')) {
|
||||
$table->integer('amount_cents')->nullable()->after('account_id');
|
||||
}
|
||||
});
|
||||
|
||||
if (Schema::hasColumn('payments', 'amount')) {
|
||||
DB::statement('UPDATE payments SET amount_cents = CASE WHEN amount IS NOT NULL THEN ROUND(amount * 100) ELSE amount_cents END');
|
||||
}
|
||||
|
||||
Schema::table('payments', function (Blueprint $table): void {
|
||||
if (Schema::hasColumn('payments', 'amount')) {
|
||||
$table->dropColumn('amount');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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('payments', function (Blueprint $table): void {
|
||||
if (! Schema::hasColumn('payments', 'balance_before')) {
|
||||
$table->decimal('balance_before', 20, 4)->nullable()->after('amount');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('payments', function (Blueprint $table): void {
|
||||
if (Schema::hasColumn('payments', 'balance_before')) {
|
||||
$table->dropColumn('balance_before');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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('payments', function (Blueprint $table): void {
|
||||
if (! $this->hasUniqueIndex('payments', 'payments_account_id_reference_unique')) {
|
||||
$table->unique(['account_id', 'reference'], 'payments_account_id_reference_unique');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('payments', function (Blueprint $table): void {
|
||||
if ($this->hasUniqueIndex('payments', 'payments_account_id_reference_unique')) {
|
||||
$table->dropUnique('payments_account_id_reference_unique');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function hasUniqueIndex(string $table, string $indexName): bool
|
||||
{
|
||||
// Works for both SQLite and others by checking existing indexes from connection schema manager when available.
|
||||
try {
|
||||
$connection = Schema::getConnection();
|
||||
$schemaManager = $connection->getDoctrineSchemaManager();
|
||||
$indexes = $schemaManager->listTableIndexes($table);
|
||||
|
||||
return array_key_exists($indexName, $indexes);
|
||||
} catch (\Throwable $e) {
|
||||
// Fallback: attempt dropping/creating blindly in migration operations
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$driver = DB::getDriverName();
|
||||
if ($driver === 'pgsql') {
|
||||
// Drop constraint if exists (covers both constraint-created and index-created uniqueness)
|
||||
try {
|
||||
DB::statement('ALTER TABLE payments DROP CONSTRAINT IF EXISTS payments_account_id_reference_unique');
|
||||
} catch (\Throwable $e) {
|
||||
// ignore
|
||||
}
|
||||
// Drop index if exists
|
||||
try {
|
||||
DB::statement('DROP INDEX IF EXISTS payments_account_id_reference_unique');
|
||||
} catch (\Throwable $e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// Create a partial unique index that ignores soft-deleted rows, use a new name
|
||||
DB::statement('CREATE UNIQUE INDEX IF NOT EXISTS uniq_payments_account_reference_not_deleted ON payments (account_id, reference) WHERE deleted_at IS NULL');
|
||||
} else {
|
||||
// Fallback for other drivers: ensure a regular unique index exists (no partial support)
|
||||
Schema::table('payments', function ($table): void {
|
||||
try {
|
||||
$table->unique(['account_id', 'reference'], 'payments_account_id_reference_unique');
|
||||
} catch (\Throwable $e) {
|
||||
// ignore if already exists
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$driver = DB::getDriverName();
|
||||
if ($driver === 'pgsql') {
|
||||
DB::statement('DROP INDEX IF EXISTS uniq_payments_account_reference_not_deleted');
|
||||
// Recreate a standard unique index (non-partial) with the original name
|
||||
DB::statement('CREATE UNIQUE INDEX IF NOT EXISTS payments_account_id_reference_unique ON payments (account_id, reference)');
|
||||
} else {
|
||||
// For other drivers, nothing special beyond ensuring the standard index exists
|
||||
Schema::table('payments', function ($table): void {
|
||||
try {
|
||||
$table->unique(['account_id', 'reference'], 'payments_account_id_reference_unique');
|
||||
} catch (\Throwable $e) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -37,11 +37,25 @@ public function run(): void
|
||||
'key' => 'person_addresses',
|
||||
'canonical_root' => 'address',
|
||||
'label' => 'Person Addresses',
|
||||
'fields' => ['address', 'country', 'type_id', 'description'],
|
||||
'aliases' => ['address', 'person_addresses'],
|
||||
'fields' => ['address', 'city', 'postal_code', 'country', 'type_id', 'description'],
|
||||
'field_aliases' => [
|
||||
'ulica' => 'address',
|
||||
'naslov' => 'address',
|
||||
'mesto' => 'city',
|
||||
'posta' => 'postal_code',
|
||||
'pošta' => 'postal_code',
|
||||
'zip' => 'postal_code',
|
||||
'drzava' => 'country',
|
||||
'država' => 'country',
|
||||
'opis' => 'description',
|
||||
],
|
||||
'aliases' => ['person_addresses', 'address', 'addresses'],
|
||||
'rules' => [
|
||||
['pattern' => '/^(naslov|ulica|address)\b/i', 'field' => 'address'],
|
||||
['pattern' => '/^(mesto|city|kraj)\b/i', 'field' => 'city'],
|
||||
['pattern' => '/^(posta|pošta|zip|postal)\b/i', 'field' => 'postal_code'],
|
||||
['pattern' => '/^(drzava|država|country)\b/i', 'field' => 'country'],
|
||||
['pattern' => '/^(komentar|opis|opomba|comment|description|note)\b/i', 'field' => 'description'],
|
||||
],
|
||||
'ui' => ['order' => 2],
|
||||
],
|
||||
@@ -106,6 +120,41 @@ public function run(): void
|
||||
],
|
||||
'ui' => ['order' => 7],
|
||||
],
|
||||
[
|
||||
'key' => 'payments',
|
||||
'canonical_root' => 'payment',
|
||||
'label' => 'Payments',
|
||||
// include common fields and helpful references for mapping
|
||||
'fields' => [
|
||||
'reference',
|
||||
'payment_nu',
|
||||
'payment_date',
|
||||
'amount',
|
||||
'type_id',
|
||||
'active',
|
||||
// optional helpers for mapping by related records
|
||||
'debt_id',
|
||||
'account_id',
|
||||
'account_reference',
|
||||
'contract_reference',
|
||||
],
|
||||
'field_aliases' => [
|
||||
'date' => 'payment_date',
|
||||
'datum' => 'payment_date',
|
||||
'paid_at' => 'payment_date',
|
||||
'number' => 'payment_nu',
|
||||
'znesek' => 'amount',
|
||||
'value' => 'amount',
|
||||
],
|
||||
'aliases' => ['payment', 'payments', 'placila', 'plačila'],
|
||||
'rules' => [
|
||||
['pattern' => '/^(sklic|reference|ref)\b/i', 'field' => 'reference'],
|
||||
['pattern' => '/^(stevilka|številka|number|payment\s*no\.?|payment\s*nu)\b/i', 'field' => 'payment_nu'],
|
||||
['pattern' => '/^(datum|date|paid\s*at|payment\s*date)\b/i', 'field' => 'payment_date'],
|
||||
['pattern' => '/^(znesek|amount|vplacilo|vplačilo|placilo|plačilo)\b/i', 'field' => 'amount'],
|
||||
],
|
||||
'ui' => ['order' => 8],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($defs as $d) {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\ImportTemplate;
|
||||
use App\Models\ImportTemplateMapping;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PaymentsImportTemplateSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$template = ImportTemplate::query()->firstOrCreate([
|
||||
'name' => 'Payments CSV (reference)',
|
||||
], [
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'description' => 'Payments import by contract reference: contract.reference lookup + payment fields',
|
||||
'source_type' => 'csv',
|
||||
'default_record_type' => 'payments',
|
||||
'sample_headers' => [
|
||||
'Pogodba sklic',
|
||||
'Številka plačila',
|
||||
'Datum',
|
||||
'Znesek',
|
||||
'Sklic plačila',
|
||||
],
|
||||
'is_active' => true,
|
||||
'meta' => [
|
||||
'delimiter' => ',',
|
||||
'enclosure' => '"',
|
||||
'escape' => '\\',
|
||||
'payments_import' => true,
|
||||
'contract_key_mode' => 'reference',
|
||||
],
|
||||
]);
|
||||
|
||||
$mappings = [
|
||||
[
|
||||
'entity' => 'contracts',
|
||||
'source_column' => 'Pogodba sklic',
|
||||
'target_field' => 'contract.reference',
|
||||
'transform' => 'trim',
|
||||
'position' => 1,
|
||||
],
|
||||
[
|
||||
'entity' => 'payments',
|
||||
'source_column' => 'Številka plačila',
|
||||
'target_field' => 'payment.payment_nu',
|
||||
'transform' => 'trim',
|
||||
'position' => 2,
|
||||
],
|
||||
[
|
||||
'entity' => 'payments',
|
||||
'source_column' => 'Datum',
|
||||
'target_field' => 'payment.payment_date',
|
||||
'transform' => null,
|
||||
'position' => 3,
|
||||
],
|
||||
[
|
||||
'entity' => 'payments',
|
||||
'source_column' => 'Znesek',
|
||||
'target_field' => 'payment.amount',
|
||||
'transform' => 'decimal',
|
||||
'position' => 4,
|
||||
],
|
||||
[
|
||||
'entity' => 'payments',
|
||||
'source_column' => 'Sklic plačila',
|
||||
'target_field' => 'payment.reference',
|
||||
'transform' => 'trim',
|
||||
'position' => 5,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($mappings as $map) {
|
||||
ImportTemplateMapping::firstOrCreate([
|
||||
'import_template_id' => $template->id,
|
||||
'source_column' => $map['source_column'],
|
||||
], [
|
||||
'entity' => $map['entity'],
|
||||
'target_field' => $map['target_field'],
|
||||
'transform' => $map['transform'],
|
||||
'position' => $map['position'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user