Skip to content

Commit 9a14806

Browse files
author
Itamar Junior
committed
Implement CRO integration for company details: add refresh functionality, job for scheduled updates, and enhance date handling in CompanyEdit component.
1 parent b4a6198 commit 9a14806

File tree

4 files changed

+274
-11
lines changed

4 files changed

+274
-11
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
namespace App\Jobs;
4+
5+
use App\Models\Company;
6+
use App\Services\Core\CroSearchService;
7+
use Illuminate\Bus\Queueable;
8+
use Illuminate\Contracts\Queue\ShouldQueue;
9+
use Illuminate\Foundation\Bus\Dispatchable;
10+
use Illuminate\Queue\InteractsWithQueue;
11+
use Illuminate\Queue\SerializesModels;
12+
use Illuminate\Support\Carbon;
13+
use Illuminate\Support\Facades\Log;
14+
use Throwable;
15+
16+
class RefreshCompaniesFromCro implements ShouldQueue
17+
{
18+
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
19+
20+
/**
21+
* Create a new job instance.
22+
*
23+
* @param int $chunkSize The number of companies processed per chunk.
24+
*/
25+
public function __construct(protected int $chunkSize = 100)
26+
{
27+
}
28+
29+
/**
30+
* Execute the job.
31+
*/
32+
public function handle(CroSearchService $cro): void
33+
{
34+
Company::query()
35+
->whereNotNull('company_number')
36+
->orderBy('id')
37+
->chunkById($this->chunkSize, function ($companies) use ($cro) {
38+
foreach ($companies as $company) {
39+
if (!$this->isValidCompanyNumber($company->company_number)) {
40+
continue;
41+
}
42+
43+
try {
44+
$details = $this->fetchCompanyDetails($cro, $company->company_number);
45+
46+
if ($details === null) {
47+
continue;
48+
}
49+
50+
$company->update($this->mapDetailsToAttributes($details));
51+
} catch (Throwable $e) {
52+
Log::warning('Failed refreshing company from CRO', [
53+
'company_id' => $company->id,
54+
'company_number' => $company->company_number,
55+
'message' => $e->getMessage(),
56+
]);
57+
}
58+
}
59+
});
60+
}
61+
62+
protected function fetchCompanyDetails(CroSearchService $cro, string $companyNumber): ?array
63+
{
64+
$details = $cro->getCompanyDetails($companyNumber);
65+
66+
return empty($details) ? null : $details;
67+
}
68+
69+
protected function mapDetailsToAttributes(array $details): array
70+
{
71+
return [
72+
'name' => $this->uppercase($details['company_name'] ?? null),
73+
'company_type' => $details['comp_type_desc'] ?? null,
74+
'status' => $details['company_status_desc'] ?? null,
75+
'effective_date' => $this->formatCroDate($details['company_status_date'] ?? null),
76+
'registration_date' => $this->formatCroDate($details['company_reg_date'] ?? null),
77+
'last_annual_return' => $this->formatCroDate($details['last_ar_date'] ?? null),
78+
'next_annual_return' => $this->formatCroDate($details['next_ar_date'] ?? null),
79+
'next_financial_statement_due' => $this->formatCroDate($details['next_fs_due_date'] ?? null),
80+
'last_accounts' => $this->formatCroDate($details['last_acc_date'] ?? null),
81+
'last_agm' => $this->formatCroDate($details['last_agm_date'] ?? null),
82+
'financial_year_end' => $this->formatCroDate($details['financial_year_end'] ?? null),
83+
'postcode' => $this->uppercase($details['eircode'] ?? null),
84+
'address_line_1' => $this->uppercase($details['company_addr_1'] ?? null),
85+
'address_line_2' => $this->uppercase($details['company_addr_2'] ?? null),
86+
'address_line_3' => $this->uppercase($details['company_addr_3'] ?? null),
87+
'address_line_4' => $this->uppercase($details['company_addr_4'] ?? null),
88+
'place_of_business' => $this->uppercase($details['place_of_business'] ?? null),
89+
'company_type_code' => $details['company_type_code'] ?? null,
90+
'company_status_code' => $details['company_status_code'] ?? null,
91+
];
92+
}
93+
94+
protected function formatCroDate(?string $isoDate): ?string
95+
{
96+
if (!$isoDate || $isoDate === '0001-01-01T00:00:00Z') {
97+
return null;
98+
}
99+
100+
try {
101+
return Carbon::parse($isoDate)->format('Y-m-d');
102+
} catch (Throwable) {
103+
return null;
104+
}
105+
}
106+
107+
protected function uppercase(?string $value): ?string
108+
{
109+
return $value === null ? null : strtoupper($value);
110+
}
111+
112+
protected function isValidCompanyNumber(?string $number): bool
113+
{
114+
if ($number === null) {
115+
return false;
116+
}
117+
118+
return (bool) preg_match('/^\d{5,6}$/', $number);
119+
}
120+
}

app/Livewire/Company/CompanyEdit.php

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
namespace App\Livewire\Company;
44

55
use App\Models\Company as CompanyModel;
6-
use Livewire\Component;
6+
use App\Services\Core\CroSearchService;
7+
use Illuminate\Support\Carbon;
78
use Illuminate\Support\Facades\Auth;
9+
use Livewire\Component;
10+
use Throwable;
811

912
class CompanyEdit extends Component
1013
{
@@ -15,7 +18,7 @@ class CompanyEdit extends Component
1518
public ?string $custom = null;
1619
public ?string $company_type = null;
1720
public ?string $status = null;
18-
public bool $active = true;
21+
public ?bool $active = null;
1922
public ?string $effective_date = null;
2023
public ?string $registration_date = null;
2124
public ?string $last_annual_return = null;
@@ -41,7 +44,7 @@ protected function rules(): array
4144
'custom' => 'nullable|string',
4245
'company_type' => 'nullable|string',
4346
'status' => 'nullable|string',
44-
'active' => 'boolean',
47+
'active' => 'nullable|boolean',
4548
'effective_date' => 'nullable|date',
4649
'registration_date' => 'nullable|date',
4750
'last_annual_return' => 'nullable|date',
@@ -61,6 +64,28 @@ protected function rules(): array
6164
];
6265
}
6366

67+
protected array $dateFields = [
68+
'effective_date',
69+
'registration_date',
70+
'last_annual_return',
71+
'next_annual_return',
72+
'next_financial_statement_due',
73+
'last_accounts',
74+
'last_agm',
75+
'financial_year_end',
76+
];
77+
78+
protected array $croDateFieldMap = [
79+
'effective_date' => 'company_status_date',
80+
'registration_date' => 'company_reg_date',
81+
'last_annual_return' => 'last_ar_date',
82+
'next_annual_return' => 'next_ar_date',
83+
'next_financial_statement_due' => 'next_fs_due_date',
84+
'last_accounts' => 'last_acc_date',
85+
'last_agm' => 'last_agm_date',
86+
'financial_year_end' => 'financial_year_end',
87+
];
88+
6489
public function mount(CompanyModel $company)
6590
{
6691
if ($company->business_id !== Auth::user()->current_business_id) {
@@ -92,6 +117,10 @@ public function mount(CompanyModel $company)
92117
'company_type_code',
93118
'company_status_code',
94119
]));
120+
121+
foreach ($this->dateFields as $field) {
122+
$this->{$field} = $this->formatDateForInput($company->{$field});
123+
}
95124
}
96125

97126
public function save()
@@ -105,14 +134,14 @@ public function save()
105134
'company_type' => $this->company_type,
106135
'status' => $this->status,
107136
'active' => $this->active,
108-
'effective_date' => $this->effective_date,
109-
'registration_date' => $this->registration_date,
110-
'last_annual_return' => $this->last_annual_return,
111-
'next_annual_return' => $this->next_annual_return,
112-
'next_financial_statement_due' => $this->next_financial_statement_due,
113-
'last_accounts' => $this->last_accounts,
114-
'last_agm' => $this->last_agm,
115-
'financial_year_end' => $this->financial_year_end,
137+
'effective_date' => $this->formatDateForPersistence($this->effective_date),
138+
'registration_date' => $this->formatDateForPersistence($this->registration_date),
139+
'last_annual_return' => $this->formatDateForPersistence($this->last_annual_return),
140+
'next_annual_return' => $this->formatDateForPersistence($this->next_annual_return),
141+
'next_financial_statement_due' => $this->formatDateForPersistence($this->next_financial_statement_due),
142+
'last_accounts' => $this->formatDateForPersistence($this->last_accounts),
143+
'last_agm' => $this->formatDateForPersistence($this->last_agm),
144+
'financial_year_end' => $this->formatDateForPersistence($this->financial_year_end),
116145
'postcode' => $this->postcode,
117146
'address_line_1' => $this->address_line_1,
118147
'address_line_2' => $this->address_line_2,
@@ -129,8 +158,100 @@ public function save()
129158
redirect()->route('company.manage');
130159
}
131160

161+
public function refreshFromCro(): void
162+
{
163+
if (!preg_match('/^\d{5,6}$/', $this->company_number)) {
164+
session()->flash('error', 'Enter a valid company number before refreshing from the CRO.');
165+
return;
166+
}
167+
168+
try {
169+
/** @var CroSearchService $cro */
170+
$cro = app(CroSearchService::class);
171+
172+
$matches = $cro->searchByNumber($this->company_number);
173+
174+
if (count($matches) === 0) {
175+
session()->flash('error', 'No CRO records found for that company number.');
176+
return;
177+
}
178+
179+
$details = $cro->getCompanyDetails($this->company_number);
180+
181+
if (empty($details)) {
182+
session()->flash('error', 'CRO returned an empty response for that company number.');
183+
return;
184+
}
185+
186+
$this->applyCroDetails($details);
187+
188+
session()->flash('success', 'Company details refreshed from the CRO. Review and save to persist the changes.');
189+
} catch (Throwable $e) {
190+
report($e);
191+
session()->flash('error', 'CRO API error: ' . $e->getMessage());
192+
}
193+
}
194+
132195
public function render()
133196
{
134197
return view('livewire.company.company-edit');
135198
}
199+
200+
protected function formatDateForInput($value): ?string
201+
{
202+
if (!$value) {
203+
return null;
204+
}
205+
206+
return Carbon::parse($value)->format('Y-m-d');
207+
}
208+
209+
protected function formatDateForPersistence(?string $value): ?string
210+
{
211+
return $value ?: null;
212+
}
213+
214+
protected function applyCroDetails(array $details): void
215+
{
216+
$name = $this->uppercase($details['company_name'] ?? null);
217+
218+
if ($name !== null) {
219+
$this->name = $name;
220+
}
221+
222+
$this->company_type = $details['comp_type_desc'] ?? null;
223+
$this->status = $details['company_status_desc'] ?? null;
224+
225+
foreach ($this->croDateFieldMap as $property => $sourceKey) {
226+
$this->{$property} = $this->formatCroDate($details[$sourceKey] ?? null);
227+
}
228+
229+
$this->postcode = $this->uppercase($details['eircode'] ?? null);
230+
$this->address_line_1 = $this->uppercase($details['company_addr_1'] ?? null);
231+
$this->address_line_2 = $this->uppercase($details['company_addr_2'] ?? null);
232+
$this->address_line_3 = $this->uppercase($details['company_addr_3'] ?? null);
233+
$this->address_line_4 = $this->uppercase($details['company_addr_4'] ?? null);
234+
$this->place_of_business = $this->uppercase($details['place_of_business'] ?? null);
235+
236+
$this->company_type_code = $details['company_type_code'] ?? null;
237+
$this->company_status_code = $details['company_status_code'] ?? null;
238+
}
239+
240+
protected function formatCroDate(?string $isoDate): ?string
241+
{
242+
if (!$isoDate || $isoDate === '0001-01-01T00:00:00Z') {
243+
return null;
244+
}
245+
246+
try {
247+
return Carbon::parse($isoDate)->format('Y-m-d');
248+
} catch (Throwable) {
249+
return null;
250+
}
251+
}
252+
253+
protected function uppercase(?string $value): ?string
254+
{
255+
return $value === null ? null : strtoupper($value);
256+
}
136257
}

bootstrap/app.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
use App\Jobs\RefreshCompaniesFromCro;
4+
use Illuminate\Console\Scheduling\Schedule;
35
use Illuminate\Foundation\Application;
46
use Illuminate\Foundation\Configuration\Exceptions;
57
use Illuminate\Foundation\Configuration\Middleware;
@@ -13,6 +15,9 @@
1315
->withMiddleware(function (Middleware $middleware) {
1416
//
1517
})
18+
->withSchedule(function (Schedule $schedule) {
19+
$schedule->job(new RefreshCompaniesFromCro())->dailyAt('00:00');
20+
})
1621
->withExceptions(function (Exceptions $exceptions) {
1722
//
1823
})->create();

resources/views/livewire/company/company-edit.blade.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,25 @@
99
</x-ui.flash-message>
1010
</div>
1111
@endif
12+
@if (session('error'))
13+
<div class="mb-4">
14+
<x-ui.flash-message type="error" title="Error">
15+
{{ session('error') }}
16+
</x-ui.flash-message>
17+
</div>
18+
@endif
1219

1320
<form wire:submit.prevent="save" class="my-6 w-full space-y-6">
21+
<div class="flex flex-wrap items-center gap-4">
22+
<flux:button type="button" variant="outline" wire:click="refreshFromCro"
23+
wire:loading.attr="disabled" wire:target="refreshFromCro">
24+
<span wire:loading.remove wire:target="refreshFromCro">{{ __('Refresh From CRO') }}</span>
25+
<span wire:loading wire:target="refreshFromCro">{{ __('Refreshing...') }}</span>
26+
</flux:button>
27+
<p class="text-sm text-gray-500">
28+
{{ __('Pulls the latest details from the CRO into this form. Save afterwards to persist the changes.') }}
29+
</p>
30+
</div>
1431
<flux:input wire:model="company_number" :label="__('Company Number')" required />
1532
<flux:input wire:model="name" :label="__('Name')" required />
1633
<flux:input wire:model="custom" :label="__('Custom')" />

0 commit comments

Comments
 (0)