mirror of
https://gitlab.com/CIEFWorldwideSdnBhd/exchange-2.0.git
synced 2026-08-19 04:23:55 +00:00
Merge branch 'dillon/122-1688-po-automation-project' into vapor/development
This commit is contained in:
@@ -80,3 +80,5 @@ SENDING_EMAIL_WELCOME_VOUCHER_ENABLED=false
|
||||
E_INVOICE_START_DATE="2025-07-01 00:00:00"
|
||||
MAINTENANCE_MESSAGE_TITLE="We'll be back online on 00:00 1/7/2025"
|
||||
MAINTENANCE_MESSAGE="Sorry for the inconvenience but we're performing some maintenance at the moment."
|
||||
|
||||
OPENAI_API_KEY=""
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\General\Eloquent\Filters;
|
||||
|
||||
|
||||
use App\Classes\ValueObjects\Constants\KVPKey;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ChatgptPrompt implements Filter
|
||||
{
|
||||
|
||||
public static function apply(Builder $builder, $value)
|
||||
{
|
||||
return $builder->where(
|
||||
'key',
|
||||
'LIKE',
|
||||
KVPKey::CHATGPT_PROMPT_PREFIX . '%'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Jobs\Commands\V2;
|
||||
|
||||
use App\Classes\Modules\Transactions\Processors\ETLPurchaseOrderTransactionProcessor;
|
||||
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 Illuminate\Support\Facades\Log;
|
||||
|
||||
class ETLPurchaseOrderTransactionV2CommandJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/** @var array */
|
||||
private $products;
|
||||
|
||||
/** @var array */
|
||||
private $productsMetadata;
|
||||
|
||||
/** @var int|null */
|
||||
private $bookingId;
|
||||
|
||||
/**
|
||||
* ETLPurchaseOrderTransactionV2CommandJob constructor.
|
||||
* @param array $products
|
||||
* @param array $productsMetadata
|
||||
* @param int $bookingId
|
||||
*/
|
||||
public function __construct(array $products, array $productsMetadata, ?int $bookingId)
|
||||
{
|
||||
$this->products = $products;
|
||||
$this->productsMetadata = $productsMetadata;
|
||||
$this->bookingId = $bookingId;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
Log::info(Carbon::now() . ': Start job - ETL Purchase Order Transaction.');
|
||||
$start = new Carbon();
|
||||
|
||||
(App()->make(ETLPurchaseOrderTransactionProcessor::class))->execute($this->products, $this->productsMetadata, $this->bookingId);
|
||||
|
||||
$end = new Carbon();
|
||||
$elapsedTime = $start->diff($end)->format('%H:%I:%S');
|
||||
Log::info(Carbon::now() . ': End job - ETL Purchase Order Transaction. ElapsedTime: ' . $elapsedTime . '.');
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,19 @@ class CreatesKeyValuePair extends AbstractUpdateRelationshipRecord
|
||||
* @throws \App\Classes\Exceptions\MalformedRequestException
|
||||
*/
|
||||
|
||||
public function execute(KeyValueInterface $kv, KeyValuePairObject $object) {
|
||||
public function execute(?KeyValueInterface $kv, KeyValuePairObject $object) {
|
||||
$model = new KeyValuePair();
|
||||
$model->key = $object->getKey();
|
||||
$model->value = $object->getValue();
|
||||
|
||||
return $this->handler($kv->attributesKVP(), $model);
|
||||
// Owner-less
|
||||
if (is_null($kv)) {
|
||||
$model->owner_type = null;
|
||||
$model->owner_id = null;
|
||||
$model->save();
|
||||
return $model;
|
||||
}
|
||||
|
||||
return $this->handler($kv->attributesKVP(), $model);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\Accounts\Services;
|
||||
|
||||
|
||||
use App\Classes\General\Eloquent\AbstractFetchRecord;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use App\Models\KeyValuePair;
|
||||
|
||||
class FetchesKeyValuePair extends AbstractFetchRecord
|
||||
{
|
||||
|
||||
/** @var KeyValuePair */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* FetchesKeyValuePair constructor.
|
||||
* @param KeyValuePair $repository
|
||||
*/
|
||||
public function __construct(KeyValuePair $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Builder
|
||||
*/
|
||||
public function getRepository(): Builder
|
||||
{
|
||||
return $this->repository->newQuery();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\ControllersLogic;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractControllerLogic;
|
||||
use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject;
|
||||
use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair;
|
||||
use App\Classes\Modules\KeyValuePairs\DataTransferObjects\CreateChatGPTPromptDTO;
|
||||
use App\Classes\Modules\KeyValuePairs\Standards\Rules\CanCreateChatGPTPrompt;
|
||||
use App\Classes\ValueObjects\Constants\KVPKey;
|
||||
use App\Http\Resources\ChatGPTPromptResource;
|
||||
use ErrorException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CreateChatGPTPromptKVPLogic extends AbstractControllerLogic
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function notification(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'Created ChatGPT Prompt',
|
||||
'message' => 'You have successfully created a new ChatGPT Prompt'
|
||||
];
|
||||
}
|
||||
|
||||
/** @var CanCreateChatGPTPrompt */
|
||||
private $canCreateChatGPTPrompt;
|
||||
|
||||
/** @var CreatesKeyValuePair */
|
||||
private $createsKeyValuePair;
|
||||
|
||||
/**
|
||||
* CreateChatGPTPromptKVPLogic constructor.
|
||||
* @param CanCreateChatGPTPrompt $canCreateChatGPTPrompt
|
||||
* @param CreatesKeyValuePair $createsKeyValuePair
|
||||
*/
|
||||
public function __construct(
|
||||
CanCreateChatGPTPrompt $canCreateChatGPTPrompt,
|
||||
CreatesKeyValuePair $createsKeyValuePair
|
||||
) {
|
||||
$this->canCreateChatGPTPrompt = $canCreateChatGPTPrompt;
|
||||
$this->createsKeyValuePair = $createsKeyValuePair;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
{
|
||||
$dto = new CreateChatGPTPromptDTO($request->all());
|
||||
|
||||
$this->canCreateChatGPTPrompt->passes($dto);
|
||||
|
||||
$key = KVPKey::CHATGPT_PROMPT_PREFIX . strtoupper(str_replace(' ', '_', $dto->getTitle()));
|
||||
$keyValuePairObject = new KeyValuePairObject($key, $dto->getPrompt());
|
||||
|
||||
$kvp = $this->createsKeyValuePair->execute(null, $keyValuePairObject);
|
||||
|
||||
return $this->resourceResponse(new ChatGPTPromptResource($kvp));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\ControllersLogic;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractControllerLogic;
|
||||
use App\Classes\Modules\Accounts\Services\DeletesKeyValuePair;
|
||||
use App\Classes\Modules\Accounts\Services\FetchesKeyValuePair;
|
||||
use App\Classes\Modules\KeyValuePairs\Standards\Rules\CanDeleteChatGPTPrompt;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DeleteChatGPTPromptKVPLogic extends AbstractControllerLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function notification(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'Delete ChatGPT Prompt',
|
||||
'message' => 'You have successfully deleted the ChatGPT Prompt'
|
||||
];
|
||||
}
|
||||
|
||||
/** @var CanDeleteChatGPTPrompt */
|
||||
private $canDeleteChatGPTPrompt;
|
||||
|
||||
/** @var FetchesKeyValuePair */
|
||||
private $fetchesKeyValuePair;
|
||||
|
||||
/** @var DeletesKeyValuePair */
|
||||
private $deletesKeyValuePair;
|
||||
|
||||
/**
|
||||
* DeleteChatGPTPromptKVPLogic constructor.
|
||||
* @param CanDeleteChatGPTPrompt $canDeleteChatGPTPrompt
|
||||
* @param FetchesKeyValuePair $fetchesKeyValuePair
|
||||
* @param DeletesKeyValuePair $deletesKeyValuePair
|
||||
*/
|
||||
public function __construct(
|
||||
CanDeleteChatGPTPrompt $canDeleteChatGPTPrompt,
|
||||
FetchesKeyValuePair $fetchesKeyValuePair,
|
||||
DeletesKeyValuePair $deletesKeyValuePair
|
||||
) {
|
||||
$this->canDeleteChatGPTPrompt = $canDeleteChatGPTPrompt;
|
||||
$this->fetchesKeyValuePair = $fetchesKeyValuePair;
|
||||
$this->deletesKeyValuePair = $deletesKeyValuePair;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
{
|
||||
$this->canDeleteChatGPTPrompt->passes();
|
||||
|
||||
$kvp = $this->fetchesKeyValuePair->execute(['id' => $request->route('id')]);
|
||||
|
||||
$this->deletesKeyValuePair->execute($kvp);
|
||||
|
||||
return $this->response([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\ControllersLogic;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractControllerLogic;
|
||||
use App\Classes\Modules\KeyValuePairs\Services\ListsKeyValuePairs;
|
||||
use App\Classes\Modules\KeyValuePairs\Standards\Rules\CanListChatGPTPrompts;
|
||||
use App\Http\Resources\ChatGPTPromptResource;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ListChatGPTPromptsKVPLogic extends AbstractControllerLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function notification(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'Retrieve ChatGPT Prompts',
|
||||
'message' => 'You have successfully retrieved a list of ChatGPT Prompts'
|
||||
];
|
||||
}
|
||||
|
||||
/** @var CanListChatGPTPrompts */
|
||||
private $canListChatGPTPrompts;
|
||||
|
||||
/** @var ListsKeyValuePairs */
|
||||
private $listsKeyValuePairs;
|
||||
|
||||
/**
|
||||
* ListChatGPTPromptsKVPLogic constructor.
|
||||
* @param CanListChatGPTPrompts $canListChatGPTPrompts
|
||||
* @param ListsKeyValuePairs $listsKeyValuePairs
|
||||
*/
|
||||
public function __construct(CanListChatGPTPrompts $canListChatGPTPrompts, ListsKeyValuePairs $listsKeyValuePairs)
|
||||
{
|
||||
$this->canListChatGPTPrompts = $canListChatGPTPrompts;
|
||||
$this->listsKeyValuePairs = $listsKeyValuePairs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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
|
||||
{
|
||||
$this->canListChatGPTPrompts->passes();
|
||||
|
||||
$prompts = $this->listsKeyValuePairs->execute(array_merge($this->listsKeyValuePairs->deserializeFilters($request->input('filters')), ['chatgpt_prompt' => false]));
|
||||
|
||||
return $this->collectionResponse(ChatGPTPromptResource::collection($prompts));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\ControllersLogic;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractControllerLogic;
|
||||
use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject;
|
||||
use App\Classes\Modules\KeyValuePairs\DataTransferObjects\UpdateChatGPTPromptDTO;
|
||||
use App\Classes\Modules\KeyValuePairs\Standards\Rules\CanUpdateChatGPTPrompt;
|
||||
use App\Classes\Modules\Accounts\Services\FetchesKeyValuePair;
|
||||
use App\Classes\Modules\Accounts\Services\UpdatesKeyValuePair;
|
||||
use App\Http\Resources\ChatGPTPromptResource;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
||||
class UpdateChatGPTPromptKVPLogic extends AbstractControllerLogic
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function notification():array {
|
||||
return [
|
||||
'title' => 'Updated ChatGPT Prompt',
|
||||
'message' => 'You have successfully updated the ChatGPT prompt'
|
||||
];
|
||||
}
|
||||
|
||||
/** @var CanUpdateChatGPTPrompt */
|
||||
private $canUpdateChatGPTPrompt;
|
||||
|
||||
/** @var UpdatesKeyValuePair */
|
||||
private $updatesKeyValuePair;
|
||||
|
||||
/** @var FetchesKeyValuePair */
|
||||
private $fetchesKeyValuePair;
|
||||
|
||||
/**
|
||||
* UpdateChatGPTPromptKVPLogic constructor.
|
||||
* @param CanUpdateChatGPTPrompt $canUpdateChatGPTPrompt
|
||||
* @param UpdatesKeyValuePair $updatesKeyValuePair
|
||||
* @param FetchesKeyValuePair $fetchesKeyValuePair
|
||||
*/
|
||||
public function __construct(CanUpdateChatGPTPrompt $canUpdateChatGPTPrompt, UpdatesKeyValuePair $updatesKeyValuePair, FetchesKeyValuePair $fetchesKeyValuePair)
|
||||
{
|
||||
$this->canUpdateChatGPTPrompt = $canUpdateChatGPTPrompt;
|
||||
$this->updatesKeyValuePair = $updatesKeyValuePair;
|
||||
$this->fetchesKeyValuePair = $fetchesKeyValuePair;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
{
|
||||
$dto = new UpdateChatGPTPromptDTO($request->all());
|
||||
$this->canUpdateChatGPTPrompt->passes($dto);
|
||||
|
||||
$kvp = $this->fetchesKeyValuePair->execute(['id' => $dto->getId()]);
|
||||
$keyValuePairObject = new KeyValuePairObject($kvp->key, $dto->getPrompt());
|
||||
$this->updatesKeyValuePair->execute($kvp, $keyValuePairObject);
|
||||
|
||||
return $this->resourceResponse(new ChatGPTPromptResource($kvp));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\DataTransferObjects;
|
||||
|
||||
|
||||
use App\Classes\General\Interfaces\DataTransferObject;
|
||||
|
||||
class CreateChatGPTPromptDTO implements DataTransferObject
|
||||
{
|
||||
public string $title;
|
||||
public string $prompt;
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->title = (string) ($data['key'] ?? '');
|
||||
$this->prompt = (string) ($data['value'] ?? '');
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'title' => $this->title,
|
||||
'prompt' => $this->prompt,
|
||||
];
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getPrompt(): string
|
||||
{
|
||||
return $this->prompt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\DataTransferObjects;
|
||||
|
||||
|
||||
use App\Classes\General\Interfaces\DataTransferObject;
|
||||
|
||||
class UpdateChatGPTPromptDTO implements DataTransferObject
|
||||
{
|
||||
public int $id;
|
||||
public string $title;
|
||||
public string $prompt;
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->id = (int) ($data['id'] ?? 0);
|
||||
$this->title = (string) ($data['key'] ?? '');
|
||||
$this->prompt = (string) ($data['value'] ?? '');
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'prompt' => $this->prompt,
|
||||
];
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getPrompt(): string
|
||||
{
|
||||
return $this->prompt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\Services;
|
||||
|
||||
|
||||
use App\Classes\General\Eloquent\AbstractListRecord;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use App\Models\KeyValuePair;
|
||||
|
||||
class ListsKeyValuePairs extends AbstractListRecord
|
||||
{
|
||||
|
||||
/** @var KeyValuePair */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* ListsKeyValuePairs constructor.
|
||||
* @param KeyValuePair $repository
|
||||
*/
|
||||
public function __construct(KeyValuePair $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Builder
|
||||
*/
|
||||
function getRepository(): Builder
|
||||
{
|
||||
return $this->repository->newQuery();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\Standards\Rules;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractRule;
|
||||
use App\Classes\Modules\KeyValuePairs\Standards\Validators\CreateChatGPTPromptValidation;
|
||||
use App\Classes\Modules\KeyValuePairs\DataTransferObjects\CreateChatGPTPromptDTO;
|
||||
use App\Classes\ValueObjects\Constants\RoleTypes;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CanCreateChatGPTPrompt extends AbstractRule
|
||||
{
|
||||
|
||||
/** @var CreateChatGPTPromptValidation */
|
||||
private $validation;
|
||||
|
||||
/**
|
||||
* CanCreateChatGPTPrompt constructor.
|
||||
* @param CreateChatGPTPromptValidation $validation
|
||||
*/
|
||||
public function __construct(CreateChatGPTPromptValidation $validation)
|
||||
{
|
||||
$this->validation = $validation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorized($object): bool
|
||||
{
|
||||
if (in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CreateChatGPTPromptDTO $object
|
||||
* @return bool
|
||||
* @throws \App\Classes\Exceptions\RequestValidationException
|
||||
*/
|
||||
protected function validators($object): bool
|
||||
{
|
||||
return $this->validation->validate($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CreateChatGPTPromptDTO $object
|
||||
* @return bool
|
||||
*/
|
||||
protected function criteria($object): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\Standards\Rules;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractRule;
|
||||
use App\Classes\ValueObjects\Constants\RoleTypes;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CanDeleteChatGPTPrompt extends AbstractRule
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorized($object): bool
|
||||
{
|
||||
if (in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $object
|
||||
* @return bool
|
||||
*/
|
||||
protected function validators($object): bool
|
||||
{
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $object
|
||||
* @return bool
|
||||
*/
|
||||
protected function criteria($object): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\Standards\Rules;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractRule;
|
||||
use App\Classes\ValueObjects\Constants\RoleTypes;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CanListChatGPTPrompts extends AbstractRule
|
||||
{
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorized($object): bool
|
||||
{
|
||||
if (in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $object
|
||||
* @return bool
|
||||
*/
|
||||
protected function validators($object): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $object
|
||||
* @return bool
|
||||
*/
|
||||
protected function criteria($object): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\Standards\Rules;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractRule;
|
||||
use App\Classes\Modules\KeyValuePairs\DataTransferObjects\UpdateChatGPTPromptDTO;
|
||||
use App\Classes\Modules\KeyValuePairs\Standards\Validators\UpdateChatGPTPromptValidation;
|
||||
use App\Classes\ValueObjects\Constants\RoleTypes;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CanUpdateChatGPTPrompt extends AbstractRule
|
||||
{
|
||||
|
||||
/** @var UpdateChatGPTPromptValidation */
|
||||
private $validation;
|
||||
|
||||
|
||||
/**
|
||||
* CanUpdateChatGPTPrompt constructor.
|
||||
* @param UpdateChatGPTPromptValidation $validation
|
||||
*/
|
||||
public function __construct(UpdateChatGPTPromptValidation $validation)
|
||||
{
|
||||
$this->validation = $validation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorized($object): bool
|
||||
{
|
||||
if (in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UpdateChatGPTPromptDTO $object
|
||||
* @return bool
|
||||
* @throws \App\Classes\Exceptions\RequestValidationException
|
||||
*/
|
||||
protected function validators($object): bool
|
||||
{
|
||||
return $this->validation->validate($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UpdateChatGPTPromptDTO $object
|
||||
* @return bool
|
||||
*/
|
||||
protected function criteria($object): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\Standards\Validators;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractValidation;
|
||||
use App\Classes\Modules\KeyValuePairs\DataTransferObjects\UpdateChatGPTPromptDTO;
|
||||
|
||||
class CreateChatGPTPromptValidation extends AbstractValidation
|
||||
{
|
||||
/**
|
||||
* @param UpdateChatGPTPromptDTO $object
|
||||
* @return array
|
||||
*/
|
||||
protected function data($object): array
|
||||
{
|
||||
return [
|
||||
'title' => $object->getTitle(),
|
||||
'prompt' => $object->getPrompt(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'prompt' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function messages(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\KeyValuePairs\Standards\Validators;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractValidation;
|
||||
use App\Classes\Modules\KeyValuePairs\DataTransferObjects\UpdateChatGPTPromptDTO;
|
||||
|
||||
class UpdateChatGPTPromptValidation extends AbstractValidation
|
||||
{
|
||||
/**
|
||||
* @param UpdateChatGPTPromptDTO $object
|
||||
* @return array
|
||||
*/
|
||||
protected function data($object): array
|
||||
{
|
||||
return [
|
||||
'title' => $object->getTitle(),
|
||||
'prompt' => $object->getPrompt(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'prompt' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function messages(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\OpenAI\Services;
|
||||
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Classes\Exceptions\MalformedRequestException;
|
||||
use App\Classes\Exceptions\ConnectionErrorException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CreatesChatGPTResponse
|
||||
{
|
||||
/**
|
||||
* @param string $userPrompt
|
||||
* @return null|object
|
||||
* @throws MalformedRequestException
|
||||
*/
|
||||
public function execute(string $userPrompt) {
|
||||
try {
|
||||
$data = [
|
||||
'model' => 'gpt-4-turbo', //gpt-4-turbo, gpt-4o-mini
|
||||
'messages' => [
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => $userPrompt
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$response = Http::withHeaders([
|
||||
'Authorization' => 'Bearer ' . config('openai.api_key')
|
||||
])->post(config('openai.base_url') . '/v1/chat/completions', $data);
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
return $data;
|
||||
} else {
|
||||
Log::info('CreatesChatGPTResponse: ' . $response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (\Illuminate\Http\Client\ConnectionException $exception) {
|
||||
$error = 'Failed to connect to ChatGPT API';
|
||||
throw new ConnectionErrorException($error, $exception->getMessage(), $userPrompt, $exception->getTraceAsString());
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
throw new MalformedRequestException('Unable to get correct response from ChatGPT API: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\Transactions\ControllersLogic;
|
||||
|
||||
|
||||
use App\Classes\Exceptions\InternalServerErrorException;
|
||||
use App\Classes\General\Abstracts\AbstractControllerLogic;
|
||||
use App\Classes\Modules\Transactions\Processors\ETLPurchaseOrderTransactionProcessor;
|
||||
use App\Classes\Modules\Transactions\Standards\Rules\CanETLPurchaseOrder;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ETLPurchaseOrderTransactionLogic extends AbstractControllerLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function notification(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'ETL Purchase Order',
|
||||
'message' => 'You have successfully etl purchase order document'
|
||||
];
|
||||
}
|
||||
|
||||
/** @var CanETLPurchaseOrder */
|
||||
private $canETLPurchaseOrder;
|
||||
|
||||
/** @var ETLPurchaseOrderTransactionProcessor */
|
||||
private $etlPurchaseOrderTransactionProcessor;
|
||||
|
||||
/**
|
||||
* ETLPurchaseOrderTransactionLogic constructor.
|
||||
* @param CanETLPurchaseOrder $canETLPurchaseOrder
|
||||
* @param ETLPurchaseOrderTransactionProcessor $etlPurchaseOrderTransactionProcessor
|
||||
*/
|
||||
public function __construct(CanETLPurchaseOrder $canETLPurchaseOrder, ETLPurchaseOrderTransactionProcessor $etlPurchaseOrderTransactionProcessor)
|
||||
{
|
||||
$this->canETLPurchaseOrder = $canETLPurchaseOrder;
|
||||
$this->etlPurchaseOrderTransactionProcessor = $etlPurchaseOrderTransactionProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param string $id
|
||||
* @return JsonResponse
|
||||
* @throws \App\Classes\Exceptions\MalformedRequestException
|
||||
* @throws \App\Classes\Exceptions\InternalServerErrorException
|
||||
*/
|
||||
public function logic(Request $request, $id = ''): JsonResponse
|
||||
{
|
||||
$this->canETLPurchaseOrder->passes();
|
||||
|
||||
if ($request->has('products') && is_array($request->input('products'))) {
|
||||
$products = $request->input('products');
|
||||
$productsMetadata = $request->input('productsMetadata');
|
||||
}
|
||||
|
||||
try{
|
||||
$products = $this->etlPurchaseOrderTransactionProcessor->execute($products, $productsMetadata);
|
||||
} catch (\Exception $exception) {
|
||||
throw new InternalServerErrorException('Something went wrong!');
|
||||
}
|
||||
|
||||
return $this->response($products);
|
||||
}
|
||||
}
|
||||
+63
-26
@@ -1,14 +1,17 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Classes\Modules\Transactions\ControllersLogic;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractControllerLogic;
|
||||
use App\Classes\Jobs\Commands\V2\ETLPurchaseOrderTransactionV2CommandJob;
|
||||
use App\Classes\Modules\Bookings\Services\FetchesBooking;
|
||||
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
|
||||
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
|
||||
use App\Classes\Modules\Transactions\Processors\CreatePurchaseOrderTransactionProcessor;
|
||||
use App\Classes\Modules\Transactions\Processors\ETLPurchaseOrderTransactionProcessor;
|
||||
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
|
||||
use App\Classes\Modules\Transactions\Standards\Rules\CanETLPurchaseOrder;
|
||||
use App\Classes\ValueObjects\Constants\ApprovalStatus;
|
||||
use App\Classes\ValueObjects\Constants\PaymentMethodType;
|
||||
use App\Classes\ValueObjects\Constants\TransactionType;
|
||||
@@ -25,13 +28,16 @@ class ImportPurchaseOrderTransactionLogic extends AbstractControllerLogic
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function notification():array {
|
||||
protected function notification(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'Update Purchase Order',
|
||||
'message' => 'You have successfully updated you booking\'s purchase order'
|
||||
'message' => $this->notificationMessage ?? "You have successfully updated your booking's purchase order",
|
||||
];
|
||||
}
|
||||
|
||||
private ?string $notificationMessage = null;
|
||||
|
||||
/** @var FetchesBooking */
|
||||
private $fetchesBooking;
|
||||
|
||||
@@ -41,17 +47,27 @@ class ImportPurchaseOrderTransactionLogic extends AbstractControllerLogic
|
||||
/** @var CreatePurchaseOrderTransactionProcessor */
|
||||
private $createPurchaseOrderTransactionProcessor;
|
||||
|
||||
/** @var ETLPurchaseOrderTransactionProcessor */
|
||||
private $etlPurchaseOrderTransactionProcessor;
|
||||
|
||||
/** @var CanETLPurchaseOrder */
|
||||
private $canETLPurchaseOrder;
|
||||
|
||||
/**
|
||||
* CreatePurchaseOrderTransactionLogic constructor.
|
||||
* ImportPurchaseOrderTransactionLogic constructor.
|
||||
* @param FetchesBooking $fetchesBooking
|
||||
* @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
|
||||
* @param CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor
|
||||
* @param ETLPurchaseOrderTransactionProcessor $etlPurchaseOrderTransactionProcessor
|
||||
* @param CanETLPurchaseOrder $canETLPurchaseOrder
|
||||
*/
|
||||
public function __construct(FetchesBooking $fetchesBooking, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor)
|
||||
public function __construct(FetchesBooking $fetchesBooking, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor, ETLPurchaseOrderTransactionProcessor $etlPurchaseOrderTransactionProcessor, CanETLPurchaseOrder $canETLPurchaseOrder)
|
||||
{
|
||||
$this->fetchesBooking = $fetchesBooking;
|
||||
$this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
|
||||
$this->createPurchaseOrderTransactionProcessor = $createPurchaseOrderTransactionProcessor;
|
||||
$this->etlPurchaseOrderTransactionProcessor = $etlPurchaseOrderTransactionProcessor;
|
||||
$this->canETLPurchaseOrder = $canETLPurchaseOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,34 +76,55 @@ class ImportPurchaseOrderTransactionLogic extends AbstractControllerLogic
|
||||
* @return JsonResponse
|
||||
* @throws \App\Classes\Exceptions\MalformedRequestException
|
||||
*/
|
||||
public function logic(Request $request, $id = '') : JsonResponse
|
||||
public function logic(Request $request, $id = ''): JsonResponse
|
||||
{
|
||||
$files = $request->file('files');
|
||||
$products = []; // Initialize
|
||||
if ($request->has('products') && is_array($request->input('products'))) {
|
||||
$this->canETLPurchaseOrder->passes();
|
||||
|
||||
$object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports');
|
||||
foreach ($object->getFiles() as $file){
|
||||
$collection = Excel::toCollection(null, json_decode($file)->file_info->original->file, null, null, true);
|
||||
$products = $request->input('products');
|
||||
$productsMetadata = $request->input('productsMetadata');
|
||||
|
||||
$sheet = $collection->first()->skip(1);
|
||||
$count = collect($products)
|
||||
->filter(function ($product) {
|
||||
return isset($product['description']) &&
|
||||
preg_match('/\p{Han}+/u', $product['description']);
|
||||
})
|
||||
->count();
|
||||
|
||||
$products = $sheet->map(function ($row) {
|
||||
Log::info($row);
|
||||
$stockCode = $row[0];
|
||||
$description = $row[1];
|
||||
$quantity = $row[2];
|
||||
$unit_price = $row[3];
|
||||
if($count > 10){
|
||||
ETLPurchaseOrderTransactionV2CommandJob::dispatch($products, $productsMetadata, $request->route('id'));
|
||||
$this->notificationMessage = "Please refresh page in a few minutes";
|
||||
return $this->response([]);
|
||||
}
|
||||
else{
|
||||
$products = $this->etlPurchaseOrderTransactionProcessor->execute($products, $productsMetadata);
|
||||
}
|
||||
} else {
|
||||
$object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports');
|
||||
foreach ($object->getFiles() as $file) {
|
||||
$collection = Excel::toCollection(null, json_decode($file)->file_info->original->file, null, null, true);
|
||||
|
||||
$sheet = $collection->first()->skip(1);
|
||||
|
||||
$products = $sheet->map(function ($row) {
|
||||
Log::info($row);
|
||||
$stockCode = $row[0];
|
||||
$description = $row[1];
|
||||
$quantity = $row[2];
|
||||
$unit_price = $row[3];
|
||||
|
||||
return [
|
||||
'stockCode' => $stockCode,
|
||||
'description' => $description,
|
||||
'quantity' => $quantity,
|
||||
'unit_price' => $unit_price
|
||||
];
|
||||
})->all();
|
||||
}
|
||||
|
||||
return [
|
||||
'stockCode' => $stockCode,
|
||||
'description' => $description,
|
||||
'quantity' => $quantity,
|
||||
'unit_price' => $unit_price
|
||||
];
|
||||
})->all();
|
||||
}
|
||||
|
||||
// dd($products);
|
||||
|
||||
/** @var Booking $booking */
|
||||
$booking = $this->fetchesBooking->execute(['id' => $request->route('id') ?? $id]);
|
||||
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\Transactions\Processors;
|
||||
|
||||
|
||||
use App\Classes\Exceptions\ResourceNotFoundException;
|
||||
use App\Classes\Modules\Bookings\Services\FetchesBooking;
|
||||
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
|
||||
use App\Classes\Modules\Transactions\Processors\CreatePurchaseOrderTransactionProcessor;
|
||||
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
|
||||
use App\Classes\Modules\OpenAI\Services\CreatesChatGPTResponse;
|
||||
use App\Classes\ValueObjects\Constants\KVPKey;
|
||||
use App\Classes\ValueObjects\Constants\ApprovalStatus;
|
||||
use App\Classes\ValueObjects\Constants\PaymentMethodType;
|
||||
use App\Classes\ValueObjects\Constants\TransactionType;
|
||||
use App\Models\KeyValuePair;
|
||||
use App\Models\Booking;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ETLPurchaseOrderTransactionProcessor
|
||||
{
|
||||
/** @var CreatesChatGPTResponse */
|
||||
private $createsChatGPTResponse;
|
||||
|
||||
/** @var FetchesBooking */
|
||||
private $fetchesBooking;
|
||||
|
||||
/** @var GeneratesTransactionBillNumber */
|
||||
private $generatesTransactionBillNumber;
|
||||
|
||||
/** @var CreatePurchaseOrderTransactionProcessor */
|
||||
private $createPurchaseOrderTransactionProcessor;
|
||||
|
||||
/**
|
||||
* ETLPurchaseOrderTransactionProcessor constructor.
|
||||
* @param FetchesBooking $fetchesBooking
|
||||
* @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
|
||||
* @param CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor
|
||||
* @param CreatesChatGPTResponse $createsChatGPTResponse
|
||||
*/
|
||||
public function __construct(FetchesBooking $fetchesBooking, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor, CreatesChatGPTResponse $createsChatGPTResponse)
|
||||
{
|
||||
$this->fetchesBooking = $fetchesBooking;
|
||||
$this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
|
||||
$this->createPurchaseOrderTransactionProcessor = $createPurchaseOrderTransactionProcessor;
|
||||
$this->createsChatGPTResponse = $createsChatGPTResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $products
|
||||
* @param array $productsMetadata
|
||||
* @param int|null $bookingId
|
||||
* @return array
|
||||
* @throws \App\Classes\Exceptions\MalformedRequestException
|
||||
* @throws \App\Classes\Exceptions\ResourceNotFoundException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function execute(array $products, array $productsMetadata, ?int $bookingId = null)
|
||||
{
|
||||
foreach ($products as &$product) {
|
||||
$product['quantity'] = (int) $product['quantity'];
|
||||
$product['unit_price'] = (float) $product['unit_price'];
|
||||
}
|
||||
|
||||
// Translate descriptions if they contain Chinese characters
|
||||
$products = collect($products)->map(function ($product) {
|
||||
if (isset($product['description']) && preg_match("/\p{Han}+/u", $product['description'])) {
|
||||
$kvp = KeyValuePair::where('key', KVPKey::CHATGPT_PROMPT_PREFIX . "TRANSLATE")->first();
|
||||
$userPrompt = '';
|
||||
if($kvp){
|
||||
$templateFromDb = $kvp->value;
|
||||
$productsMetadataJson = json_encode($product['description'], JSON_UNESCAPED_UNICODE);
|
||||
$userPrompt = str_replace('{{productDescription}}', $productsMetadataJson, $templateFromDb);
|
||||
}
|
||||
else{
|
||||
throw new ResourceNotFoundException('AI Setup incomplete!');
|
||||
}
|
||||
|
||||
$result = $this->createsChatGPTResponse->execute($userPrompt);
|
||||
$content = $result['choices'][0]['message']['content'] ?? null;
|
||||
|
||||
// 1. Try raw JSON
|
||||
$decoded = json_decode($content, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$json = $content;
|
||||
}
|
||||
// 2. Fallback to fenced ```json block
|
||||
elseif (preg_match('/```json\s*(\[[\s\S]*?\]|\{[\s\S]*?\})\s*```/i', $content, $matches)) {
|
||||
$json = $matches[1];
|
||||
}
|
||||
else {
|
||||
Log::warning('No JSON found in ChatGPT response', ['content' => $content]);
|
||||
return;
|
||||
}
|
||||
|
||||
$translations = json_decode($json, true);
|
||||
if($translations){
|
||||
$translatedText = $translations[0];
|
||||
$product['description'] = $translatedText;
|
||||
}
|
||||
}
|
||||
return $product;
|
||||
})->toArray();
|
||||
|
||||
if (is_array($productsMetadata) && count($productsMetadata) > 0) {
|
||||
$kvp = KeyValuePair::where('key', KVPKey::CHATGPT_PROMPT_PREFIX . "EXTRACT1")->first();
|
||||
$prompt = '';
|
||||
if($kvp){
|
||||
$templateFromDb = $kvp->value;
|
||||
$productsMetadataJson = json_encode($productsMetadata, JSON_UNESCAPED_UNICODE);
|
||||
$prompt = str_replace('{{productsMetadata}}', $productsMetadataJson, $templateFromDb);
|
||||
}
|
||||
else{
|
||||
throw new ResourceNotFoundException('AI Setup incomplete!');
|
||||
}
|
||||
|
||||
$result = $this->createsChatGPTResponse->execute($prompt);
|
||||
$content = $result['choices'][0]['message']['content'] ?? null;
|
||||
|
||||
$freight = 0;
|
||||
$preferential = 0;
|
||||
|
||||
if ($content) {
|
||||
if (preg_match('/```json\s*(\{.*?\})\s*```/s', $content, $matches)) { //If wrapped in ```json, extract it
|
||||
$json = $matches[1];
|
||||
} else {
|
||||
$json = trim($content);
|
||||
}
|
||||
|
||||
$fees = json_decode($json);
|
||||
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$freight = $fees->Freight ?? 0;
|
||||
$preferential = $fees->Preferential ?? 0;
|
||||
} else {
|
||||
Log::error('JSON decode failed', [
|
||||
'error' => json_last_error_msg(),
|
||||
'content' => $content
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add Freight as a separate product
|
||||
if($freight){
|
||||
$products[] = [
|
||||
'stockCode' => '',
|
||||
'description' => 'First-Mile Delivery',
|
||||
'quantity' => 1,
|
||||
'unit_price' => $freight,
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
// Add Preferential as a separate product (as negative price if needed)
|
||||
if($preferential){
|
||||
$products[] = [
|
||||
'stockCode' => '',
|
||||
'description' => 'Discount',
|
||||
'quantity' => 1,
|
||||
'unit_price' => $preferential,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if($bookingId){
|
||||
/** @var Booking $booking */
|
||||
$booking = $this->fetchesBooking->execute(['id' => $bookingId]);
|
||||
|
||||
$billNumber = $this->generatesTransactionBillNumber->execute('PO-');
|
||||
|
||||
$total = collect($products)->sum(function($product){
|
||||
return $product['quantity'] * floatval(str_replace(',', '', $product['unit_price']));
|
||||
});
|
||||
|
||||
$object = new TransactionObject($billNumber, TransactionType::PURCHASE_ORDER, $booking->company->id, 1,
|
||||
1, PaymentMethodType::CASH,
|
||||
$total, $total, $booking->fix_currency_id, $booking->fix_currency_id,
|
||||
1, 0, 0, null, ApprovalStatus::PENDING_SUBMISSION, $products);
|
||||
|
||||
|
||||
$transaction = $this->createPurchaseOrderTransactionProcessor->execute($booking, $object);
|
||||
}
|
||||
|
||||
return $products;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\Transactions\Standards\Rules;
|
||||
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractRule;
|
||||
use App\Classes\ValueObjects\Constants\RoleTypes;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CanETLPurchaseOrder extends AbstractRule
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorized($object): bool
|
||||
{
|
||||
if (in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $object
|
||||
* @return bool
|
||||
* @throws \App\Classes\Exceptions\RequestValidationException
|
||||
*/
|
||||
protected function validators($object): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $object
|
||||
* @return bool
|
||||
*/
|
||||
protected function criteria($object): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,4 +38,6 @@ class KVPKey
|
||||
|
||||
public const BOOKING_AMOUNT_UPDATE = 'BOOKING_AMOUNT_UPDATE';
|
||||
|
||||
public const CHATGPT_PROMPT_PREFIX = 'CHATGPT_PROMPT_';
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\KeyValuePairs;
|
||||
|
||||
|
||||
use App\Classes\Modules\KeyValuePairs\ControllersLogic\CreateChatGPTPromptKVPLogic;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CreateChatGPTPromptKVPController
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param CreateChatGPTPromptKVPLogic $logic
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function create(Request $request, CreateChatGPTPromptKVPLogic $logic): JsonResponse
|
||||
{
|
||||
return $logic->execute($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\KeyValuePairs;
|
||||
|
||||
|
||||
use App\Classes\Modules\KeyValuePairs\ControllersLogic\DeleteChatGPTPromptKVPLogic;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DeleteChatGPTPromptKVPController
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param DeleteChatGPTPromptKVPLogic $logic
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function delete(Request $request, DeleteChatGPTPromptKVPLogic $logic): JsonResponse
|
||||
{
|
||||
return $logic->execute($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\KeyValuePairs;
|
||||
|
||||
|
||||
use App\Classes\Modules\KeyValuePairs\ControllersLogic\ListChatGPTPromptsKVPLogic;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ListChatGPTPromptsKVPController
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param ListChatGPTPromptsKVPLogic $logic
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function list(Request $request, ListChatGPTPromptsKVPLogic $logic): JsonResponse
|
||||
{
|
||||
return $logic->execute($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\KeyValuePairs;
|
||||
|
||||
|
||||
use App\Classes\Modules\KeyValuePairs\ControllersLogic\UpdateChatGPTPromptKVPLogic;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UpdateChatGPTPromptKVPController
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param UpdateChatGPTPromptKVPLogic $logic
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function update(Request $request, UpdateChatGPTPromptKVPLogic $logic): JsonResponse
|
||||
{
|
||||
return $logic->execute($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Transactions;
|
||||
|
||||
|
||||
use App\Classes\Modules\Transactions\ControllersLogic\ETLPurchaseOrderTransactionLogic;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
||||
class ETLPurchaseOrderTransactionController
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param ETLPurchaseOrderTransactionLogic $logic
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function etl(Request $request, ETLPurchaseOrderTransactionLogic $logic) : JsonResponse {
|
||||
return $logic->execute($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ChatGPTPromptResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'key' => $this->key,
|
||||
'value' => $this->value,
|
||||
'created_at' => Carbon::parse($this->created_at)->format('d-m-Y'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'base_url' => env('OPENAI_BASE_URL', 'https://api.openai.com'),
|
||||
'api_key' => env('OPENAI_API_KEY', ''),
|
||||
'is_enabled' => env('OPENAI_IS_ENABLED', 'true'),
|
||||
];
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpdateValueColumnToLongtextInKeyValuePairsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('key_value_pairs', function (Blueprint $table) {
|
||||
$table->longText('value')->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('key_value_pairs', function (Blueprint $table) {
|
||||
$table->string('value')->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class MakeOwnerNullableInKeyValuePairs extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('key_value_pairs', function (Blueprint $table) {
|
||||
$table->string('owner_type')->nullable()->change();
|
||||
$table->unsignedBigInteger('owner_id')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('key_value_pairs', function (Blueprint $table) {
|
||||
$table->string('owner_type')->nullable(false)->change();
|
||||
$table->unsignedBigInteger('owner_id')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
Vendored
+22
-2
@@ -85,12 +85,32 @@ gulp.task('sourceCss', gulp.series('sourceImages', () => {
|
||||
.pipe(gulp.dest(pkg.paths.build.css));
|
||||
}));
|
||||
|
||||
// gulp.task('sourceJs', () => {
|
||||
// fancyLog('Compiling source JS dependencies');
|
||||
// return gulp.src(pkg.globs.sourceJs)
|
||||
// .pipe(plugins.plumber({errorHandler: onError}))
|
||||
// .pipe(plugins.print())
|
||||
// .pipe(plugins.concat('site.js'))
|
||||
// .pipe(plugins.uglifyEs())
|
||||
// .pipe(gulp.dest(pkg.paths.build.js));
|
||||
// });
|
||||
|
||||
const sourceJsKeepNames = [
|
||||
'pdf.js',
|
||||
'pdf-table-extractor.js',
|
||||
'pdf.worker.js',
|
||||
];
|
||||
|
||||
gulp.task('sourceJs', () => {
|
||||
fancyLog('Compiling source JS dependencies');
|
||||
|
||||
return gulp.src(pkg.globs.sourceJs)
|
||||
.pipe(plugins.plumber({errorHandler: onError}))
|
||||
.pipe(plugins.plumber({ errorHandler: onError }))
|
||||
.pipe(plugins.print())
|
||||
.pipe(plugins.concat('site.js'))
|
||||
.pipe(plugins.if(
|
||||
file => !sourceJsKeepNames.includes(path.basename(file.path)),
|
||||
plugins.concat('site.js')
|
||||
))
|
||||
.pipe(plugins.uglifyEs())
|
||||
.pipe(gulp.dest(pkg.paths.build.js));
|
||||
});
|
||||
|
||||
+499
@@ -0,0 +1,499 @@
|
||||
// modify from https://github.com/mozilla/pdf.js/blob/master/examples/node/pdf2svg.js
|
||||
pdf_table_extractor_progress = function(result){
|
||||
};
|
||||
|
||||
pdf_table_extractor = function(doc){
|
||||
var numPages = doc.numPages;
|
||||
var result = {};
|
||||
result.pageTables = [];
|
||||
result.numPages = numPages;
|
||||
result.currentPages = 0;
|
||||
|
||||
var transform_fn = function(m1, m2) {
|
||||
return [
|
||||
m1[0] * m2[0] + m1[2] * m2[1],
|
||||
m1[1] * m2[0] + m1[3] * m2[1],
|
||||
m1[0] * m2[2] + m1[2] * m2[3],
|
||||
m1[1] * m2[2] + m1[3] * m2[3],
|
||||
m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
|
||||
m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
|
||||
];
|
||||
};
|
||||
|
||||
var applyTransform_fn = function(p, m) {
|
||||
var xt = p[0] * m[0] + p[1] * m[2] + m[4];
|
||||
var yt = p[0] * m[1] + p[1] * m[3] + m[5];
|
||||
return [xt, yt];
|
||||
};
|
||||
|
||||
|
||||
var lastPromise = Promise.resolve(); // will be used to chain promises
|
||||
var loadPage = function (pageNum) {
|
||||
return doc.getPage(pageNum).then(function (page) {
|
||||
var verticles = [];
|
||||
var horizons = [];
|
||||
var merges = {};
|
||||
var merge_alias = {};
|
||||
var transformMatrix = [1,0,0,1,0,0];;
|
||||
var transformStack = [];
|
||||
|
||||
return page.getOperatorList().then(function (opList) {
|
||||
// Get rectangle first
|
||||
var showed = {};
|
||||
var REVOPS = [];
|
||||
for (var op in pdfjsLib.OPS) {
|
||||
REVOPS[pdfjsLib.OPS[op]] = op;
|
||||
}
|
||||
|
||||
var strokeRGBColor = null;
|
||||
var fillRGBColor = null;
|
||||
var current_x, current_y;
|
||||
var edges = [];
|
||||
var line_max_width = 2;
|
||||
var lineWidth = null;
|
||||
|
||||
while (opList.fnArray.length) {
|
||||
var fn = opList.fnArray.shift();
|
||||
var args = opList.argsArray.shift();
|
||||
if (pdfjsLib.OPS.constructPath == fn) {
|
||||
while (args[0].length) {
|
||||
op = args[0].shift();
|
||||
if (op == pdfjsLib.OPS.rectangle) {
|
||||
x = args[1].shift();
|
||||
y = args[1].shift();
|
||||
width = args[1].shift();
|
||||
height = args[1].shift();
|
||||
if (Math.min(width, height) < line_max_width) {
|
||||
edges.push({y:y, x:x, width:width, height:height, transform: transformMatrix});
|
||||
}
|
||||
} else if (op == pdfjsLib.OPS.moveTo) {
|
||||
current_x = args[1].shift();
|
||||
current_y = args[1].shift();
|
||||
} else if (op == pdfjsLib.OPS.lineTo) {
|
||||
x = args[1].shift();
|
||||
y = args[1].shift();
|
||||
|
||||
if(lineWidth == null) {
|
||||
if (current_x == x) {
|
||||
edges.push({
|
||||
y: Math.min(y, current_y),
|
||||
x: Math.min(x, current_x),
|
||||
height: Math.abs(y - current_y),
|
||||
transform: transformMatrix
|
||||
});
|
||||
} else if (current_y == y) {
|
||||
edges.push({
|
||||
x: Math.min(x, current_x),
|
||||
y: Math.min(y, current_y),
|
||||
width: Math.abs(x - current_x),
|
||||
transform: transformMatrix
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (current_x == x) {
|
||||
edges.push({y: Math.min(y, current_y), x: x - lineWidth / 2, width: lineWidth, height: Math.abs(y - current_y), transform: transformMatrix});
|
||||
} else if (current_y == y) {
|
||||
edges.push({x: Math.min(x, current_x), y: y - lineWidth / 2, height: lineWidth, width: Math.abs(x - current_x), transform: transformMatrix});
|
||||
}
|
||||
}
|
||||
current_x = x;
|
||||
current_y = y;
|
||||
} else {
|
||||
// throw ('constructPath ' + op);
|
||||
}
|
||||
}
|
||||
} else if (pdfjsLib.OPS.save == fn) {
|
||||
transformStack.push(transformMatrix);
|
||||
} else if (pdfjsLib.OPS.restore == fn ){
|
||||
transformMatrix = transformStack.pop();
|
||||
} else if (pdfjsLib.OPS.transform == fn) {
|
||||
transformMatrix = transform_fn(transformMatrix, args);
|
||||
} else if (pdfjsLib.OPS.setStrokeRGBColor == fn) {
|
||||
strokeRGBColor = args;
|
||||
} else if (pdfjsLib.OPS.setFillRGBColor == fn) {
|
||||
fillRGBColor = args;
|
||||
} else if (pdfjsLib.OPS.setLineWidth == fn) {
|
||||
lineWidth = args[0];
|
||||
} else if (['eoFill'].indexOf(REVOPS[fn]) >= 0) {
|
||||
} else if ('undefined' === typeof(showed[fn])) {
|
||||
showed[fn] = REVOPS[fn];
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
edges = edges.map(function(edge){
|
||||
var point1 = applyTransform_fn([edge.x, edge.y], edge.transform);
|
||||
var point2 = applyTransform_fn([edge.x + edge.width, edge.y + edge.height], edge.transform);
|
||||
return {
|
||||
x: Math.min(point1[0], point2[0]),
|
||||
y: Math.min(point1[1], point2[1]),
|
||||
width: Math.abs(point1[0] - point2[0]),
|
||||
height: Math.abs(point1[1] - point2[1]),
|
||||
};
|
||||
});
|
||||
// merge rectangle to verticle lines and horizon lines
|
||||
edges1 = JSON.parse(JSON.stringify(edges));
|
||||
edges1.sort(function(a, b){ return (a.x - b.x) || (a.y - b.y); });
|
||||
edges2 = JSON.parse(JSON.stringify(edges));
|
||||
edges2.sort(function(a, b){ return (a.y - b.y) || (a.x - b.x); });
|
||||
|
||||
// get verticle lines
|
||||
var current_x = null;
|
||||
var current_y = null;
|
||||
var current_height = 0;
|
||||
var lines = [];
|
||||
var lines_add_verticle = function(lines, top, bottom){
|
||||
var hit = false;
|
||||
for (var i = 0; i < lines.length; i ++) {
|
||||
if (lines[i].bottom < top || lines[i].top > bottom) {
|
||||
continue;
|
||||
}
|
||||
hit = true;
|
||||
|
||||
top = Math.min(lines[i].top, top);
|
||||
bottom = Math.max(lines[i].bottom, bottom);
|
||||
new_lines = [];
|
||||
if (i > 1) {
|
||||
news_lines = lines.slice(0, i - 1);
|
||||
}
|
||||
new_lines = new_lines.concat(lines.slice(i + 1));
|
||||
lines = new_lines;
|
||||
return lines_add_verticle(lines, top, bottom);
|
||||
}
|
||||
if (!hit) {
|
||||
lines.push({top: top, bottom: bottom});
|
||||
}
|
||||
return lines;
|
||||
};
|
||||
|
||||
while (edge = edges1.shift()) {
|
||||
// skip horizon lines
|
||||
if (edge.width > line_max_width) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// new verticle lines
|
||||
if (null === current_x || edge.x - current_x > line_max_width) {
|
||||
if (current_height > line_max_width) {
|
||||
lines = lines_add_verticle(lines, current_y, current_y + current_height);
|
||||
}
|
||||
if (null !== current_x && lines.length) {
|
||||
verticles.push({x: current_x, lines: lines});
|
||||
}
|
||||
current_x = edge.x;
|
||||
current_y = edge.y;
|
||||
current_height = 0;
|
||||
lines = [];
|
||||
}
|
||||
|
||||
if (Math.abs(current_y + current_height - edge.y) < 10) {
|
||||
current_height = edge.height + edge.y - current_y;
|
||||
} else {
|
||||
if (current_height > line_max_width) {
|
||||
lines = lines_add_verticle(lines, current_y, current_y + current_height);
|
||||
}
|
||||
current_y = edge.y;
|
||||
current_height = edge.height;
|
||||
}
|
||||
}
|
||||
if (current_height > line_max_width) {
|
||||
lines = lines_add_verticle(lines, current_y, current_y + current_height);
|
||||
}
|
||||
|
||||
// no table
|
||||
if (current_x === null || lines.length == 0) {
|
||||
return {};
|
||||
}
|
||||
verticles.push({x: current_x, lines: lines});
|
||||
|
||||
// Get horizon lines
|
||||
current_x = null;
|
||||
current_y = null;
|
||||
var current_width = 0;
|
||||
var lines_add_horizon = function(lines, left, right){
|
||||
var hit = false;
|
||||
for (var i = 0; i < lines.length; i ++) {
|
||||
if (lines[i].right < left || lines[i].left > right) {
|
||||
continue;
|
||||
}
|
||||
hit = true;
|
||||
|
||||
left = Math.min(lines[i].left, left);
|
||||
right = Math.max(lines[i].right, right);
|
||||
new_lines = [];
|
||||
if (i > 1) {
|
||||
news_lines = lines.slice(0, i - 1);
|
||||
}
|
||||
new_lines = new_lines.concat(lines.slice(i + 1));
|
||||
lines = new_lines;
|
||||
return lines_add_horizon(lines, left, right);
|
||||
}
|
||||
if (!hit) {
|
||||
lines.push({left: left, right: right});
|
||||
}
|
||||
return lines;
|
||||
};
|
||||
|
||||
while (edge = edges2.shift()) {
|
||||
if (edge.height > line_max_width) {
|
||||
continue;
|
||||
}
|
||||
if (null === current_y || edge.y - current_y > line_max_width) {
|
||||
if (current_width > line_max_width) {
|
||||
lines = lines_add_horizon(lines, current_x, current_x + current_width);
|
||||
}
|
||||
if (null !== current_y && lines.length) {
|
||||
horizons.push({y: current_y, lines: lines});
|
||||
}
|
||||
current_x = edge.x;
|
||||
current_y = edge.y;
|
||||
current_width = 0;
|
||||
lines = [];
|
||||
}
|
||||
|
||||
if (Math.abs(current_x + current_width - edge.x) < 10) {
|
||||
current_width = edge.width + edge.x - current_x;
|
||||
} else {
|
||||
if (current_width > line_max_width) {
|
||||
lines = lines_add_horizon(lines, current_x, current_x + current_width);
|
||||
}
|
||||
current_x = edge.x;
|
||||
current_width = edge.width;
|
||||
}
|
||||
}
|
||||
if (current_width > line_max_width) {
|
||||
lines = lines_add_horizon(lines, current_x, current_x + current_width);
|
||||
}
|
||||
// no table
|
||||
if (current_y === null || lines.length == 0) {
|
||||
return {};
|
||||
}
|
||||
horizons.push({y: current_y, lines: lines});
|
||||
|
||||
var search_index = function(v, list) {
|
||||
for (var i = 0; i < list.length; i ++) {
|
||||
if (Math.abs(list[i] - v) < 5) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
// handle merge cells
|
||||
x_list = verticles.map(function(a){ return a.x; });
|
||||
|
||||
// check top_out and bottom_out
|
||||
var y_list = horizons.map(function(a){ return a.y; }).sort(function(a, b) { return b - a; });
|
||||
var y_max = verticles
|
||||
.map(function(verticle) { return verticle.lines[0].bottom; })
|
||||
.sort().reverse()[0];
|
||||
var y_min = verticles
|
||||
.map(function(verticle) { return verticle.lines[verticle.lines.length - 1].top; })
|
||||
.sort()[0];
|
||||
var top_out = search_index(y_min, y_list) == -1 ? 1 : 0;
|
||||
var bottom_out = search_index(y_max, y_list) == -1 ? 1 : 0;
|
||||
|
||||
var verticle_merges = {};
|
||||
// skip the 1st lines and final lines
|
||||
for (var r = 0; r < horizons.length - 2 + top_out + bottom_out; r ++) {
|
||||
hor = horizons[bottom_out + horizons.length - r - 2];
|
||||
lines = hor.lines.slice(0);
|
||||
col = search_index(lines[0].left, x_list);
|
||||
if (col != 0) {
|
||||
for (var c = 0; c < col; c ++) {
|
||||
verticle_merges[[r, c].join('-')] = {row: r, col: c, width: 1, height: 2};
|
||||
}
|
||||
}
|
||||
while (line = lines.shift()) {
|
||||
left_col = search_index(line.left, x_list);
|
||||
right_col = search_index(line.right, x_list);
|
||||
if (left_col != col) {
|
||||
for (var c = col; c < left_col; c ++) {
|
||||
verticle_merges[[r, c].join('-')] = {row: r, col: c, width: 1, height: 2};
|
||||
}
|
||||
}
|
||||
col = right_col;
|
||||
}
|
||||
if (col != verticles.length - 1 + top_out) {
|
||||
for (var c = col; c < verticles.length - 1 + top_out; c ++) {
|
||||
verticle_merges[[r, c].join('-')] = {row: r, col: c, width: 1, height: 2};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var merged = false;
|
||||
for (var r_c in verticle_merges) {
|
||||
var m = verticle_merges[r_c];
|
||||
var final_id = [m.row + m.height - 1, m.col + m.width - 1].join('-');
|
||||
if ('undefined' !== typeof(verticle_merges[final_id])) {
|
||||
verticle_merges[r_c].height += verticle_merges[final_id].height - 1;
|
||||
delete(verticle_merges[final_id]);
|
||||
merged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!merged) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var horizon_merges = {};
|
||||
|
||||
for (var c = 0; c < verticles.length - 2; c ++) {
|
||||
ver = verticles[c + 1];
|
||||
lines = ver.lines.slice(0);
|
||||
row = search_index(lines[0].bottom, y_list) + bottom_out;
|
||||
if (row != 0) {
|
||||
for (var r = 0; r < row; r ++) {
|
||||
horizon_merges[[r, c].join('-')] = {row: r, col: c, width: 2, height: 1};
|
||||
}
|
||||
}
|
||||
while (line = lines.shift()) {
|
||||
top_row = search_index(line.top, y_list);
|
||||
if (top_row == -1) {
|
||||
top_row = y_list.length + bottom_out;
|
||||
} else {
|
||||
top_row += bottom_out;
|
||||
}
|
||||
bottom_row = search_index(line.bottom, y_list) + bottom_out;
|
||||
if (bottom_row != row) {
|
||||
for (var r = bottom_row; r < row; r ++) {
|
||||
horizon_merges[[r, c].join('-')] = {row: r, col: c, width: 2, height: 1};
|
||||
}
|
||||
}
|
||||
row = top_row;
|
||||
}
|
||||
if (row != horizons.length - 1 + bottom_out + top_out) {
|
||||
for (var r = row; r < horizons.length - 1 + bottom_out + top_out; r ++) {
|
||||
horizon_merges[[r, c].join('-')] = {row: r, col: c, width: 2, height: 1};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (top_out) {
|
||||
horizons.unshift({y: y_min, lines: []});
|
||||
}
|
||||
if (bottom_out) {
|
||||
horizons.push({y:y_max, lines:[]});
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var merged = false;
|
||||
for (var r_c in horizon_merges) {
|
||||
var m = horizon_merges[r_c];
|
||||
var final_id = [m.row + m.height - 1, m.col + m.width - 1].join('-');
|
||||
if ('undefined' !== typeof(horizon_merges[final_id])) {
|
||||
horizon_merges[r_c].width += horizon_merges[final_id].width - 1;
|
||||
delete(horizon_merges[final_id]);
|
||||
merged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!merged) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
merges = verticle_merges;
|
||||
for (var id in horizon_merges) {
|
||||
if ('undefined' !== typeof(merges[id])) {
|
||||
merges[id].width = horizon_merges[id].width;
|
||||
} else {
|
||||
merges[id] = horizon_merges[id];
|
||||
}
|
||||
}
|
||||
for (var id in merges) {
|
||||
for (var c = 0; c < merges[id].width; c ++) {
|
||||
for (var r = 0; r < merges[id].height; r ++) {
|
||||
if (c == 0 && r == 0) {
|
||||
continue;
|
||||
}
|
||||
delete(merges[[r + merges[id].row, c + merges[id].col].join('-')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
merge_alias = {};
|
||||
for (var id in merges) {
|
||||
for (var c = 0; c < merges[id].width; c ++) {
|
||||
for (var r = 0; r < merges[id].height; r ++) {
|
||||
if (r == 0 && c == 0) {
|
||||
continue;
|
||||
}
|
||||
merge_alias[[merges[id].row + r, merges[id].col + c].join('-')] = [merges[id].row, merges[id].col].join('-');
|
||||
}
|
||||
}
|
||||
}
|
||||
}).then(function(){
|
||||
return page.getTextContent().then(function (content) {
|
||||
tables = [];
|
||||
table_pos = [];
|
||||
for (var i = 0; i < horizons.length - 1; i ++) {
|
||||
tables[i] = [];
|
||||
table_pos[i] = [];
|
||||
for (var j = 0; j < verticles.length - 1; j ++) {
|
||||
tables[i][j] = '';
|
||||
table_pos[i][j] = null;
|
||||
}
|
||||
}
|
||||
while (item = content.items.shift()) {
|
||||
x = item.transform[4];
|
||||
y = item.transform[5];
|
||||
|
||||
var col = -1;
|
||||
for (var i = 0; i < verticles.length - 1 ; i ++) {
|
||||
if (x >= verticles[i].x && x < verticles[i + 1].x) {
|
||||
col = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (col == -1) {
|
||||
continue;
|
||||
}
|
||||
var row = -1;
|
||||
for (var i = 0; i < horizons.length - 1 ; i ++) {
|
||||
if (y >= horizons[i].y && y < horizons[i + 1].y) {
|
||||
row = horizons.length - i - 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (row == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('undefined' !== typeof(merge_alias[row + '-' + col])) {
|
||||
id = merge_alias[row + '-' + col];
|
||||
row = id.split('-')[0];
|
||||
col = id.split('-')[1];
|
||||
}
|
||||
if (null !== table_pos[row][col] && Math.abs(table_pos[row][col] - y) > 5) {
|
||||
tables[row][col] += "\n";
|
||||
}
|
||||
table_pos[row][col] = y;
|
||||
tables[row][col] += item.str;
|
||||
}
|
||||
if (tables.length) {
|
||||
result.pageTables.push({
|
||||
page: pageNum,
|
||||
tables: tables,
|
||||
merges: merges,
|
||||
merge_alias: merge_alias,
|
||||
width: verticles.length - 1,
|
||||
height: horizons.length - 1,
|
||||
});
|
||||
}
|
||||
result.currentPages ++;
|
||||
if ('function' === typeof(pdf_table_extractor_progress)) {
|
||||
pdf_table_extractor_progress(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
for (var i = 1; i <= numPages; i++) {
|
||||
lastPromise = lastPromise.then(loadPage.bind(null, i));
|
||||
}
|
||||
return lastPromise.then(function(){
|
||||
return result;
|
||||
});
|
||||
};
|
||||
Vendored
+14074
File diff suppressed because it is too large
Load Diff
Vendored
+47415
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,539 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<div class="dropbox p-5 mb-4 bg-light text-secondary rounded text-center position-relative"
|
||||
@dragenter.prevent="onDragEnter"
|
||||
@dragover.prevent="onDragOver"
|
||||
@drop.prevent="onDrop"
|
||||
:class="{ 'bg-secondary text-white': isDragging }">
|
||||
<div id="drop">
|
||||
<span>
|
||||
<img class="logo mb-3" src="logo.png" alt="Your Logo" v-if="false"><br>
|
||||
Drag and drop your PDF file here
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="filesProcessed" class="alert alert-success mt-3 mb-0" role="alert">
|
||||
<strong>{{ fileName }}</strong> content extracted. <br/>
|
||||
<div class="row m-b-10">
|
||||
<div class="col">
|
||||
<span class="text-complete pointer requestModal text-underline" data-type="previewExtractedTable">Preview</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-center mt-3">
|
||||
<label for="pdf-file-input"
|
||||
class="btn btn-sm btn-outline-complete rounded-0 pointer flex-fill mx-1 text-center mb-0">
|
||||
Browse files
|
||||
</label>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-complete rounded-0 pointer flex-fill mx-1"
|
||||
v-if="isPurchaseOrderWithTablesPDF"
|
||||
@click="exportToCSV"
|
||||
:disabled="isExporting">
|
||||
{{ isExporting ? 'Exporting...' : 'Export Extracted Content (csv)' }}
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-complete rounded-0 pointer flex-fill mx-1"
|
||||
v-if="isPurchaseOrderWithTablesPDF"
|
||||
@click="importToSystem"
|
||||
:disabled="isImporting">
|
||||
{{ isImporting ? 'Uploading...' : 'Continue Upload' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input type="file" id="pdf-file-input" @change="onFileChange" accept="application/pdf" style="display: none;" />
|
||||
</div>
|
||||
|
||||
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="previewExtractedTable" size="large">
|
||||
<div class="row p-t-25 text-left">
|
||||
<div class="col bg-white padding-40 b-rad-lg">
|
||||
<div class="modal-header">
|
||||
<h2>Preview Extracted Content</h2>
|
||||
</div>
|
||||
<div id="html-result" v-if="tables.length > 0">
|
||||
<div v-for="(table, tIndex) in tables" :key="tIndex" class="table-wrapper">
|
||||
<table border="1">
|
||||
<tbody>
|
||||
<tr v-for="(row, rIndex) in table" :key="rIndex">
|
||||
<td v-for="(cell, cIndex) in row"
|
||||
:key="cIndex"
|
||||
:colspan="cell.colspan"
|
||||
:rowspan="cell.rowspan"
|
||||
v-if="!cell.hidden">
|
||||
{{ cell.text }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</modal-component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { integer } from 'vuelidate/lib/validators';
|
||||
|
||||
export default {
|
||||
name: 'PdfTableExtractorComponent',
|
||||
props: {
|
||||
section:{
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
bookingId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isDragging: false,
|
||||
tables: [],
|
||||
nonTableData: [],
|
||||
allTablesForExportImport: [],
|
||||
isExporting: false,
|
||||
isImporting: false,
|
||||
filesProcessed: false,
|
||||
showPreviewModal: false,
|
||||
fileName: '',
|
||||
translationCache: {},
|
||||
mergeTableRemoveFirstLine: false,
|
||||
parameters: {
|
||||
products: [],
|
||||
productsMetadata: []
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isPurchaseOrderWithTablesPDF() {
|
||||
return this.allTablesForExportImport.length > 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onDragEnter(e) {
|
||||
this.isDragging = true;
|
||||
},
|
||||
onDragOver(e) {
|
||||
this.isDragging = true;
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
},
|
||||
onDrop(e) {
|
||||
this.isDragging = false;
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length) {
|
||||
this.processFile(files[0]);
|
||||
}
|
||||
},
|
||||
onFileChange(e) {
|
||||
const files = e.target.files;
|
||||
if (files.length) {
|
||||
this.processFile(files[0]);
|
||||
}
|
||||
},
|
||||
async processFile(file) {
|
||||
this.tables = [];
|
||||
this.allTablesForExportImport = [];
|
||||
this.filesProcessed = false;
|
||||
this.fileName = file.name;
|
||||
this.parameters.products = [];
|
||||
this.parameters.productsMetadata = [];
|
||||
|
||||
try {
|
||||
await this.loadDependencies();
|
||||
} catch (error) {
|
||||
console.error("Failed to load PDF dependencies:", error);
|
||||
alert("Critical Error: Could not load PDF libraries.");
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const data = e.target.result;
|
||||
this.parseContent(data);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
},
|
||||
async loadDependencies() {
|
||||
if (typeof pdfjsLib !== 'undefined' && typeof pdf_table_extractor !== 'undefined') {
|
||||
return;
|
||||
}
|
||||
if (typeof pdfjsLib === 'undefined') {
|
||||
await this.loadScript('/js/pdf.js');
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = '/js/pdf.worker.js';
|
||||
}
|
||||
if (typeof pdf_table_extractor === 'undefined') {
|
||||
await this.loadScript('/js/pdf-table-extractor.js');
|
||||
}
|
||||
},
|
||||
loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (document.querySelector(`script[src="${src}"]`)) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
},
|
||||
parseContent(content) {
|
||||
if (typeof pdfjsLib === 'undefined' || typeof pdf_table_extractor === 'undefined') {
|
||||
console.error('PDF.js or pdf-table-extractor not loaded.');
|
||||
alert('Required PDF libraries are missing.');
|
||||
return;
|
||||
}
|
||||
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = '/js/pdf.worker.js';
|
||||
}
|
||||
|
||||
const loadingTask = pdfjsLib.getDocument(content);
|
||||
|
||||
loadingTask.promise
|
||||
.then(async (doc) => {
|
||||
const result = await pdf_table_extractor(doc);
|
||||
this.processExtractedTables(result);
|
||||
await this.extractNonTableData4(doc, result);
|
||||
this.filesProcessed = true;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error extracting tables:", error);
|
||||
alert("Error parsing PDF.");
|
||||
});
|
||||
},
|
||||
processExtractedTables(result) {
|
||||
let extractedTables = [];
|
||||
let exportData = [];
|
||||
const pages = [...result.pageTables];
|
||||
|
||||
pages.forEach(page_tables => {
|
||||
const tables = page_tables.tables;
|
||||
const merge_alias = page_tables.merge_alias;
|
||||
const merges = page_tables.merges;
|
||||
|
||||
if (tables.length > 0) {
|
||||
let currentTable = [];
|
||||
|
||||
for (let r = 0; r < tables.length; r++) {
|
||||
if (this.mergeTableRemoveFirstLine && page_tables.page != 1 && r == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let rowCells = [];
|
||||
let rowForExport = [];
|
||||
|
||||
for (let c = 0; c < tables[r].length; c++) {
|
||||
let r_c = [r, c].join('-');
|
||||
|
||||
if (merge_alias[r_c]) {
|
||||
rowCells.push({ hidden: true });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (merges[r_c] && merges[r_c].width > 2) {
|
||||
rowCells.push({ hidden: true });
|
||||
continue;
|
||||
}
|
||||
|
||||
let cell = {
|
||||
text: tables[r][c],
|
||||
hidden: false,
|
||||
rowspan: 1,
|
||||
colspan: 1
|
||||
};
|
||||
|
||||
if (merges[r_c]) {
|
||||
if (merges[r_c].width > 1) cell.colspan = merges[r_c].width;
|
||||
if (merges[r_c].height > 1) cell.rowspan = merges[r_c].height;
|
||||
}
|
||||
|
||||
rowCells.push(cell);
|
||||
let value = tables[r][c];
|
||||
let cleanValue = value.replace(/[\r\n]+/g, ' ').trim();
|
||||
rowForExport.push(cleanValue);
|
||||
}
|
||||
|
||||
currentTable.push(rowCells);
|
||||
|
||||
if (rowForExport.length > 1) {
|
||||
const limitedRowForExport = rowForExport.slice(0, 7);
|
||||
const headerRow = exportData[0];
|
||||
const isSameAsHeader = headerRow && limitedRowForExport.every((val, idx) => val === headerRow[idx]);
|
||||
|
||||
if (!isSameAsHeader) {
|
||||
exportData.push(limitedRowForExport);
|
||||
}
|
||||
}
|
||||
}
|
||||
extractedTables.push(currentTable);
|
||||
}
|
||||
});
|
||||
|
||||
this.tables = extractedTables;
|
||||
this.allTablesForExportImport = exportData;
|
||||
},
|
||||
async extractNonTableData4(doc, result) {
|
||||
this.nonTableData = [];
|
||||
|
||||
for (let i = 1; i <= doc.numPages; i++) {
|
||||
const page = await doc.getPage(i);
|
||||
const content = await page.getTextContent();
|
||||
const pageStrItems = content.items;
|
||||
const pageTableData = result.pageTables.find(pt => pt.page === i);
|
||||
const tableBoxes = [];
|
||||
|
||||
if (pageTableData?.tables?.length) {
|
||||
pageTableData.tables.forEach((table, tIdx) => {
|
||||
let dataRowCount = 0;
|
||||
table.forEach(row => {
|
||||
const hasData = Array.isArray(row)
|
||||
? row.some(cell => cell.trim().length > 0)
|
||||
: String(row).trim().length > 0;
|
||||
if (hasData) dataRowCount++;
|
||||
});
|
||||
|
||||
if (dataRowCount > 1) {
|
||||
let xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity;
|
||||
|
||||
table.forEach((row, rIdx) => {
|
||||
if (dataRowCount === 1 && rIdx !== 0) return;
|
||||
|
||||
if (Array.isArray(row)) {
|
||||
row.forEach(cell => {
|
||||
const lines = String(cell).split('\n').map(l => l.trim()).filter(Boolean);
|
||||
lines.forEach(line => {
|
||||
const matched = pageStrItems.find(item => item.str.includes(line));
|
||||
if (matched) {
|
||||
const x = matched.transform[4];
|
||||
const y = matched.transform[5];
|
||||
const w = matched.width || 0;
|
||||
xMin = Math.min(xMin, x);
|
||||
xMax = Math.max(xMax, x + w);
|
||||
yMin = Math.min(yMin, y);
|
||||
yMax = Math.max(yMax, y);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (typeof row === 'string') {
|
||||
const lines = row.split('\n').map(l => l.trim()).filter(Boolean);
|
||||
lines.forEach(line => {
|
||||
const matched = pageStrItems.find(item => item.str.includes(line));
|
||||
if (matched) {
|
||||
const x = matched.transform[4];
|
||||
const y = matched.transform[5];
|
||||
const w = matched.width || 0;
|
||||
xMin = Math.min(xMin, x);
|
||||
xMax = Math.max(xMax, x + w);
|
||||
yMin = Math.min(yMin, y);
|
||||
yMax = Math.max(yMax, y);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (xMin < Infinity) {
|
||||
tableBoxes.push({ xMin, xMax, yMin, yMax });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const filteredItems = pageStrItems.filter(item => {
|
||||
const x = item.transform[4];
|
||||
const y = item.transform[5];
|
||||
const str = item.str.trim();
|
||||
if (!str) return false;
|
||||
|
||||
const isInTable = tableBoxes.some(box =>
|
||||
x >= box.xMin && x <= box.xMax &&
|
||||
y >= box.yMin && y <= box.yMax
|
||||
);
|
||||
|
||||
return !isInTable;
|
||||
});
|
||||
|
||||
const lines = {};
|
||||
const tolerance = 5;
|
||||
|
||||
filteredItems.forEach(item => {
|
||||
const y = item.transform[5];
|
||||
const x = item.transform[4];
|
||||
let added = false;
|
||||
|
||||
for (const lineY in lines) {
|
||||
if (Math.abs(parseFloat(lineY) - y) < tolerance) {
|
||||
lines[lineY].push({ str: item.str, x });
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
lines[y] = [{ str: item.str, x }];
|
||||
}
|
||||
});
|
||||
|
||||
const sortedLineKeys = Object.keys(lines)
|
||||
.sort((a, b) => parseFloat(b) - parseFloat(a));
|
||||
|
||||
const readableContent = sortedLineKeys.map(key => {
|
||||
const lineItems = lines[key].sort((a, b) => a.x - b.x);
|
||||
return lineItems.map(i => i.str).join(' ');
|
||||
});
|
||||
|
||||
if (readableContent.length) {
|
||||
this.nonTableData.push({
|
||||
page: i,
|
||||
content: readableContent
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
exportToCSV() {
|
||||
if (this.isExporting) return;
|
||||
this.isExporting = true;
|
||||
|
||||
this.parameters.products = [];
|
||||
this.parameters.productsMetadata = [];
|
||||
for (let index = 0; index < this.allTablesForExportImport.length; index++) {
|
||||
if (index === 0) continue;
|
||||
|
||||
const rowArray = this.allTablesForExportImport[index];
|
||||
const partNumber = rowArray[1] || '';
|
||||
let productName = rowArray[2] || '';
|
||||
const specification = rowArray[3] || '';
|
||||
const quantity = rowArray[4] || '';
|
||||
const rawUnitPrice = rowArray[5] || '';
|
||||
const unitPrice = rawUnitPrice.match(/\d+\.?\d*/)?.[0] || '';
|
||||
|
||||
productName = productName.replace(/^["'](.*)["']$/, '$1');
|
||||
|
||||
this.parameters.products.push({
|
||||
stockCode: partNumber,
|
||||
description: productName + ' | ' + specification,
|
||||
quantity: quantity,
|
||||
unit_price: unitPrice
|
||||
});
|
||||
this.parameters.productsMetadata = this.nonTableData;
|
||||
}
|
||||
this.submit(route('api.transaction.po.etl', this.bookingId), 'post', this.section + 'PDFExport', true, true);
|
||||
},
|
||||
importToSystem() {
|
||||
if (this.isImporting) return;
|
||||
this.isImporting = true;
|
||||
|
||||
if(this.parameters.products && this.parameters.products.length === 0){
|
||||
for (let index = 0; index < this.allTablesForExportImport.length; index++) {
|
||||
if (index === 0) continue;
|
||||
|
||||
const rowArray = this.allTablesForExportImport[index];
|
||||
const partNumber = rowArray[1] || '';
|
||||
let productName = rowArray[2] || '';
|
||||
const specification = rowArray[3] || '';
|
||||
const quantity = rowArray[4] || '';
|
||||
const rawUnitPrice = rowArray[5] || '';
|
||||
const unitPrice = rawUnitPrice.match(/\d+\.?\d*/)?.[0] || '';
|
||||
|
||||
productName = productName.replace(/^["'](.*)["']$/, '$1');
|
||||
|
||||
this.parameters.products.push({
|
||||
stockCode: partNumber,
|
||||
description: productName + ' | ' + specification,
|
||||
quantity: quantity,
|
||||
unit_price: unitPrice
|
||||
});
|
||||
this.parameters.productsMetadata = this.nonTableData;
|
||||
}
|
||||
}
|
||||
this.submit(route('api.transaction.po.import', this.bookingId), 'post', this.section + 'PDFImport', true, true);
|
||||
},
|
||||
escapeCSV(value) {
|
||||
if (value === null || value === undefined) return '';
|
||||
const str = value.toString();
|
||||
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
||||
return `"${str.replace(/"/g, '""')}"`;
|
||||
}
|
||||
return str;
|
||||
},
|
||||
containsChinese(text) {
|
||||
return /[\u4e00-\u9fff]/.test(text);
|
||||
},
|
||||
successHandler(response, section){
|
||||
if(this.section + 'PDFImport' === section){
|
||||
this.updateList()
|
||||
this.isImporting = false;
|
||||
}
|
||||
else if(this.section + 'PDFExport' === section){
|
||||
try {
|
||||
const BOM = '\uFEFF';
|
||||
let csvContent = "stock code,description,quantity,unit price\r\n";
|
||||
response.payload.forEach(item => {
|
||||
const formattedRow = [
|
||||
item.stockCode,
|
||||
this.escapeCSV(item.description),
|
||||
item.quantity,
|
||||
item.unit_price
|
||||
].join(',');
|
||||
|
||||
csvContent += formattedRow + "\r\n";
|
||||
});
|
||||
|
||||
const blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.setAttribute("download", "order_details.csv");
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
this.parameters.products = response.payload;
|
||||
this.parameters.productsMetadata = [];
|
||||
} catch (e) {
|
||||
console.error("Export failed:", e);
|
||||
alert("Export failed. Please try again.");
|
||||
} finally {
|
||||
this.isExporting = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
errorHandler(response, statusCode, section){
|
||||
this.isExporting = false;
|
||||
this.isImporting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dropbox {
|
||||
border: 2px dashed #bbb;
|
||||
border-radius: 5px;
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
margin-bottom: 20px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
margin-bottom: 20px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -29,12 +29,17 @@
|
||||
</div>
|
||||
<div class="row m-t-20" v-if="!submitted">
|
||||
<div class="col">
|
||||
<div class="row m-b-15">
|
||||
<div class="row" v-if="$store.getters.isAdmin">
|
||||
<div class="col">
|
||||
<span class="pointer text-complete fs-11 text-upper text-underline" @click="useUploadCsvPo = !useUploadCsvPo">{{ useUploadCsvPo ? '< Back to Manual Enter Purchase Order' : 'Click here to Upload PURCHASE ORDER CSV' }}</span>
|
||||
<span class="pointer text-complete fs-11 text-upper text-underline" @click="useUploadPdfPo = !useUploadPdfPo; useUploadCsvPo = false;">{{ useUploadPdfPo ? '< Back to Manual Enter Purchase Order' : 'Click here to Upload PURCHASE ORDER PDF' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="!useUploadCsvPo">
|
||||
<div class="row m-b-15">
|
||||
<div class="col">
|
||||
<span class="pointer text-complete fs-11 text-upper text-underline" @click="useUploadCsvPo = !useUploadCsvPo; useUploadPdfPo = false;">{{ useUploadCsvPo ? '< Back to Manual Enter Purchase Order' : 'Click here to Upload PURCHASE ORDER CSV' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="!useUploadCsvPo && !useUploadPdfPo">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-4 pr-md-1">
|
||||
@@ -112,6 +117,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="useUploadPdfPo">
|
||||
<div class="col">
|
||||
<pdf-table-extractor-component :booking-id="data.id" :section="section"></pdf-table-extractor-component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -325,6 +335,7 @@
|
||||
interval:false,
|
||||
submitted: false,
|
||||
useUploadCsvPo: false,
|
||||
useUploadPdfPo: false,
|
||||
product: {
|
||||
stockCode: '',
|
||||
description: '',
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="row m-b-15 parentContainer">
|
||||
<div class="col">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-3">
|
||||
<div class="row">
|
||||
<div class="col-auto p-r-10">
|
||||
<div class="font-heading all-caps fs-8 muted">Date</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="font-heading fs-10">{{item.created_at}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="row">
|
||||
<div class="col-auto p-r-10">
|
||||
<div class="font-heading all-caps fs-8 muted">Title</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="font-heading fs-10">{{item.key | truncate(50)}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col-auto p-r-10">
|
||||
<div class="font-heading all-caps fs-8 muted">Prompt</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="font-heading fs-10">{{item.value | truncate(100)}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto parentContainer">
|
||||
<div class="row align-items-center">
|
||||
<template v-if="$store.getters.isAdmin">
|
||||
<div class="col-auto no-padding">
|
||||
<button class="btn btn-xs btn-complete b-rad-none m-r-5 requestModal" data-type="editModal">
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto no-padding">
|
||||
<button class="btn btn-xs btn-outline-danger b-rad-none m-r-5 requestModal" data-type="deleteChatGPTPrompt" v-if="item.key !== 'CHATGPT_PROMPT_TRANSLATE' && item.key !== 'CHATGPT_PROMPT_EXTRACT1'">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<modal-form-component :data="data" size="large" section="allChatGPTPromptsSection">
|
||||
<template slot="form" slot-scope="{section}">
|
||||
<chat-gpt-form-component :data="data" :section="section"></chat-gpt-form-component>
|
||||
</template>
|
||||
</modal-form-component>
|
||||
|
||||
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="deleteChatGPTPrompt">
|
||||
<delete-chat-gpt-form-component :data="item" section="allChatGPTPromptsSection" class="text-center"></delete-chat-gpt-form-component>
|
||||
</modal-component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentHandler from '../../../general/mixins/componentHandler';
|
||||
export default {
|
||||
props: {
|
||||
section: {
|
||||
default: 'singleChatGPTPromptSection'
|
||||
}
|
||||
},
|
||||
mixins: [componentHandler]
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<loading-component style="height: 50px; 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-20">
|
||||
<div class="col">
|
||||
<h6 class="all-caps m-b-5 bold no-margin">{{this.data?"Update":"Create"}} ChatGPT Prompt</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-b-5 animate__animated animate__fadeInUpBig animate__fast" v-if="error">
|
||||
<div class="col">
|
||||
<small class="bold fs-10 text-danger">{{error}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-b-10">
|
||||
<div class="col">
|
||||
<validation-wrapper-component :validator="$v.parameters.key">
|
||||
<label>Title</label>
|
||||
<input type="text" class="form-control" v-model="parameters.key" :disabled="this.data">
|
||||
</validation-wrapper-component>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<validation-wrapper-component :validator="$v.parameters.value">
|
||||
<label>Prompt</label>
|
||||
<textarea class="form-control h-75" v-model="parameters.value" rows="20"></textarea>
|
||||
</validation-wrapper-component>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col p-r-5">
|
||||
<div class="btn btn-sm btn-default bg-master-lightest btn-block b-rad-none" :data-dismiss="closable ? 'modal' : ''" @click="$emit('close')">Cancel</div>
|
||||
</div>
|
||||
<div class="col p-l-5">
|
||||
<div class="btn btn-sm btn-success btn-block b-rad-none" @click="submitForm()">{{this.data?"Update":"Create"}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ModalFormHandler from '../../../general/mixins/modalFormHandler';
|
||||
import { required } from "vuelidate/lib/validators";
|
||||
export default {
|
||||
props : {
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
parameters: {
|
||||
key: '',
|
||||
value: '',
|
||||
}
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
parameters: {
|
||||
key: {
|
||||
required
|
||||
},
|
||||
value: {
|
||||
required
|
||||
},
|
||||
}
|
||||
},
|
||||
computed:{},
|
||||
created(){},
|
||||
methods: {
|
||||
submitForm(){
|
||||
this.submit((this.data ? this.route('api.kvp.prompt.update', this.data.id) : this.route('api.kvp.prompt.create')), (this.data ? 'put' : 'post'), this.section, true, false)
|
||||
}
|
||||
},
|
||||
mixins: [ModalFormHandler],
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<loading-component style="height: 300px; top: 0;" key="1" color="success" v-show="isLoading" ></loading-component>
|
||||
<div class="row justify-content-center" v-show="!isLoading">
|
||||
<div class="col">
|
||||
<div class="row m-b-20">
|
||||
<div class="col">
|
||||
<h3 class="all-caps">Are you Sure?</h3>
|
||||
<div class="fs-11">Are you sure you want to delete ChatGPT prompt with title <b>{{item.key}}</b> ?</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col p-r-5">
|
||||
<div class="btn btn-sm btn-success btn-block b-rad-none" data-dismiss="modal">Cancel</div>
|
||||
</div>
|
||||
<div class="col p-l-5">
|
||||
<div class="btn btn-sm btn-danger btn-block b-rad-none" @click="submit(route('api.kvp.prompt.delete', item.id), 'delete', section, true, true)">Delete</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import componentHandler from '../../../general/mixins/componentHandler';
|
||||
import ModalFormHandler from '../../../general/mixins/modalFormHandler';
|
||||
export default {
|
||||
mixins: [componentHandler, ModalFormHandler]
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="alert alert-info padding-15" role="alert" v-show="!isLoading">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h5 class="pull-left">{{item.key}}</h5>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fa fa-times text-info-darker pointer" data-dismiss="alert"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p v-html="item.value"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import componentHandler from '../../../general/mixins/componentHandler';
|
||||
import ModalFormHandler from '../../../general/mixins/modalFormHandler';
|
||||
export default {
|
||||
mixins: [componentHandler, ModalFormHandler]
|
||||
}
|
||||
</script>
|
||||
@@ -240,8 +240,31 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-b-5">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col bg-master-light tabButton" tab-name="chatGPTPrompts">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto p-t-10 p-b-10 b-r b-grey">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
width="35" height="35"
|
||||
viewBox="0 0 172 172"
|
||||
style=" fill:#000000;"><defs><linearGradient x1="99.4375" y1="43.33594" x2="99.4375" y2="60.14625" gradientUnits="userSpaceOnUse" id="color-1_43971_gr1"><stop offset="0" stop-color="#4ec9ff"></stop><stop offset="1" stop-color="#2bffe6"></stop></linearGradient><linearGradient x1="106.15625" y1="26.875" x2="106.15625" y2="146.16775" gradientUnits="userSpaceOnUse" id="color-2_43971_gr2"><stop offset="0" stop-color="#009add"></stop><stop offset="1" stop-color="#00baa4"></stop></linearGradient><linearGradient x1="83.3125" y1="26.875" x2="83.3125" y2="146.16775" gradientUnits="userSpaceOnUse" id="color-3_43971_gr3"><stop offset="0" stop-color="#009add"></stop><stop offset="1" stop-color="#00baa4"></stop></linearGradient><linearGradient x1="73.90625" y1="26.875" x2="73.90625" y2="146.16775" gradientUnits="userSpaceOnUse" id="color-4_43971_gr4"><stop offset="0" stop-color="#009add"></stop><stop offset="1" stop-color="#00baa4"></stop></linearGradient><linearGradient x1="51.0625" y1="26.875" x2="51.0625" y2="146.16775" gradientUnits="userSpaceOnUse" id="color-5_43971_gr5"><stop offset="0" stop-color="#009add"></stop><stop offset="1" stop-color="#00baa4"></stop></linearGradient><linearGradient x1="51.0625" y1="26.875" x2="51.0625" y2="146.16775" gradientUnits="userSpaceOnUse" id="color-6_43971_gr6"><stop offset="0" stop-color="#009add"></stop><stop offset="1" stop-color="#00baa4"></stop></linearGradient><linearGradient x1="45.6875" y1="26.875" x2="45.6875" y2="146.16775" gradientUnits="userSpaceOnUse" id="color-7_43971_gr7"><stop offset="0" stop-color="#009add"></stop><stop offset="1" stop-color="#00baa4"></stop></linearGradient><linearGradient x1="43" y1="26.875" x2="43" y2="146.16775" gradientUnits="userSpaceOnUse" id="color-8_43971_gr8"><stop offset="0" stop-color="#009add"></stop><stop offset="1" stop-color="#00baa4"></stop></linearGradient></defs><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M0,172v-172h172v172z" fill="none"></path><g><path d="M112.875,59.125h-26.875c-1.4835,0 -2.6875,-1.204 -2.6875,-2.6875v-10.75c0,-1.4835 1.204,-2.6875 2.6875,-2.6875h26.875c1.4835,0 2.6875,1.204 2.6875,2.6875v10.75c0,1.4835 -1.204,2.6875 -2.6875,2.6875z" fill="url(#color-1_43971_gr1)"></path><path d="M155.875,96.75c0,-7.97381 -5.82381,-14.59581 -13.4375,-15.88313v-18.82325c0,-2.08819 -1.16906,-3.94525 -3.05031,-4.85094c-1.88125,-0.90031 -4.06081,-0.65575 -5.69481,0.64769l-21.48925,17.18925c-0.08062,0.0645 -0.13975,0.14781 -0.21769,0.21769h-31.36044c-7.40944,0 -13.4375,6.02806 -13.4375,13.4375h-5.375c-2.96431,0 -5.375,2.41069 -5.375,5.375v5.375c0,2.96431 2.41069,5.375 5.375,5.375h5.375c0,7.40944 6.02806,13.4375 13.4375,13.4375v16.125c0,5.92863 4.82138,10.75 10.75,10.75c5.92863,0 10.75,-4.82137 10.75,-10.75v-16.125h9.86044c0.07794,0.06988 0.13706,0.15319 0.22038,0.22037l21.48656,17.18925c0.98094,0.78475 2.16075,1.18519 3.35131,1.18519c0.79281,0 1.59369,-0.17738 2.3435,-0.5375c1.88125,-0.903 3.05031,-2.76275 3.05031,-4.84825v-18.82325c7.61369,-1.28731 13.4375,-7.90931 13.4375,-15.88312zM61.8125,99.4375v-5.375h5.375v5.375zM96.75,134.375c0,2.96431 -2.41069,5.375 -5.375,5.375c-2.96431,0 -5.375,-2.41069 -5.375,-5.375v-16.125h10.75zM80.625,112.875c-4.44513,0 -8.0625,-3.61738 -8.0625,-8.0625v-16.125c0,-4.44513 3.61737,-8.0625 8.0625,-8.0625h29.5625v32.25h-8.0625zM137.05175,131.46175l-21.48925,-17.19462v-35.04231l10.75,-8.59194v26.11713h5.375v-30.41175l5.375,-4.29462l0.00538,69.42887c0,0 -0.00538,-0.00269 -0.01613,-0.01075zM142.4375,107.11837v-20.73675c4.6225,1.20131 8.0625,5.375 8.0625,10.36838c0,4.99337 -3.44,9.16706 -8.0625,10.36838z" fill="url(#color-2_43971_gr2)"></path><path d="M77.9375,91.375v5.375h5.375v-5.375h5.375v-5.375h-5.375c-2.96431,0 -5.375,2.41069 -5.375,5.375z" fill="url(#color-3_43971_gr3)"></path><path d="M21.5,107.5v-67.1875c0,-4.44512 3.61737,-8.0625 8.0625,-8.0625h88.6875c4.44512,0 8.0625,3.61738 8.0625,8.0625v10.75h5.375v-10.75c0,-7.40944 -6.02806,-13.4375 -13.4375,-13.4375h-88.6875c-7.40944,0 -13.4375,6.02806 -13.4375,13.4375v67.1875c0,7.40944 6.02806,13.4375 13.4375,13.4375h32.25v-5.375h-32.25c-4.44513,0 -8.0625,-3.61737 -8.0625,-8.0625z" fill="url(#color-4_43971_gr4)"></path><path d="M32.25,43h37.625v5.375h-37.625z" fill="url(#color-5_43971_gr5)"></path><path d="M32.25,53.75h37.625v5.375h-37.625z" fill="url(#color-6_43971_gr6)"></path><path d="M32.25,64.5h26.875v5.375h-26.875z" fill="url(#color-7_43971_gr7)"></path><path d="M32.25,75.25h21.5v5.375h-21.5z" fill="url(#color-8_43971_gr8)"></path></g></g></svg>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="fs-12 m-t-5 all-caps m-b-5">ChatGPT Prompts</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="row m-b-5" v-if="$store.getters.isAdmin">
|
||||
<!-- <div class="row m-b-5" v-if="$store.getters.isAdmin">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@@ -311,7 +334,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -699,6 +722,42 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row tabsContainer hide tabContent" tab-name="chatGPTPrompts">
|
||||
<div class="col">
|
||||
<loading-component style="height: 200px; top: 0;" key="1" color="success" v-show="$store.getters.isLoading('announcementsSection')"></loading-component>
|
||||
<div class="row" v-show="!$store.getters.isLoading('announcementsSection')">
|
||||
<div class="col">
|
||||
<div class="row p-b-5 m-b-20 b-b b-grey align-items-center parentContainer">
|
||||
<div class="col">
|
||||
<div class="font-heading all-caps fs-10 hint-text">
|
||||
ChatGPT Prompts
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-xs btn-primary b-rad-none requestModal" data-type="createModal">
|
||||
<i class="fa fa-plus m-r-5"></i>
|
||||
Create ChatGPT Prompt
|
||||
</button>
|
||||
<modal-form-component section="allChatGPTPromptsSection">
|
||||
<template slot="form" slot-scope="{section}">
|
||||
<chat-gpt-form-component :section="section"></chat-gpt-form-component>
|
||||
</template>
|
||||
</modal-form-component>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-b-20">
|
||||
<div class="col">
|
||||
<list-component section="allChatGPTPromptsSection" :endpoint="route('api.kvp.prompts.list')" :options="{}">
|
||||
<template slot="list" slot-scope="{data}">
|
||||
<single-chat-gpt-prompt-component :data="data"></single-chat-gpt-prompt-component>
|
||||
</template>
|
||||
</list-component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="row tabsContainer hide tabContent" tab-name="milestonesSegment" v-if="$store.getters.isAdmin">
|
||||
<div class="col">
|
||||
|
||||
+3
-1
@@ -77,9 +77,11 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
|
||||
require __DIR__ . '/export.php';
|
||||
|
||||
require __DIR__ . '/setting.php';
|
||||
|
||||
|
||||
require __DIR__ . '/affiliate.php';
|
||||
|
||||
require __DIR__ . '/key_value_pair.php';
|
||||
|
||||
// require __DIR__ . '/rate.php';
|
||||
// require __DIR__ . '/receipt.php';
|
||||
});
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\KeyValuePairs\CreateChatGPTPromptKVPController;
|
||||
use App\Http\Controllers\KeyValuePairs\ListChatGPTPromptsKVPController;
|
||||
use App\Http\Controllers\KeyValuePairs\UpdateChatGPTPromptKVPController;
|
||||
use App\Http\Controllers\KeyValuePairs\DeleteChatGPTPromptKVPController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::group(['prefix' => 'kvp', 'as' => 'kvp.', 'namespace' => 'KeyValuePairs'], function () {
|
||||
Route::get('/prompts/list', [ListChatGPTPromptsKVPController::class, 'list'])->name('prompts.list');
|
||||
Route::put('/prompt/update/{id}', [UpdateChatGPTPromptKVPController::class, 'update'])->name('prompt.update');
|
||||
Route::post('/prompt/create', [CreateChatGPTPromptKVPController::class, 'create'])->name('prompt.create');
|
||||
Route::delete('/prompt/delete/{id}', [DeleteChatGPTPromptKVPController::class, 'delete'])->name('prompt.delete');
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Transactions\ETLPurchaseOrderTransactionController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::group(['prefix' => 'transactions', 'namespace' => 'Transactions', 'as' => 'transaction.'], function () {
|
||||
@@ -22,6 +23,7 @@ Route::group(['prefix' => 'transactions', 'namespace' => 'Transactions', 'as' =>
|
||||
|
||||
Route::post('booking/{id}/details/update', 'CreatePurchaseOrderTransactionController@create')->name('po.create');
|
||||
Route::post('booking/{id}/details/import', 'ImportPurchaseOrderTransactionController@import')->name('po.import');
|
||||
Route::post('booking/{id}/details/etl', [ETLPurchaseOrderTransactionController::class, 'etl'])->name('po.etl');
|
||||
|
||||
Route::get('bulk/po/{issuer_id}/{start_date}/{end_date}', 'CreateBulkPurchaseOrderTransactionController@create')->name('po.bulk.create');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user