E-Invoice - Project 27: Import Tax Entity, Customers Data Report

This commit is contained in:
Dillon Ngo
2025-07-20 18:58:53 +08:00
parent 494efd7c6d
commit 2bdc0e1828
8 changed files with 6190 additions and 45 deletions
+67
View File
@@ -87,4 +87,71 @@ class Helper
return strtoupper('ringgit ' . $ringgitWords . ' and ' . $centsWords . ' cents only'); return strtoupper('ringgit ' . $ringgitWords . ' and ' . $centsWords . ' cents only');
} }
public static function getStateCodeByName($name)
{
$path = resource_path('data/lhdn/StateCodes.json');
if (!file_exists($path)) {
return null;
}
$json = file_get_contents($path);
$data = json_decode($json, true);
if (!is_array($data)) {
return null;
}
$name = strtolower(trim($name));
// Step 1: Try exact match
foreach ($data as $item) {
if (
isset($item['State']) &&
strtolower(trim($item['State'])) === $name
) {
return $item['Code'] ?? null;
}
}
// Step 2: Try partial match
foreach ($data as $item) {
if (
isset($item['State']) &&
str_contains(strtolower($item['State']), $name)
) {
return $item['Code'] ?? null;
}
}
return null;
}
public static function getMsicDescriptionByCode($code)
{
$path = resource_path('data/lhdn/MSICSubCategoryCodes.json');
if (!file_exists($path)) {
return null;
}
$json = file_get_contents($path);
$data = json_decode($json, true);
if (!is_array($data)) {
return null;
}
foreach ($data as $item) {
if (
isset($item['Code']) &&
$item['Code'] === $code
) {
return $item['Description'] ?? null;
}
}
return null;
}
} }
@@ -0,0 +1,98 @@
<?php
namespace App\Classes\Modules\Exports\Services;
use App\Classes\General\Helper;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Models\Company;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class ExportsCompanies implements FromQuery, WithHeadings, WithHeadingRow, WithMapping, ShouldAutoSize
{
use Exportable;
protected $startDate;
protected $endDate;
public function __construct($startDate = null, $endDate = null) {
$this->startDate = $startDate ? Carbon::parse($startDate)->startOfDay() : Carbon::now()->subMonths(1);
$this->endDate = $endDate ? Carbon::parse($endDate)->endOfDay() : Carbon::now();
}
public function headings(): array
{
return [
'TIN',
'IdentityNo',
'Name',
'IdentityType',
'TaxClassification',
'MSICCode',
'BusinessActivityDesc',
'DebtorCode',
'TradeName',
'Address',
'PostCode',
'Phone',
'EmailAddress',
'City',
'CountryCode',
'StateCode'
];
}
/**
* @return \Illuminate\Support\Collection|mixed
*/
public function query()
{
return Company::whereBetween('created_at', [$this->startDate, $this->endDate]);
}
/**
* @param Company $company
*
* @return array
*/
public function map($company): array
{
$employee = $company->employees()->first();
$lastBooking = $company->bookings()->orderByDesc('id')->first();
$identityNo = $company->documents->where('document_type', DocumentType::IDENTITY_CARD)->first();
$identityType = DocumentType::IDENTITY_CARD;
if(!$identityNo){
$identityNo = $company->documents->where('document_type', DocumentType::SSM_REGISTRATION)->first();
$identityType = DocumentType::SSM_REGISTRATION;
}
$address = $company->addresses()->where('e_invoice', '=', true)->latest()->first();
if(!$address){
$address = $company->addresses()->where('billing', '=', true)->first();
}
return [
$company->tin, // 'TIN',
$identityNo ? $identityNo->reference: '', // 'IdentityNo',
$employee ? $employee->name : '', // 'Name',
$identityType === DocumentType::IDENTITY_CARD ? 'MyKad' : '', // 'IdentityType',
$company->type !== null ? (string) $company->type : '0', // 'TaxClassification',
$company->msic_code, // 'MSICCode',
$company->msic_code ? Helper::getMsicDescriptionByCode($company->msic_code) : '', // 'BusinessActivityDesc',
$company->debtor, // 'DebtorCode',
$company->name, // 'TradeName',
$address ? $address->street_one . ',' . $address->street_two : '',// 'Address',
$address ? $address->postcode : '', // 'PostCode',
$company->contacts()->first() ? $company->contacts()->first()->phone : '', // 'Phone',
$employee ? $employee->email : '', // 'EmailAddress',
$address ? $address->district()->first()->name : '', // 'City',
'MYS', // 'CountryCode',
$address ? Helper::getStateCodeByName($address->state()->first()->name) : '', // 'StateCode'
];
}
}
@@ -21,11 +21,12 @@ class ExportsSalesInvoiceReport implements FromQuery, WithHeadings, WithHeadingR
{ {
use Exportable; use Exportable;
private $request; protected $startDate;
protected $endDate;
public function __construct(Request $request) public function __construct($startDate = null, $endDate = null) {
{ $this->startDate = $startDate ? Carbon::parse($startDate)->startOfDay() : Carbon::now()->subMonths(1);
$this->request = $request; $this->endDate = $endDate ? Carbon::parse($endDate)->endOfDay() : Carbon::now();
} }
public function headings(): array public function headings(): array
@@ -53,25 +54,6 @@ class ExportsSalesInvoiceReport implements FromQuery, WithHeadings, WithHeadingR
*/ */
public function query() public function query()
{ {
$start_date = $this->request->input('startDate', null);
if ($start_date) {
$start_date = Carbon::parse($this->request->input('startDate'))->format('Y-m-d');
}
$end_date = $this->request->input('endDate', null);
if ($end_date) {
$end_date = Carbon::parse($this->request->input('endDate'))->format('Y-m-d');
}
if (!$start_date) {
$start_date = Carbon::now()->subMonths(1)->startOfDay();
}
if (!$end_date) {
$end_date = Carbon::now()->endOfDay();
}
$query = Booking::where('status', ApprovalStatus::COMPLETED);
// $query = Transaction::query(); // $query = Transaction::query();
// $query->where('type', TransactionType::PAYMENT)->where('payment_method', '!=', PaymentMethodType::WALLET); // $query->where('type', TransactionType::PAYMENT)->where('payment_method', '!=', PaymentMethodType::WALLET);
// $query->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); // $query->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
@@ -79,20 +61,7 @@ class ExportsSalesInvoiceReport implements FromQuery, WithHeadings, WithHeadingR
// $query->where('type', TransactionType::PURCHASE_ORDER)->complete(); // $query->where('type', TransactionType::PURCHASE_ORDER)->complete();
// }); // });
if($start_date && $end_date) { return Booking::where('status', ApprovalStatus::COMPLETED)->whereBetween('created_at', [$this->startDate, $this->endDate]);
$query->whereBetween('created_at', [
Carbon::parse($start_date)->format('Y-m-d 0:00:00'),
Carbon::parse($end_date)->format('Y-m-d 23:59:59')
]);
}
elseif($start_date && !$end_date) {
$query->where('created_at', '>=', Carbon::parse($start_date)->format('Y-m-d 0:00:00'));
}
elseif(!$start_date && $end_date) {
$query->where('created_at', '<=', Carbon::parse($end_date)->format('Y-m-d 23:59:59'));
}
return $query;
} }
/** /**
@@ -101,11 +70,15 @@ class ExportsSalesInvoiceReport implements FromQuery, WithHeadings, WithHeadingR
*/ */
public function map($booking): array public function map($booking): array
{ {
// $transaction = $booking->transactions()->payments()->complete()->first(); $records = [];
$purchaseOrder = $booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first(); $purchaseOrder = $booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first();
$company = $booking->company()->first(); $company = $booking->company()->first();
$lastPaymentTransaction = $booking->transactions()->where('type', TransactionType::PAYMENT)->whereIn('status', [ApprovalStatus::COMPLETED, ApprovalStatus::APPROVED])->latest()->first(); $lastPaymentTransaction = $booking->transactions()->where('type', TransactionType::PAYMENT)->whereIn('status', [ApprovalStatus::COMPLETED, ApprovalStatus::APPROVED])->latest()->first();
if(!$lastPaymentTransaction){
return $records;
}
$documentDate = $lastPaymentTransaction->created_at; $documentDate = $lastPaymentTransaction->created_at;
if(Carbon::parse($booking->updated_at)->isAfter($lastPaymentTransaction->created_at)){ if(Carbon::parse($booking->updated_at)->isAfter($lastPaymentTransaction->created_at)){
$documentDate = $booking->updated_at; $documentDate = $booking->updated_at;
@@ -113,11 +86,7 @@ class ExportsSalesInvoiceReport implements FromQuery, WithHeadings, WithHeadingR
$formattedDocumentDate = Carbon::parse($documentDate)->format('m/d/Y'); $formattedDocumentDate = Carbon::parse($documentDate)->format('m/d/Y');
$firstItem = true; $firstItem = true;
$records = [];
$transactionDetails = $purchaseOrder->transactionDetails; $transactionDetails = $purchaseOrder->transactionDetails;
foreach ($transactionDetails as $detail) { foreach ($transactionDetails as $detail) {
$records[] = [ $records[] = [
$firstItem ? '<<New>>' : '', $firstItem ? '<<New>>' : '',
@@ -8,11 +8,35 @@ use Illuminate\Http\Request;
use Maatwebsite\Excel\Excel; use Maatwebsite\Excel\Excel;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use App\Classes\General\AWSS3Helper; use App\Classes\General\AWSS3Helper;
use App\Classes\Modules\Exports\Services\ExportsCompanies;
use Carbon\Carbon;
class ExportController class ExportController
{ {
public function salesInvoices(Request $request){ public function salesInvoices(Request $request){
$exportsTransactions = new ExportsSalesInvoiceReport($request); $validated = $request->validate([
'startDate' => 'nullable|date_format:d-m-Y',
'endDate' => 'nullable|date_format:d-m-Y|after_or_equal:startDate',
]);
$startDate = null;
$endDate = null;
if (isset($validated['startDate']) && $validated['startDate']) {
$startDate = Carbon::createFromFormat('d-m-Y', $validated['startDate'])->startOfDay();
} else {
$startDate = Carbon::now()->subMonths(1)->startOfDay();
}
if (isset($validated['endDate']) && $validated['endDate']) {
$endDate = Carbon::createFromFormat('d-m-Y', $validated['endDate'])->endOfDay();
} else {
$endDate = Carbon::now()->endOfDay();
}
$exportsTransactions = new ExportsSalesInvoiceReport($startDate, $endDate);
$exportFileName = 'Exchange - Sales Invoice Report.xls'; $exportFileName = 'Exchange - Sales Invoice Report.xls';
$filesystemDriver = Storage::getDefaultDriver(); $filesystemDriver = Storage::getDefaultDriver();
if($filesystemDriver === 's3'){ if($filesystemDriver === 's3'){
@@ -24,4 +48,39 @@ class ExportController
} }
return $response; return $response;
} }
public function companies(Request $request){
$validated = $request->validate([
'startDate' => 'nullable|date_format:d-m-Y',
'endDate' => 'nullable|date_format:d-m-Y|after_or_equal:startDate',
]);
$startDate = null;
$endDate = null;
if (isset($validated['startDate']) && $validated['startDate']) {
$startDate = Carbon::createFromFormat('d-m-Y', $validated['startDate'])->startOfDay();
} else {
$startDate = Carbon::now()->subMonths(1)->startOfDay();
}
if (isset($validated['endDate']) && $validated['endDate']) {
$endDate = Carbon::createFromFormat('d-m-Y', $validated['endDate'])->endOfDay();
} else {
$endDate = Carbon::now()->endOfDay();
}
$exportsCompanies = new ExportsCompanies($startDate, $endDate);
$exportFileName = 'Exchange - Customers Data Report.xls';
$filesystemDriver = Storage::getDefaultDriver();
if($filesystemDriver === 's3'){
return response([ 'src' => AWSS3Helper::S3Exportable($exportFileName, $exportsCompanies) ]);
}
else{
$response = $exportsCompanies->download($exportFileName, Excel::XLS, ['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']);
ob_end_clean();
}
return $response;
}
} }
@@ -82,6 +82,7 @@ export default {
options() { options() {
return [ return [
'Sales Invoice Report', 'Sales Invoice Report',
'Customers Report',
]; ];
}, },
handleClick(){ handleClick(){
@@ -90,7 +91,8 @@ export default {
const reportType = this.parameters.reportType; const reportType = this.parameters.reportType;
const routesMap = { const routesMap = {
'Sales Invoice Report': route('api.export.transaction.sales-invoices'), 'Sales Invoice Report': route('api.export.transactions.sales-invoices'),
'Customers Report': route('api.export.companies.customers-data'),
}; };
let url = `${routesMap[reportType]}?startDate=${this.parameters.startDate}&endDate=${this.parameters.endDate}`; let url = `${routesMap[reportType]}?startDate=${this.parameters.startDate}&endDate=${this.parameters.endDate}`;
File diff suppressed because it is too large Load Diff
+70
View File
@@ -0,0 +1,70 @@
[
{
"Code": "01",
"State": "Johor"
},
{
"Code": "02",
"State": "Kedah"
},
{
"Code": "03",
"State": "Kelantan"
},
{
"Code": "04",
"State": "Melaka"
},
{
"Code": "05",
"State": "Negeri Sembilan"
},
{
"Code": "06",
"State": "Pahang"
},
{
"Code": "07",
"State": "Pulau Pinang"
},
{
"Code": "08",
"State": "Perak"
},
{
"Code": "09",
"State": "Perlis"
},
{
"Code": "10",
"State": "Selangor"
},
{
"Code": "11",
"State": "Terengganu"
},
{
"Code": "12",
"State": "Sabah"
},
{
"Code": "13",
"State": "Sarawak"
},
{
"Code": "14",
"State": "Wilayah Persekutuan Kuala Lumpur"
},
{
"Code": "15",
"State": "Wilayah Persekutuan Labuan"
},
{
"Code": "16",
"State": "Wilayah Persekutuan Putrajaya"
},
{
"Code": "17",
"State": "Not Applicable"
}
]
+4 -1
View File
@@ -5,7 +5,10 @@ use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'export', 'as' => 'export.', 'namespace' => 'Exports'], function () { Route::group(['prefix' => 'export', 'as' => 'export.', 'namespace' => 'Exports'], function () {
Route::group(['prefix' => 'transactions', 'as' => 'transaction.'], function () { Route::group(['prefix' => 'transactions', 'as' => 'transactions.'], function () {
Route::get('/sales-invoices', [ExportController::class, 'salesInvoices'])->name('sales-invoices'); Route::get('/sales-invoices', [ExportController::class, 'salesInvoices'])->name('sales-invoices');
}); });
Route::group(['prefix' => 'companies', 'as' => 'companies.'], function () {
Route::get('/customers-data', [ExportController::class, 'companies'])->name('customers-data');
});
}); });