Merge branch 'dillon/90-e-invoice-shipping-portal-f' into dillon/90-e-invoice-shipping-portal-e-1

This commit is contained in:
Dillon Ngo
2025-09-18 12:46:40 +08:00
20 changed files with 1040 additions and 57 deletions
@@ -0,0 +1,133 @@
<?php
namespace App\Classes\Jobs\Commands\V2;
use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject;
use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair;
use App\Classes\Modules\Accounts\Services\UpdatesKeyValuePair;
use App\Classes\ValueObjects\Constants\KVPKey;
use App\Classes\ValueObjects\Constants\TransactionType;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Order;
use Illuminate\Support\Facades\Log;
class ProcessSalesInvoiceReportV2CommandJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** @var array */
private $details;
/**
* ProcessSalesInvoiceReportV2CommandJob constructor.
* @param array $details
*/
public function __construct(array $details)
{
$this->details = $details;
}
public function handle()
{
Log::info(Carbon::now() . ': Start job - Processing single record for E-Invoice from Sales Invoice Report Import.');
$start = new Carbon();
$docNo = $this->details['docno'] ?? null;
$remark1 = $this->details['remark1'] ?? null;
$remark2 = $this->details['remark2'] ?? null;
$docDate = $this->details['docdate'] ?? null;
$debtorCode = $this->details['debtorcode'] ?? null;
$ref = $this->details['ref'] ?? null;
$shipInfo = $this->details['shipinfo'] ?? null;
$accNo = $this->details['accno'] ?? null;
$detailDescription = $this->details['detaildescription'] ?? null;
$furtherDescription = $this->details['furtherdescription'] ?? null;
$classification = $this->details['classification'] ?? null;
$projNo = $this->details['projno'] ?? null;
$deptNo = $this->details['deptno'] ?? null;
$qty = $this->details['qty'] ?? null;
$unitPrice = $this->details['unitprice'] ?? null;
$taxCode = $this->details['taxcode'] ?? null;
$taxableAmt = $this->details['taxableamt'] ?? null;
$taxRate = $this->details['taxrate'] ?? null;
$submitEinvoice = $this->details['submiteinvoice'] ?? null;
$consolidatedEinvoice = $this->details['consolidatedeinvoice'] ?? null;
$eInvoiceValidationLink = $this->details['einvoicevalidationlink'] ?? null;
// Log for debugging
Log::info("Processing Sales Invoice Report:", [
'DocNo' => $docNo,
'Remark1' => $remark1,
'Remark2' => $remark2,
'DocDate' => $docDate,
'DebtorCode' => $debtorCode,
'Ref' => $ref,
'ShipInfo' => $shipInfo,
'AccNo' => $accNo,
'DetailDescription' => $detailDescription,
'FurtherDescription' => $furtherDescription,
'Classification' => $classification,
'ProjNo' => $projNo,
'DeptNo' => $deptNo,
'Qty' => $qty,
'UnitPrice' => $unitPrice,
'TaxCode' => $taxCode,
'TaxableAmt' => $taxableAmt,
'TaxRate' => $taxRate,
'SubmitEinvoice' => $submitEinvoice,
'ConsolidatedEinvoice' => $consolidatedEinvoice,
'EInvoiceValidationLink' => $eInvoiceValidationLink,
]);
$order = Order::where('reference', $ref)->first();
$transactions = $order->transactions()->whereIn('transactions.type', [TransactionType::SHIPPING_INVOICE, TransactionType::STORAGE_INVOICE])->get();
$matchedTransaction = null;
foreach ($transactions as $transaction) {
$transactionDetails = $transaction->transactionDetails;
foreach ($transactionDetails as $detail) {
$detailName = str_replace('<br>', ' ', $detail->name);
if (
$detailName === $furtherDescription &&
$detail->quantity == $qty &&
$detail->price == $unitPrice
) {
$matchedTransaction = $transaction;
break 2;
}
}
}
if($matchedTransaction){
if($docNo != "" && $docNo != "<<New>>"){
$this->updateOrCreateKeyValuePair($matchedTransaction, KVPKey::AUTOCOUNT_DOCNO_INVOICE, $docNo);
}
if($eInvoiceValidationLink){
$this->updateOrCreateKeyValuePair($matchedTransaction, KVPKey::AUTOCOUNT_EINVOICE_VALIDATION_LINK, $eInvoiceValidationLink);
}
}
$end = new Carbon();
$elapsedTime = $start->diff($end)->format('%H:%I:%S');
Log::info(Carbon::now() . ': End job - Processing single record for E-Invoice from Sales Invoice Report Import. ElapsedTime: ' . $elapsedTime . '.');
}
private function updateOrCreateKeyValuePair($booking, $key, $value)
{
$keyValuePairObject = new KeyValuePairObject($key, $value);
$metadata = $booking->attributesKVP()->where('key', $key)->first();
if ($metadata) {
(App()->make(UpdatesKeyValuePair::class))->execute($metadata, $keyValuePairObject);
} else {
(App()->make(CreatesKeyValuePair::class))->execute($booking, $keyValuePairObject);
}
}
}
@@ -0,0 +1,63 @@
<?php
namespace App\Classes\Jobs\Commands\V2;
use App\Classes\Modules\Transactions\Processors\CreateShippingEInvoiceDocProcessor;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Classes\ValueObjects\Constants\KVPKey;
use App\Classes\ValueObjects\Constants\TransactionType;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Transaction;
use Illuminate\Support\Facades\Log;
class ProcessTransactionForEInvoiceV2CommandJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** @var Transaction */
private $transaction;
/**
* ProcessTransactionForEInvoiceV2CommandJob constructor.
* @param Transaction $transaction
*/
public function __construct(Transaction $transaction)
{
$this->transaction = $transaction;
}
public function handle()
{
Log::info(Carbon::now() . ': Start job - Processing single transaction for E-Invoice.');
$start = new Carbon();
$isGenerated = false;
$companyModule = $this->transaction->owner->owner->companyModule;
if($companyModule->company->e_invoice === 1){
$document = $this->transaction->documents()->where('document_type', DocumentType::SHIPPING_EINVOICE)->first();
$kvp = $this->transaction->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE)->first();
if(!$document && $kvp && $this->transaction->type == TransactionType::SHIPPING_INVOICE){
(App()->make(CreateShippingEInvoiceDocProcessor::class))->execute($this->transaction);
$isGenerated = true;
}
}
if(!$isGenerated){
Log::info("NO E-Invoice generated for transaction with id: " . $this->transaction->id . ', order: ' . $this->transaction->owner->owner->reference );
}
else{
Log::info("E-Invoice generated for transaction with id: " . $this->transaction->id . ', order: ' . $this->transaction->owner->owner->reference );
}
$end = new Carbon();
$elapsedTime = $start->diff($end)->format('%H:%I:%S');
Log::info(Carbon::now() . ': End job - Processing single transaction for E-Invoice. ElapsedTime: ' . $elapsedTime . '.');
}
}
@@ -0,0 +1,186 @@
<?php
namespace App\Classes\Modules\Exports\Services;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Models\Transaction;
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;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class ExportsSalesInvoicesReport 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 [
'DocNo',
'DocDate',
'Remark1',
'Remark2',
'DebtorCode',
'Ref',
'ShipInfo',
'AccNo',
'DetailDescription',
'FurtherDescription',
'Classification',
'ProjNo',
'DeptNo',
'Qty',
'UnitPrice',
'TaxCode',
'TaxableAmt',
'TaxRate',
'SubmitEinvoice',
'ConsolidatedEInvoice',
// 'PaymentDate',
];
}
/**
* @return \Illuminate\Support\Collection|mixed
*/
public function query()
{
$start_date = $this->startDate;
$end_date = $this->endDate;
// $query = Transaction::query();
$query = Transaction::with([
'transactionDetails',
'transactions', // child transactions (e.g. PAYMENT)
'owner.containers',
'owner.owner.companyModule.company.contacts',
'owner.owner.companyModule.inviters',
'owner.owner.companyModule.employees'
]);
// paidInvoiceSection
$approvalStatus = ApprovalStatus::COMPLETED;
// Adjusted the query to include both types and status
$query->whereIn('type', [TransactionType::SHIPPING_INVOICE, TransactionType::STORAGE_INVOICE])
->where('status', $approvalStatus);
if ($start_date && $end_date) {
$query->whereHas('transactions', function($transaction) use ($start_date, $end_date) {
$transaction->where('type', TransactionType::PAYMENT)
->whereBetween('updated_at', [$start_date, $end_date])
->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
});
} elseif ($start_date) {
$query->whereHas('transactions', function($transaction) use ($start_date) {
$transaction->where('type', TransactionType::PAYMENT)
->where('updated_at', '>=', $start_date)
->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
});
} elseif ($end_date) {
$query->whereHas('transactions', function($transaction) use ($end_date) {
$transaction->where('type', TransactionType::PAYMENT)
->where('updated_at', '<=', $end_date)
->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
});
}
return $query;
}
public function map($transaction): array
{
$container = $transaction->owner->containers->first();
$order = $transaction->owner->owner;
$company = $order->companyModule->company;
$lastPaymentTransaction = $transaction->transactions
->whereIn('type', [TransactionType::PAYMENT, TransactionType::TOP_UP])
->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])
->sortByDesc('created_at')
->first();
$documentDate = Carbon::parse($lastPaymentTransaction->created_at);
if($company->e_invoice === 1){
$documentDate = $documentDate->copy()->endOfMonth();
}
$formattedDocumentDate = Carbon::parse($documentDate)->format('m/d/Y');
if($transaction->type === TransactionType::STORAGE_INVOICE){
$shippingTransactionDetails = $transaction->transactionDetails->where('reference', 'STORAGE_FEE')->first();
}
else{
$shippingTransactionDetails = $transaction->transactionDetails->where('reference', 'SHIPPING_FEE')->first();
}
$marking = $order->companyModule->inviters()->withPivot('invitee_reference')->first()->pivot->invitee_reference;
$contact = $order->companyModule->company->contacts->first();
$userName = $order->companyModule->employees->first();
$link = route('customer.payment-and-billing', $marking);
$furtherDescription = '';
foreach ($transaction->transactionDetails as $item){
if($item->reference === 'SHIPPING_FEE')
$furtherDescription .= str_replace('X1 Freight Service Charge<br>', '', $item->name)."\n";
elseif($item->reference === 'OVER_WEIGHT_CHARGES')
$furtherDescription .= $item->name.' '.$item->quantity.' CBM'."\n";
elseif($item->reference === 'MIN_CBM_CHARGES')
$furtherDescription .= $item->name.' '.$item->quantity.' CBM'."\n";
elseif($item->reference === 'STORAGE_FEE')
$furtherDescription .= str_replace('<br>', ' | ', $item->name).' '."\n";
else
$furtherDescription .= $item->name.' '.$item->quantity.' X '.$item->price."\n";
}
$transactionDetails = $transaction->transactionDetails;
$firstItem = true;
$rows = [];
foreach ($transactionDetails as $detail) {
$rows[] = [
$firstItem ? '<<New>>' : '', //DocNo
$formattedDocumentDate, //DocDate
$transaction->id, //Remark1
$transaction->bill_no, //Remark2
$company->debtor, //DebtorCode
$order->reference, //Ref
$order->reference, //ShipInfo
'500-0000', //AccNo
'SERVICE OR ITEM :', //DetailDescription
str_replace('<br>', ' ', $detail->name), //FurtherDescription
'022', //Classification
$container->reference, //ProjNo
'C ', //DeptNo
$detail->quantity, //Qty
$detail->price, //UnitPrice
floatval($detail->tax_percentage) > 0 ? 'SV-6' : '', //TaxCode
floatval($detail->tax_percentage) > 0 ? number_format($detail->amount, 2) : '0.00', //TaxableAmt
$detail->tax_percentage, //TaxRate
$firstItem ? 'T' : '', //SubmitEinvoice
$company->e_invoice === 1 ? 'F' : 'T', //ConsolidatedEInvoice
// $transaction->transactions()->where('type', TransactionType::PAYMENT)->first()->updated_at->format('m/d/Y'), //PaymentDate
];
if($firstItem) {
$firstItem = false;
}
}
return $rows;
}
}
@@ -0,0 +1,55 @@
<?php
namespace App\Classes\Modules\Imports\ControllersLogic;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\Modules\Imports\Services\AutoCountDataImport;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
class ImportExcelLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Import Excel',
'message' => 'You have successfully imported data from excel file'
];
}
/**
* @param Request $request
* @return JsonResponse
* @throws MalformedRequestException
*/
public function logic(Request $request) : JsonResponse
{
$result = [];
$reportType = $request->input('report_type');
$object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports');
$files = $object->getFiles();
if (count($files) > 1) {
throw new MalformedRequestException('Import function can only process one file at a time.');
}
foreach ($files as $file) {
$filePath = json_decode($file)->file_info->original->file;
$import = new AutoCountDataImport($reportType);
Excel::import($import, $filePath);
}
return $this->response($result);
}
}
@@ -0,0 +1,68 @@
<?php
namespace App\Classes\Modules\Imports\Services;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithChunkReading;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Jobs\Commands\V2\ProcessSalesInvoiceReportV2CommandJob;
use Illuminate\Support\Facades\Log;
class AutoCountDataImport implements ToCollection, WithHeadingRow, WithChunkReading
{
protected $reportType;
public function __construct($reportType)
{
$this->reportType = $reportType;
}
public function headingRow(): int
{
return 1;
}
/**
* @param Collection $collection
*/
public function collection(Collection $collection)
{
static $headerProcessed = false;
foreach ($collection as $row) {
if (!$headerProcessed) {
$header = $row->keys()->map(fn($h) => strtolower(trim($h)))->toArray();
$this->validateHeader($header, $this->reportType);
$headerProcessed = true;
}
if ($this->reportType === 'Sales Invoice Report') {
ProcessSalesInvoiceReportV2CommandJob::dispatch($row->toArray());
}
else{
throw new MalformedRequestException('Cannot process report type: ' . $this->reportType);
}
}
}
public function chunkSize(): int
{
return 1000;
}
private function validateHeader(array $header, String $reportType)
{
$optionalColumn = 'einvoicevalidationlink';
$salesInvoiceHeader =
['docno', 'docdate', 'remark1', 'remark2', 'debtorcode', 'ref',
'shipinfo', 'accno', 'detaildescription', 'furtherdescription', 'classification', 'projno', 'deptno',
'qty', 'unitprice', 'taxcode', 'taxableamt', 'taxrate', 'submiteinvoice', 'consolidatedeinvoice'];
if ($reportType === 'Sales Invoice Report' &&
$header !== $salesInvoiceHeader &&
$header !== [...$salesInvoiceHeader, $optionalColumn]) {
throw new MalformedRequestException('Uploaded Excel file format is incorrect. Column headers do not match expected format.');
}
}
}
@@ -0,0 +1,84 @@
<?php
namespace App\Classes\Modules\Orders\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Jobs\Commands\V2\ProcessTransactionForEInvoiceV2CommandJob;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Transaction;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Log;
class CreateBatchProcessingAutoCountImportLogic extends AbstractControllerLogic
{
protected int $processedCount = 0;
/**
* @return array
*/
protected function notification(): array
{
return [
'title' => 'Generate E-Invoices',
'message' => sprintf(
'You have successfully submitted %d transaction%s for E-Invoices processing.',
$this->processedCount,
$this->processedCount === 1 ? '' : 's'
),
];
}
/**
* @param Request $request
* @return JsonResponse
* @throws \App\Classes\Exceptions\AccessForbiddenException
* @throws \App\Classes\Exceptions\MalformedRequestException
* @throws \App\Classes\Exceptions\RequestValidationException
*/
public function logic(Request $request): JsonResponse
{
$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;
$startDate = isset($validated['startDate']) && $validated['startDate']
? Carbon::createFromFormat('d-m-Y', $validated['startDate'])->startOfDay()
: Carbon::now()->startOfMonth()->startOfDay();
$endDate = isset($validated['endDate']) && $validated['endDate']
? Carbon::createFromFormat('d-m-Y', $validated['endDate'])->endOfDay()
: Carbon::now()->endOfMonth()->endOfDay();
$query = Transaction::query();
$approvalStatus = ApprovalStatus::COMPLETED;
$query->whereIn('type', [TransactionType::SHIPPING_INVOICE, TransactionType::STORAGE_INVOICE])
->where('status', $approvalStatus);
$transactions = $query->whereHas('transactions', function($transaction) use ($startDate, $endDate) {
$transaction->where('type', TransactionType::PAYMENT)
->whereBetween('updated_at', [$startDate, $endDate])
->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
})->get();
$count = 0;
foreach ($transactions as $transaction) {
ProcessTransactionForEInvoiceV2CommandJob::dispatch($transaction);
// $count = $count + 1;
// if($count > 15){
// break;
// }
}
$this->processedCount = count($transactions);
return $this->resourceResponse(JsonResource::collection(collect([])));
}
}
@@ -14,6 +14,7 @@ use App\Classes\Modules\Documents\Services\CreatesFiles;
use App\Classes\Modules\Documents\Services\CreatesDocument;
use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\ValueObjects\Constants\KVPKey;
class CreateShippingEInvoiceDocProcessor
{
@@ -54,8 +55,26 @@ class CreateShippingEInvoiceDocProcessor
$lastDayOfMonth = $invoice_transaction->created_at->copy()->endOfMonth();
$documentDate = $lastDayOfMonth;
$documentIdentifierPrefix = 'EI#: ';
$documentIdentifier = '';
$transaction_invoice_pdf = LaravelMpdf::loadView($view, ['invoice_transaction' => $invoice_transaction, 'brn' => $brn, 'document_date' => $documentDate, 'document_identifier' => $documentIdentifier, 'document_identifier_prefix' => $documentIdentifierPrefix]);
$autoCountInvoiceId = '';
$autoCountEInvoiceValidationLink = 'CIEF';
$metadata = $invoice_transaction->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE)->first();
if($metadata){
$autoCountInvoiceId = $metadata->value;
}
$metadata = $invoice_transaction->attributesKVP()->where('key', KVPKey::AUTOCOUNT_EINVOICE_VALIDATION_LINK)->first();
if($metadata){
$autoCountEInvoiceValidationLink = $metadata->value;
}
$transaction_invoice_pdf = LaravelMpdf::loadView($view, [
'invoice_transaction' => $invoice_transaction,
'brn' => $brn,
'document_date' => $documentDate,
'document_identifier' => $autoCountInvoiceId,
'document_identifier_prefix' => $documentIdentifierPrefix,
'autocountEInvoiceValidationLink' => $autoCountEInvoiceValidationLink,
]);
$document_object = new DocumentObject(
DocumentType::SHIPPING_EINVOICE,
@@ -70,7 +89,7 @@ class CreateShippingEInvoiceDocProcessor
$this->createsFiles->execute($document, $document_object);
$keyValuePairObject = new KeyValuePairObject("EINVOICE_ISSUED", 1);
$keyValuePairObject = new KeyValuePairObject(KVPKey::EINVOICE_ISSUED, 1);
$this->createsKeyValuePair->execute($invoice_transaction, $keyValuePairObject);
}
}
@@ -0,0 +1,13 @@
<?php
namespace App\Classes\ValueObjects\Constants;
class KVPKey
{
public const EINVOICE_ISSUED = 'EINVOICE_ISSUED';
public const AUTOCOUNT_DOCNO_INVOICE = 'AUTOCOUNT_DOCNO_I';
public const AUTOCOUNT_EINVOICE_VALIDATION_LINK = 'AUTOCOUNT_EINVOICE_VALIDATION_LINK';
}
@@ -8,42 +8,66 @@ use Maatwebsite\Excel\Excel;
use Illuminate\Support\Facades\Storage;
use App\Classes\General\AWSS3Helper;
use App\Classes\Modules\Exports\Services\ExportsCompanies;
use App\Classes\Modules\Exports\Services\ExportsSalesInvoicesReport;
use Carbon\Carbon;
class ExportController
{
public function companies(Request $request){
[$startDate, $endDate] = $this->getValidatedDates($request);
$exporter = new ExportsCompanies($startDate, $endDate);
return $this->handleExport($exporter, 'IZYIM - Customers Data Report.xls');
}
public function salesInvoices(Request $request){
[$startDate, $endDate] = $this->getValidatedDates($request);
$exporter = new ExportsSalesInvoicesReport($startDate, $endDate);
return $this->handleExport($exporter, 'IZYIM - Sales Invoice Report.xls');
}
private function getValidatedDates(Request $request): array
{
$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;
// $startDate = isset($validated['startDate']) && $validated['startDate']
// ? Carbon::createFromFormat('d-m-Y', $validated['startDate'])->startOfDay()
// : Carbon::now()->subMonth()->startOfDay();
if (isset($validated['startDate']) && $validated['startDate']) {
$startDate = Carbon::createFromFormat('d-m-Y', $validated['startDate'])->startOfDay();
} else {
$startDate = Carbon::now()->subMonths(1)->startOfDay();
}
// $endDate = isset($validated['endDate']) && $validated['endDate']
// ? Carbon::createFromFormat('d-m-Y', $validated['endDate'])->endOfDay()
// : Carbon::now()->endOfDay();
if (isset($validated['endDate']) && $validated['endDate']) {
$endDate = Carbon::createFromFormat('d-m-Y', $validated['endDate'])->endOfDay();
} else {
$endDate = Carbon::now()->endOfDay();
}
$startDate = isset($validated['startDate']) && $validated['startDate']
? Carbon::createFromFormat('d-m-Y', $validated['startDate'])->startOfDay()
: Carbon::now()->startOfMonth()->startOfDay();
$exportsCompanies = new ExportsCompanies($startDate, $endDate);
$endDate = isset($validated['endDate']) && $validated['endDate']
? Carbon::createFromFormat('d-m-Y', $validated['endDate'])->endOfDay()
: Carbon::now()->endOfMonth()->endOfDay();
$exportFileName = 'IZYIM - Customers Data Report.xls';
return [$startDate, $endDate];
}
private function handleExport($exporter, string $exportFileName)
{
$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();
if ($filesystemDriver === 's3') {
return response([
'src' => AWSS3Helper::S3Exportable($exportFileName, $exporter)
]);
}
$response = $exporter->download(
$exportFileName,
Excel::XLS,
['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']
);
ob_end_clean(); // prevent corrupt download in some environments
return $response;
}
}
@@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers\Imports;
use App\Classes\Modules\Imports\ControllersLogic\ImportExcelLogic;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ImportController extends Controller
{
public function salesInvoices(Request $request, ImportExcelLogic $logic): JsonResponse
{
return $logic->execute($request);
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Orders;
use App\Classes\Modules\Orders\ControllersLogic\CreateBatchProcessingAutoCountImportLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CreateBatchProcessingAutoCountImportController
{
/**
* @param Request $request
* @param CreateBatchProcessingAutoCountImportLogic $logic
* @return JsonResponse
*/
public function batchProcess(Request $request, CreateBatchProcessingAutoCountImportLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
+1 -1
View File
@@ -82,7 +82,7 @@ class TransactionResource extends JsonResource
'updated_at' => Carbon::parse($this->updated_at)->format('d-m-Y'),
'created_at' => Carbon::parse($this->created_at)->format('d-m-Y'),
'is_einvoice_applicable' => Carbon::parse($this->created_at)->isAfter(Carbon::parse(env('E_INVOICE_START_DATE', '2025-07-01 00:00:00'))) && $this->is_einvoice_applicable,
// 'is_einvoice_downloadable' => (int) optional( $this->attributesKVP()->where('key', 'EINVOICE_ISSUED')->first())->value === 1,
// 'is_einvoice_downloadable' => (int) optional( $this->attributesKVP()->where('key', KVPKey::EINVOICE_ISSUED)->first())->value === 1,
];
}
}
@@ -0,0 +1,162 @@
<template>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body no-padding-bottom">
<div id="accordionDownloadCustomersDataReport">
<div id="heading">
<div class="">
<div>
<span>IZYIM - Sales Invoice Report.xls</span>
<p class="text-muted mb-2"></p>
</div>
<div class="mt-auto">
<div class="row">
<div class="col-4">
<open-link-in-new-tab-component custom-class="btn btn-primary w-100 h-75" :url="downloadUrl" :is-url-protected="true">
<button class="btn btn-lg w-100 h-100 d-flex justify-content-center align-items-center btn-primary pointer">
<span><i class="fa fa-download"></i> Download</span>
</button>
</open-link-in-new-tab-component>
</div>
<div class="col-4">
<button
class="btn btn-primary w-100 h-75 d-flex justify-content-center align-items-center btn-primary requestModal pointer"
:data-type="'uploadDocumentModel'"
>
<span><i class="fa fa-upload"></i> Upload</span>
</button>
<modal-component type="uploadDocumentModel">
<upload-component :url="route('api.import.sales_invoices')" v-on:importSuccess="importSuccess($event)" :section="section" :report-type="'Sales Invoice Report'"></upload-component>
</modal-component>
</div>
<div class="col-4">
<button class="btn btn-primary w-100 h-75 d-flex justify-content-center align-items-center btn-primary pointer" @click="handleGenerateEInvoiceClick()" >
<span>Process E-Invoices</span>
</button>
<modal-component
class="animate__animated animate__fast animate__fadeIn"
styleType="fill-in" size="large"
id="modal-generate-einvoice">
<general-confirmation-form-component
contentText="Are you sure you want to batch process for E-Invoices? (Only applicable for new e-invoices with DocNo. Please also make sure the date range are properly selected)"
modalType="confirm"
class="text-center bg-white padding-40 b-rad-lg"
:apiRoute="generateEInvoicesUrl"
apiMethod="get"
:section="section"
v-if="generateEInvoicesUrl"
>
</general-confirmation-form-component>
</modal-component>
</div>
</div>
</div>
</div>
<div id="collapseDownloadSalesInvoiceReport" class="collapse" aria-labelledby="heading" data-parent="#accordionDownloadCustomersDataReport">
<div class="card-body">
<div class="row">
<div class="col-6">
<label>Start Date</label>
<date-picker-limit-range-component v-model="startDate" @input="onStartDateChange" :maxDays="maxDays"></date-picker-limit-range-component>
</div>
<div class="col-6">
<label>End Date</label>
<date-picker-limit-range-component v-model="endDate" ref="endDatePicker" :maxDays="maxDays"></date-picker-limit-range-component>
</div>
</div>
<div class="mt-2 text-muted small">
<em>Note: Date filter not applicable to upload</em>
</div>
</div>
</div>
<button class="btn btn-link w-100 text-center no-padding" @click="toggleCollapse" data-toggle="collapse" data-target="#collapseDownloadSalesInvoiceReport" aria-expanded="true" aria-controls="collapseDownloadTransactionReport">
<i :class="['fa', isCollapsed ? 'fa-chevron-up' : 'fa-chevron-down']"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
isCollapsed: false,
maxDays: 180,
startDate: '',
endDate: '',
isValidRange: false,
section: 'ImportExportSalesInvoiceReportSection',
generateEInvoicesUrl: null,
}
},
computed: {
downloadUrl() {
let url = this.generateDownloadUrl();
return url;
}
},
methods: {
onStartDateChange(date) {
if (date) {
if (this.$refs.endDatePicker) {
const startDate = new Date(date.split('-').reverse().join('-'));
const adjustedEndDate = new Date(startDate);
this.$refs.endDatePicker.setStartDate(adjustedEndDate);
}
this.isValidRange = false;
}
},
formatDate(date) {
if (date) {
const [day, month, year] = date.split('-');
return `${day}-${month}-${year}`;
}
return '';
},
validateDateRange() {
if (this.startDate && this.endDate) {
const start = new Date(this.startDate.split('-').reverse().join('-'));
const end = new Date(this.endDate.split('-').reverse().join('-'));
const difference = Math.floor((end - start) / (1000 * 60 * 60 * 24));
this.isValidRange = difference >= 0 && difference <= this.maxDays;
return this.isValidRange;
}
return false;
},
generateDownloadUrl() {
if (this.validateDateRange()) {
const formattedStartDate = this.formatDate(this.startDate);
const formattedEndDate = this.formatDate(this.endDate);
return `${route('api.export.transactions.sales_invoices')}?startDate=${formattedStartDate}&endDate=${formattedEndDate}`;
}
return `${route('api.export.transactions.sales_invoices')}`;
},
handleGenerateEInvoiceClick(){
this.error = '';
if (!this.validate()) return;
const selectedRoute = route('api.order.batch.process');
this.generateEInvoicesUrl = `${selectedRoute}?startDate=${this.startDate}&endDate=${this.endDate}`;
$('#modal-generate-einvoice').modal('show');
},
toggleCollapse() {
this.isCollapsed = !this.isCollapsed;
},
importSuccess(payload) {
// this.error = payload.message + payload.data;
}
},
};
</script>
<style scoped>
.no-padding-bottom {
padding-bottom: 0 !important;
}
</style>
@@ -81,7 +81,9 @@
url: '#',
autoQueue: false,
processQueue: false,
acceptedFiles: 'image/*, application/pdf',
// acceptedFiles: 'image/*, application/pdf',
// acceptedFiles: 'image/*, application/pdf, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
acceptedFiles: 'image/*, application/pdf, application/vnd.ms-excel',
uploadMultiple: true,
clickable: '.select-btn',
previewTemplate: '<div class="row m-l-0 m-r-0 align-items-center m-t-5 m-b-5 bg-master-lightest text-left p-t-10 p-b-10 "> <div class="col-auto p-r-0"> <img data-dz-thumbnail style="width: 35px; height: 35px;" /> </div> <div class="col"> <div class="row m-b-5"> <div class="col"> <div class="dz-filename fs-8 bold"><span data-dz-name></span></div> </div> </div> <div class="row"> <div class="col"> <div class="dz-size muted light fs-10" data-dz-size></div> </div> </div> </div> <div class="col-auto"><i class="fs-16 fa fa-times-circle pointer hint-text" data-dz-remove></i></div> </div>'
@@ -98,10 +100,18 @@
vm.isLoading = true;
if(file.name.split('.').pop() === 'pdf'){
$(file.previewElement).closest("img[data-dz-thumbnail]").attr("src", Vapor.asset("images/icons/pdf.png"));
// $(file.previewElement).closest("img[data-dz-thumbnail]").attr("src", Vapor.asset("images/icons/pdf.png"));
const thumbnailElement = file.previewElement.querySelector("[data-dz-thumbnail]");
if (thumbnailElement) {
thumbnailElement.src = Vapor.asset("images/icons/pdf.png");
}
}
if(file.name.split('.').pop().indexOf("xls") !== -1){
$(file.previewElement).closest("img[data-dz-thumbnail]").attr("src", Vapor.asset("images/icons/xlsx.png"));
// $(file.previewElement).closest("img[data-dz-thumbnail]").attr("src", Vapor.asset("images/icons/xlsx.png"));
const thumbnailElement = file.previewElement.querySelector("[data-dz-thumbnail]");
if (thumbnailElement) {
thumbnailElement.src = Vapor.asset("images/icons/xlsx.png");
}
}
Promise.all(dropzone.files.map(function(file){
@@ -0,0 +1,97 @@
<template>
<div class="row" @keyup.enter="submitForm">
<div class="col">
<loading-component style="height: 200px; top: 0;" key="1" color="success" v-show="$store.getters.isLoading(section)"></loading-component>
<div class="row" v-show="!$store.getters.isLoading(section)">
<div class="col">
<div class="row m-b-10">
<div class="col">
<div class="font-heading fs-16 all-caps bold m-b-15">Upload: {{ reportType }}</div>
</div>
</div>
<error-message-component class="m-b-20" :error="error"></error-message-component>
<div class="row">
<div class="col">
<file-input-component :validator="$v.files" v-model="files">
<template slot="label">
<div class="font-heading fs-11 text-primary all-caps">.xls file ONLY</div>
</template>
<template slot="tips">
<div class="row">
<div class="col">
<div class="row m-b-10">
<div class="col">
<div class="font-heading fs-11 text-warning m-b-10">Please double check the Report Type selected before proceed upload.</div>
</div>
</div>
</div>
</div>
</template>
</file-input-component>
</div>
</div>
<div class="row m-t-20">
<div class="col">
<div class="row">
<div class="col-auto">
<button type="button" class="btn btn-sm bg-master-lighter p-t-10 p-b-10 p-r-35 p-l-35 btn-default b-rad-none" data-dismiss="modal">Cancel</button>
</div>
<div class="col text-right">
<button type="button" class="btn btn-sm p-t-10 p-b-10 p-r-35 p-l-35 btn-success b-rad-none" @click="submitForm">Upload</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import ModalFromHandler from '../../../general/mixins/modalFormHandler'
import { required } from "vuelidate/lib/validators";
export default {
props: {
reportType: {
type: String,
required: true
},
url: {
type: String,
required: true
},
},
data(){
return {
files: [],
parameters: {}
}
},
validations: {
files: {
required
}
},
methods: {
submitForm(){
this.parameters = {
files: this.files,
report_type: this.reportType
};
this.submit(this.url, 'post', this.section, true, true);
},
successHandler(response) {
this.closeModal();
this.formHandler();
// if(this.url === route('api.import.official_receipt')){
// this.$emit('importSuccess', response.payload);
// }
},
},
mixins: [ModalFromHandler]
}
</script>
+41 -26
View File
@@ -2,7 +2,44 @@
@section('inner_content')
<div class="row d-none" :class="[{'d-flex': $store.getters.isAdmin}]" v-if="$store.getters.isAdmin">
<!-- Group 1: Packaging Downloads -->
<!-- Group 1: Sales Invoices Downloads -->
<div class="col-12" v-if="$store.getters.isSuperAdmin">
<h4>Sales Invoices Downloads</h4>
<div class="row">
<div class="col-md-6">
<download-upload-sales-invoice-report-component>
<i class="fa fa-download"></i> Download
</download-upload-sales-invoice-report-component>
</div>
<div class="col-md-6">
</div>
</div>
</div>
<!-- Group 2: Customer Downloads -->
<div class="col-12" v-if="$store.getters.isSuperAdmin">
<h4>Customer Downloads</h4>
<div class="row">
<div class="col-md-6">
<div class="card mb-3">
<div class="card-body d-flex justify-content-between align-items-center">
<span class="w-50 d-block">shipping_all_customers_info_for_lark_system.xls</span>
<password-protected-download-component custom-class="btn btn-primary" :url="route('exportAllCustomersInfoForLarkSystem.export')">
<i class="fa fa-download"></i> Download
</password-protected-download-component>
</div>
</div>
</div>
<div class="col-md-6">
<download-customers-data-report-component>
<i class="fa fa-download"></i> Download
</download-customers-data-report-component>
</div>
</div>
</div>
<!-- Group 3: Packaging Downloads -->
<div class="col-12">
<h4>Packaging Downloads</h4>
<div class="row">
@@ -105,29 +142,7 @@
</div>
</div>
<!-- Group 2: Customer Downloads -->
<div class="col-12" v-if="$store.getters.isSuperAdmin">
<h4>Customer Downloads</h4>
<div class="row">
<div class="col-md-6">
<div class="card mb-3">
<div class="card-body d-flex justify-content-between align-items-center">
<span class="w-50 d-block">shipping_all_customers_info_for_lark_system.xls</span>
<password-protected-download-component custom-class="btn btn-primary" :url="route('exportAllCustomersInfoForLarkSystem.export')">
<i class="fa fa-download"></i> Download
</password-protected-download-component>
</div>
</div>
</div>
<div class="col-md-6">
<download-customers-data-report-component>
<i class="fa fa-download"></i> Download
</download-customers-data-report-component>
</div>
</div>
</div>
<!-- Group 3: Order Downloads -->
<!-- Group 4: Order Downloads -->
<div class="col-12">
<h4>Order Downloads</h4>
<div class="row">
@@ -147,7 +162,7 @@
</div>
</div>
<!-- Group 4: Segment Downloads -->
<!-- Group 5: Segment Downloads -->
<div class="col-12">
<h4>Segment Downloads</h4>
<div class="row">
@@ -164,7 +179,7 @@
</div>
</div>
<!-- Group 5: Feedback Downloads -->
<!-- Group 6: Feedback Downloads -->
<div class="col-12">
<h4>Feedback Downloads</h4>
<div class="row">
@@ -174,12 +174,12 @@
<tbody>
<tr align="center">
<td>
<img src="{{ url(config('qr.qr_code_img_url') . 'http://e-invoice uuid link') }}" style="width: 230px; height: 230px;" />
<img src="{{ url(config('qr.qr_code_img_url') . $autocountEInvoiceValidationLink ) }}" style="width: 230px; height: 230px;" />
</td>
</tr>
<tr align="center">
<td>
<h2 style="margin: 0 !important; font-size: 12px;"><strong>http://e-invoice uuid link</strong></h2>
<h2 style="margin: 0 !important;"><strong>{{ $autocountEInvoiceValidationLink }}</strong></h2>
</td>
</tr>
</tbody>
@@ -108,6 +108,8 @@
<tr>
<td width="5%" class="center top">{{ $key + 1 }}</td>
@php
use App\Classes\ValueObjects\Constants\KVPKey;
$adjusted_bill_no = $invoice_transaction->bill_no;
$companyModule = $invoice_transaction->owner->owner->companyModule;
@@ -122,7 +124,8 @@
$adjusted_bill_no = $documentIdentifier;
}
$isEInvoiceIssued = $invoice_transaction->attributesKVP()->where('key', "EINVOICE_ISSUED")->first();
$isEInvoiceIssued = $invoice_transaction->attributesKVP()->where('key', KVPKey::EINVOICE_ISSUED)->first();
if ($isEInvoiceIssued && (int)$isEInvoiceIssued->value === 1){
$documentIdentifier = '';
$adjusted_bill_no = $adjusted_bill_no . '(' . $documentIdentifier . ')';
@@ -182,6 +185,8 @@
@foreach ($invoice_transactions as $transaction)
{{-- DEFAULT --}}
@php
use App\Classes\ValueObjects\Constants\KVPKey;
$companyModule = $transaction->owner->owner->companyModule;
$user = $companyModule->employees()->first();
$brn = $companyModule->company->documents->where(
@@ -196,7 +201,7 @@
$sstStartDate = \Carbon\Carbon::parse(env('SST_START_DATE', '2024-04-01 00:00:00'));
$eInvoiceStartDate = \Carbon\Carbon::parse(env('E_INVOICE_START_DATE', '2025-07-01 00:00:00'));
$isEInvoiceIssued = $transaction->attributesKVP()->where('key', "EINVOICE_ISSUED")->first();
$isEInvoiceIssued = $transaction->attributesKVP()->where('key', KVPKey::EINVOICE_ISSUED)->first();
@endphp
{{-- 00 With EInvoice: Summary page --> Sales Order--> Normal Invoice (if any) --> E-invoice --> Packing List --}}
+9
View File
@@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\Exports\ExportController;
use App\Http\Controllers\Imports\ImportController;
use Illuminate\Support\Facades\Route;
@@ -8,4 +9,12 @@ Route::group(['prefix' => 'export', 'as' => 'export.', 'namespace' => 'Exports']
Route::group(['prefix' => 'companies', 'as' => 'companies.'], function () {
Route::get('/customers-data', [ExportController::class, 'companies'])->name('customers-data');
});
Route::group(['prefix' => 'transactions', 'as' => 'transactions.'], function () {
Route::get('/sales-invoices', [ExportController::class, 'salesInvoices'])->name('sales_invoices');
});
});
Route::group(['prefix' => 'import', 'as' => 'import.', 'namespace' => 'Imports'], function () {
Route::post('/import/sales-invoice', [ImportController::class, 'salesInvoices'])->name('sales_invoices');
});
+5
View File
@@ -1,6 +1,7 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Orders\CreateBatchProcessingAutoCountImportController;
Route::group(['prefix' => 'order', 'as' => 'order.', 'namespace' => 'Orders'], function () {
Route::get('/show/{id}', 'FetchOrderController@fetch')->name('show');
@@ -30,4 +31,8 @@ Route::group(['prefix' => 'order', 'as' => 'order.', 'namespace' => 'Orders'], f
Route::get('/tracking/list', 'ListOrderTrackingController@list')->name('tracking.list');
Route::group(['prefix' => 'batch/einvoice', 'as' => 'batch.'], function () {
Route::get('/process', [CreateBatchProcessingAutoCountImportController::class, 'batchProcess'])->name('process');
});
});