Merge branch 'development' into vapor/development

This commit is contained in:
Dillon Ngo
2024-06-20 17:03:59 +08:00
71 changed files with 1692 additions and 309 deletions
@@ -2,7 +2,6 @@
namespace App\Classes\General\Eloquent\Filters;
use App\Classes\ValueObjects\Constants\RoleTypes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
@@ -12,33 +11,17 @@ class HasActiveReward implements Filter
/**
* @param Builder $builder
* @param $value
* @return mixed
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
if(in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)){
// $userId = $value !== 1 ? $value : Auth::user()->id;
$userId = $value;
return $builder->where('user_id', $userId)
->where(function ($query) {
$query->whereHas('reward', function ($subquery) {
$subquery->where('is_active', true);
})
->orWhereDoesntHave('reward');
})
->whereDoesntHave('voucher.redemptions.transaction.booking.company.employees', function ($query) use ($userId) {
$query->where('user_id', $userId);
return $builder->where('user_id', Auth::user()->id) //cief todo: should not use Auth::user()->id
->where(function ($query) {
$query->whereHas('reward', function ($subquery) {
$subquery->where('is_active', true);
});
}
else{
return $builder->where('user_id', Auth::user()->id)
->where(function ($query) {
$query->whereHas('reward', function ($subquery) {
$subquery->where('is_active', true);
})
->orWhereDoesntHave('reward');
})
->whereDoesntHave('voucher.redemptions.transaction.owner');
}
// ->orWhereDoesntHave('reward');
});
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class HasActiveRewardForAdmin implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('user_id', $value)
->where(function ($query) {
$query->whereHas('reward', function ($subquery) {
$subquery->where('is_active', true);
});
// ->orWhereDoesntHave('reward');
});
}
}
@@ -0,0 +1,44 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use App\Classes\ValueObjects\Constants\RoleTypes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
class HasVouchersAll implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return mixed
*/
public static function apply(Builder $builder, $value)
{
if(in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)){
// $userId = $value !== 1 ? $value : Auth::user()->id;
$userId = $value;
return $builder->where('user_id', $userId)
->where(function ($query) {
$query->whereHas('reward', function ($subquery) {
$subquery->where('is_active', true);
})
->orWhereDoesntHave('reward');
})
->whereDoesntHave('voucher.redemptions.transaction.booking.company.employees', function ($query) use ($userId) {
$query->where('user_id', $userId);
});
}
else{
return $builder->where('user_id', Auth::user()->id)
->where(function ($query) {
$query->whereHas('reward', function ($subquery) {
$subquery->where('is_active', true);
})
->orWhereDoesntHave('reward');
})
->whereDoesntHave('voucher.redemptions.transaction.owner');
}
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Classes\General\Open1688;
class SignatureUtil {
/**
*
* @param $path
* @param array $parameters
* @param RequestPolicy $requestPolicy
* @param ClientPolicy $clientPolicy
* @return string
*/
public static function signature($path, array $parameters) {
$paramsToSign = array ();
foreach ( $parameters as $k => $v ) {
$paramToSign = $k . $v;
Array_push ( $paramsToSign, $paramToSign );
}
sort ( $paramsToSign );
$implodeParams = implode ( $paramsToSign );
$pathAndParams = $path . $implodeParams;
$sign = hash_hmac ( "sha1", $pathAndParams, config('open1688.app_secret'), true );
$signHexWithLowcase = bin2hex ( $sign );
$signHexUppercase = strtoupper ( $signHexWithLowcase );
return $signHexUppercase;
}
}
?>
+79
View File
@@ -0,0 +1,79 @@
<?php
namespace App\Classes\General;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\General\Open1688\SignatureUtil;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
class Open1688Helper
{
public static function productSearchKeyword($keyword, $page) {
$requestData = [
'offerQueryParam' => "{\"keyword\":\"$keyword\",\"beginPage\":$page,\"pageSize\":18,\"country\":\"en\"}",
];
return self::getFromOpen1688($requestData, config('open1688.product_search_keyword_api'));
}
public static function productDetail($offerId) {
$requestData = [
'offerDetailParam' => "{\"offerId\":$offerId,\"country\":\"en\"}",
];
return self::getFromOpen1688($requestData, config('open1688.product_detail_api'));
}
private static function getFromOpen1688($requestData, $apiName) {
$requestData["access_token"] = config('open1688.access_token');
$signaturedStr = self::generateRequestSignature($apiName, $requestData);
$url = config('open1688.url') . $apiName . "/" . config('open1688.app_key') . "?_aop_signature=" . $signaturedStr;
foreach($requestData as $k => $v) {
$url = $url . "&$k=$v";
}
try {
$client = new \GuzzleHttp\Client(['verify' => false]);
$response = $client->request('GET', $url);
$body = $response->getBody();
$contents = json_decode($body, true);
return $contents['result']['result'];
} catch(\GuzzleHttp\Exception\RequestException $exception){
throw new MalformedRequestException(json_decode($exception->getResponse()->getBody()->getContents(), true)['error_message']);
}
}
private static function generateRequestSignature ($apiName, $requestData) {
$pathToSign = self::generateAPIPath($apiName);
$signaturedStr = SignatureUtil::signature ($pathToSign, $requestData);
return $signaturedStr;
}
private static function generateAPIPath($apiName) {
$urlResult = "";
$defs = array (
$urlResult,
"param2",
"/",
"1",
"/",
"com.alibaba.fenxiao.crossborder",
"/",
$apiName,
"/",
config('open1688.app_key')
);
$urlResult = implode ( $defs );
return $urlResult;
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Classes\Jobs;
use App\Classes\Modules\Milestones\Processors\MilestoneRewardByTransactionProcessor;
use App\Models\Transaction;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CreateMilestoneRewardByTransactionJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** @var Transaction */
private $transaction;
/** @var array */
private $milestones;
/**
* CreateMilestoneRewardByTransactionJob constructor.
* @param Transaction $transaction
* @param array $milestones
*/
public function __construct(Transaction $transaction, array $milestones)
{
$this->transaction = $transaction;
$this->milestones = $milestones;
}
public function handle()
{
(App()->make(MilestoneRewardByTransactionProcessor::class))->execute($this->transaction, $this->milestones);
}
}
@@ -7,6 +7,7 @@ use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\StatementTransactionOwnerType;
use App\Models\AccountStatement;
use App\Models\StatementAccount;
use App\Models\StatementTransaction;
@@ -45,77 +46,28 @@ class ImportCiefLiteStatementLogic extends AbstractControllerLogic
$sheet = $collection->first()->skip(1);
$dateTo = $sheet->first()[10];
$dateFrom = $sheet->last()[10];
$totalTransaction = $sheet->count();
$offset = 1;
do {
$dateFrom = $sheet->offsetGet($sheet->count() - $offset)[10];
$offset += 1;
} while(!$dateFrom);
$dateFrom = carbon::parse($dateFrom);
$dateTo = carbon::parse($dateTo);
$statement = AccountStatement::whereDate('date_from', $dateFrom)
->whereDate('date_to', $dateTo)
->where('total_amount', $totalTransaction)
->first();
if(!$statement){
$statement = new AccountStatement([
'date_from' => $dateFrom,
'date_to' => $dateTo,
'total_amount' => $totalTransaction,
]);
}
$account = StatementAccount::where("name", "CIEF LITE")->first();
if (!$account) {
throw new Exception("Statement Account for CIEF LITE not found.");
}
$account->statements()->save($statement);
$sheet->map(function ($row) use ($statement) {
if (!$row[0]) {
$sheet->map(function ($row) {
if (!$row[0] || trim($row[8]) !== "已审核") {
return;
}
$postingDate = $row[10];
$transactionDescription = trim($row[1]);
$description2 = trim($row[2]);
$description3 = "Fee " . $row[6];
$description4 = trim($row[8]);
$transactionRef = trim($row[12]);
$postingDate =date('Y-m-d', strtotime($row[10]));
$amount = ((float) str_replace(',', '', $row[7]));
$transactionCode = $row[0];
$transaction = new StatementTransaction([
'posting_date' => $postingDate,
'transaction_description' => $transactionDescription,
'transaction_description_2' => $description2,
'transaction_description_3' => $description3,
'transaction_description_4' => $description4,
'transaction_ref' => $transactionRef,
'amount' => $amount,
'transaction_code' => $transactionCode,
]);
$statementTransaction = StatementTransaction::whereHas('account', function($query) {
$query->where('statement_accounts.number', 8881040198515);
})->whereDate('posting_date', $postingDate)->where('amount', $amount)->first();
// Check if the transaction already exists for this statement
$existingTransaction = StatementTransaction::where('transaction_ref', $transactionRef)
->where('posting_date', $postingDate)
->where('amount', $amount)
->where('transaction_description', $transactionDescription)
->where('transaction_code', $transactionCode)
->first();
if (!$existingTransaction) {
$statement->transactions()->save($transaction);
if (!$statementTransaction) {
return;
}
return $transaction;
$owner = $statementTransaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::WALLET_TOP_UP,
'system' => 'LITE',
'owner_reference'=> $row[0],
]);
return $owner;
});
}
@@ -12,7 +12,6 @@ use App\Classes\Modules\Companies\Processors\AssignEmployeeProcessor;
use App\Classes\Modules\Companies\Processors\AssignSegmentProcessor;
use App\Classes\Modules\Companies\Processors\CreateCompanyProcessor;
use App\Classes\Modules\Contacts\Processors\CreateContactProcessor;
use App\Classes\Modules\Milestones\Processors\CheckMilestonesForRewardProcessor;
use App\Classes\Modules\Vouchers\Processors\Voucherify\NewCustomerToVoucherifyProcessor;
use App\Classes\Modules\Vouchers\Processors\CreateVoucherProcessor;
use App\Classes\Modules\Companies\DataTransferObjects\EmploymentObject;
@@ -74,9 +73,6 @@ class CreateCustomerLogic extends AbstractControllerLogic
/** @var CreatesSeasonalSegment */
private $createsSeasonalSegment;
/** @var CheckMilestonesForRewardProcessor */
private $checkMilestonesForRewardProcessor;
/** @var NewCustomerToVoucherifyProcessor */
private $newCustomerToVoucherifyProcessor;
@@ -100,14 +96,13 @@ class CreateCustomerLogic extends AbstractControllerLogic
* @param GenerateEmailVerificationAttemptProcessor $generateEmailVerificationAttemptProcessor
* @param AssignSegmentProcessor $assignCompanyToSegmentProcessor
* @param CreatesSeasonalSegment $createsSeasonalSegment
* @param CheckMilestonesForRewardProcessor $checkMilestonesForRewardProcessor
* @param NewCustomerToVoucherifyProcessor $newCustomerToVoucherifyProcessor
* @param CreateVoucherProcessor $createVoucherProcessor
* @param RegisterOnShippingProcessor $registerOnShippingProcessor
* @param ConnectCompanyToShippingCompanyModule $connectCompanyToShippingCompanyModule
*/
public function __construct(CreateUserProcessor $createUserProcessor, CreateCompanyProcessor $createCompanyProcessor, CreateContactProcessor $createContactProcessor, AssignEmployeeProcessor $assignEmployeeProcessor, AssignSegmentProcessor $assignSegmentProcessor, AuthenticationProcessor $authenticationProcessor, GenerateEmailVerificationAttemptProcessor $generateEmailVerificationAttemptProcessor,
CreatesSeasonalSegment $createsSeasonalSegment, CheckMilestonesForRewardProcessor $checkMilestonesForRewardProcessor, NewCustomerToVoucherifyProcessor $newCustomerToVoucherifyProcessor, CreateVoucherProcessor $createVoucherProcessor, RegisterOnShippingProcessor $registerOnShippingProcessor, ConnectCompanyToShippingCompanyModule $connectCompanyToShippingCompanyModule)
CreatesSeasonalSegment $createsSeasonalSegment, NewCustomerToVoucherifyProcessor $newCustomerToVoucherifyProcessor, CreateVoucherProcessor $createVoucherProcessor, RegisterOnShippingProcessor $registerOnShippingProcessor, ConnectCompanyToShippingCompanyModule $connectCompanyToShippingCompanyModule)
{
$this->createUserProcessor = $createUserProcessor;
$this->createCompanyProcessor = $createCompanyProcessor;
@@ -118,7 +113,6 @@ class CreateCustomerLogic extends AbstractControllerLogic
$this->generateEmailVerificationAttemptProcessor = $generateEmailVerificationAttemptProcessor;
$this->assignCompanyToSegmentProcessor = $assignSegmentProcessor;
$this->createsSeasonalSegment = $createsSeasonalSegment;
$this->checkMilestonesForRewardProcessor = $checkMilestonesForRewardProcessor;
$this->newCustomerToVoucherifyProcessor = $newCustomerToVoucherifyProcessor;
$this->createVoucherProcessor = $createVoucherProcessor;
$this->registerOnShippingProcessor = $registerOnShippingProcessor;
@@ -81,8 +81,8 @@ class AuthenticationProcessor
$this->newCustomerToVoucherifyProcessor->execute(0, $user, false);
}
//cief todo: case study 1 voucherify
//$this->checkMilestonesForRewardProcessor->execute($user, [Milestones::MILESTONE_1]);
//cief todo: case study 1
//$this->checkMilestonesForRewardProcessor->execute($user, [Milestones::MILESTONE_DEMO_1]);
return ['access_token' => $this->generatesAuthenticationToken->execute($user), 'redirect_url' => $this->authenticationRedirect->url($user)];
}
@@ -27,6 +27,7 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use App\Classes\Modules\Wallets\Services\RecalculatesWalletBalance;
class CallbackBillplzLogic
{
@@ -55,7 +56,9 @@ class CallbackBillplzLogic
* @param FetchesTransaction $fetchesTransaction
* @param UpdatesTransactionStatus $updatesTransactionStatus
* @param UpdatesWalletBalance $updatesWalletBalance
* @param CreateCashBackTransactionProcessor $createCashBackTransactionProcessor
* @param RecalculatesWalletBalance $recalculatesWalletBalance
* @param CreateMilestoneRewardJob $createMilestoneRewardJob
*/
public function __construct(GetBillplzBill $getBillplzBill, FetchesTransaction $fetchesTransaction, UpdatesTransactionStatus $updatesTransactionStatus, UpdatesWalletBalance $updatesWalletBalance, CreateCashBackTransactionProcessor $createCashBackTransactionProcessor, RecalculatesWalletBalance $recalculatesWalletBalance)
{
@@ -117,4 +120,4 @@ class CallbackBillplzLogic
return $request->method() === 'POST' ? true : view('pages.payments_redirect', ['marking' => $marking ?? null, 'transaction' => $transaction, 'status' => $status]);
}
}
}
@@ -115,9 +115,7 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic
$conversionObject = $this->createConversionObject($request, $booking);
$outstanding = $this->calculatesBookingOutstanding->execute($booking);
if($conversionObject->getAmount() > round($outstanding, 2)) throw new MalformedRequestException('Your payment must not be greater than '. $outstanding .'.');
$this->validatePayment($request, $booking, $conversionObject);
$configurations = $this->fetchBookingQuotation->execute($booking->company, $conversionObject, $request->input('voucher_code'));
@@ -138,6 +136,10 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic
$this->updatesTransactionStatus->execute($transaction, ApprovalStatus::PENDING_VERIFICATION);
}
if(PaymentMethodType::PAYMENT_METHODS[$request->input('payment_method')] == PaymentMethodType::WALLET){
$this->updatesTransactionStatus->execute($transaction, ApprovalStatus::APPROVED);
}
return $this->resourceResponse(new TransactionResource($transaction));
}
@@ -154,7 +156,7 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic
private function validatePayment(Request $request, Booking $booking, CurrencyConversionObject $conversionObject): void
{
$outstanding = $this->calculatesBookingOutstanding->execute($booking);
$outstanding = $this->calculatesBookingOutstanding->execute($booking) + $this->calculatesBookingRefundAmount->execute($booking, $booking->fix_currency_id);
if($conversionObject->getAmount() > round($outstanding, 2))
throw new MalformedRequestException('Your payment must not be greater than '. $outstanding .'.');
@@ -81,9 +81,12 @@ class CheckMilestonesForRewardProcessor
try{
foreach($milestone_constants as $constant)
{
//update milestone progress
/** @var Milestone $milestone */
$milestone = $this->fetchesMilestone->execute(['name' => $constant]);
//update milestone progres
$milestone = Milestone::where('name', $constant)->first();
if(!$milestone) {
Log::error("{$constant} NOT FOUND!");
continue;
}
$result = null;
if($user->milestoneProgress->count() > 0){
$result = $user->milestoneProgress->where('milestone_id', $milestone->id)->first();
@@ -130,6 +133,10 @@ class CheckMilestonesForRewardProcessor
//Voucherify - Create Voucher
$result = $this->createsVoucherifyVoucher->execute($user, intval($reward->value));
}
else if($reward->type == RewardType::REWARD_CODE){
//Voucherify - Get Voucher
$result = $this->fetchesVoucherifyVoucher->execute($user, $reward->value);
}
else{
//Voucherify - Validates Voucher
$ValidateVoucherifyVoucherObject = new ValidateVoucherifyVoucherObject(0, $reward->value, 0.00, $user);
@@ -0,0 +1,48 @@
<?php
namespace App\Classes\Modules\Milestones\Processors;
use App\Classes\Modules\Milestones\Processors\CheckMilestonesForRewardProcessor;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\Milestones;
use App\Models\Booking;
use App\Models\Transaction;
use Illuminate\Support\Facades\Log;
class MilestoneRewardByTransactionProcessor
{
/** @var CheckMilestonesForRewardProcessor */
private $checkMilestonesForRewardProcessor;
/**
* MilestoneRewardByTransactionProcessor constructor.
* @param CheckMilestonesForRewardProcessor $checkMilestonesForRewardProcessor
*/
public function __construct(CheckMilestonesForRewardProcessor $checkMilestonesForRewardProcessor)
{
$this->checkMilestonesForRewardProcessor = $checkMilestonesForRewardProcessor;
}
/**
* @param Transaction $transaction
* @param array $milestones
* @throws \App\Classes\Exceptions\AccessForbiddenException
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(Transaction $transaction, array $milestones)
{
$company = $transaction->owner instanceof Booking ? $transaction->owner()->first()->company : null;
if($company){
$completedBookingCount = count($company->bookings()->where('status', ApprovalStatus::COMPLETED)->get());
Log::info("Number of completed bookings completed for company ".$company->reference. ": " .$completedBookingCount);
if (in_array(Milestones::MILESTONE_1, $milestones)) {
if($completedBookingCount === 2){
$user = $company->employees()->first();
$this->checkMilestonesForRewardProcessor->execute($user, [Milestones::MILESTONE_1]);
}
}
}
}
}
@@ -0,0 +1,84 @@
<?php
namespace App\Classes\Modules\Open1688\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\General\Open1688Helper;
use ErrorException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class GetProductDetailLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Get product detail',
'message' => 'You have successfully get 1688 product detail'
];
}
/**
* @param Request $request
* @return JsonResponse
* @throws ErrorException
*/
public function logic(Request $request) : JsonResponse
{
$result = Open1688Helper::productDetail($request->route('offer_id'));
$productAttribute = [];
$variants = [];
$uniqueVariants = [];
$variantInfo = [];
$priceList = [];
$variantKeyCombinationOrder = [];
foreach ($result['productAttribute'] as $attr) {
$productAttribute[$attr['attributeName']] = array_key_exists($attr['attributeName'], $productAttribute) ? $productAttribute[$attr['attributeName']] . ", " . $attr['value'] : $attr['value'];
}
$result['productAttribute'] = $productAttribute;
foreach ($result['productSkuInfos'] as $sku) {
$uniqueVariantKey = '';
foreach ($sku['skuAttributes'] as $skuAttr) {
if (count($variantKeyCombinationOrder) < count($sku['skuAttributes'])) {
array_push($variantKeyCombinationOrder, $skuAttr['attributeName']);
}
if (!in_array($skuAttr['value'], $uniqueVariants)) {
$variants[$skuAttr['attributeName']][] = [
'image' => isset($skuAttr['skuImageUrl']) ? $skuAttr['skuImageUrl'] : null,
'value' => $skuAttr['value']
];
}
array_push($uniqueVariants, $skuAttr['value']);
$uniqueVariantKey .= $skuAttr['value'];
}
$price = isset($sku['price']) ? $sku['price'] : $sku['consignPrice'];
$variantInfo[$uniqueVariantKey] = [
'skuId' => $sku['skuId'],
'amountOnSale' => $sku['amountOnSale'],
'price' => $price,
];
array_push($priceList, $price);
}
$result['variants'] = $variants;
$result['variantInfo'] = $variantInfo;
$minPrice = min($priceList);
$maxPrice = max($priceList);
$result['priceRange'] = $minPrice === $maxPrice ? $minPrice : "CNY ¥$minPrice - CNY ¥$maxPrice";
$result['variantKeyCombinationOrder'] = $variantKeyCombinationOrder;
return $this->response(['data' => $result]);
}
}
@@ -0,0 +1,36 @@
<?php
namespace App\Classes\Modules\Open1688\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\General\Open1688Helper;
use App\Http\Resources\WalletResource;
use ErrorException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class KeywordSearchProductLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Search 1688 Products',
'message' => 'You have successfully retrieved a list of products from 1688'
];
}
/**
* @param Request $request
* @return JsonResponse
* @throws ErrorException
*/
public function logic(Request $request) : JsonResponse
{
$result = Open1688Helper::productSearchKeyword($request->input('keyword'), $request->input('page'));
return $this->response($result);
}
}
@@ -72,14 +72,32 @@ class CreatePurchaseOrderTransactionLogic extends AbstractControllerLogic
$billNumber = $this->generatesTransactionBillNumber->execute('PO-');
$total = collect($request->input('products'))->sum(function($product){
$products = $request->input('products');
if ($request->input('pushToExisting')) {
$transaction = $booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first();
if ($transaction) {
foreach ($transaction->transactionDetails as $detail) {
array_push($products, [
'stockCode' => $detail->product_code,
'description' => $detail->product_name,
'quantity' => $detail->quantity,
'unit_price' => $detail->price,
'total' => $detail->amount
]);
}
}
}
$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, $request->input('products'));
1, 0, 0, null, ApprovalStatus::PENDING_SUBMISSION, $products);
$transaction = $this->createPurchaseOrderTransactionProcessor->execute($booking, $object);
@@ -16,10 +16,12 @@ use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\Modules\Transactions\Services\FetchesGroup;
use App\Classes\Modules\Transactions\Services\UpdatesTransaction;
use App\Classes\Modules\Transactions\Services\CalculatesTransactionTransferFee;
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\SegmentConstants;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Models\Transaction;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use ErrorException;
@@ -60,6 +62,9 @@ class UpdateGroupLogic extends AbstractControllerLogic
/** @var CreatesFiles */
private $createsFile;
/** @var GeneratesTransactionBillNumber */
private $generatesTransactionBillNumber;
/**
* UpdateGroupLogic constructor.
* @param FetchesGroup $fetchesGroup
@@ -69,8 +74,9 @@ class UpdateGroupLogic extends AbstractControllerLogic
* @param CalculatesTransactionTransferFee $calculatesTransactionTransferFee
* @param CreatesDocument $createsDocument
* @param CreatesFiles $createsFile
* @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
*/
public function __construct(FetchesGroup $fetchesGroup, FetchesCompany $fetchesCompany, CalculatesTransactionServiceCharge $calculatesTransactionServiceCharge, UpdatesTransaction $updatesTransaction, CalculatesTransactionTransferFee $calculatesTransactionTransferFee, CreatesDocument $createsDocument, CreatesFiles $createsFile)
public function __construct(FetchesGroup $fetchesGroup, FetchesCompany $fetchesCompany, CalculatesTransactionServiceCharge $calculatesTransactionServiceCharge, UpdatesTransaction $updatesTransaction, CalculatesTransactionTransferFee $calculatesTransactionTransferFee, CreatesDocument $createsDocument, CreatesFiles $createsFile, GeneratesTransactionBillNumber $generatesTransactionBillNumber)
{
$this->fetchesGroup = $fetchesGroup;
$this->fetchesCompany = $fetchesCompany;
@@ -79,6 +85,7 @@ class UpdateGroupLogic extends AbstractControllerLogic
$this->calculatesTransactionTransferFee = $calculatesTransactionTransferFee;
$this->createsDocument = $createsDocument;
$this->createsFile = $createsFile;
$this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
}
/**
@@ -162,8 +169,61 @@ class UpdateGroupLogic extends AbstractControllerLogic
$this->updatesTransaction->execute($transferTransaction, $object);
}
// group transfer fee from supplier currency order dashboard manual input
if ($request->input('group_transfer_fee')) {
$fee = $request->input('group_transfer_fee');
$group_transfer_fee = $group->morphTransactions()->where('type', TransactionType::TRANSFER_FEE)->first();
if ($group_transfer_fee) {
$group_transfer_fee->amount = $fee;
$group_transfer_fee->original_amount = $fee;
$group_transfer_fee->save();
} else {
$transferFeeNumber = $this->generatesTransactionBillNumber->execute('TRFR-');
$object = new TransactionObject($transferFeeNumber, TransactionType::TRANSFER_FEE, 1, $supplier->id,
$supplier->banks()->where('default', true)->first()->id, PaymentMethodType::CASH,
$fee, $fee, $group->original_currency_id, $group->original_currency_id,
1, 0, 0, null, ApprovalStatus::APPROVED);
$model = new Transaction();
$model->bill_no = $object->getBillNo();
$model->type = $object->getTransactionType();
$model->issuer = $object->getIssuer();
$model->receiver = $object->getReceiver();
$model->recipient_bank_account_id = $object->getRecipientBankAccountId();
$model->payment_method = $object->getPaymentMethod();
$model->amount = $object->getAmount();
$model->original_amount = $object->getOriginalAmount();
$model->currency_id = $object->getCurrencyId();
$model->original_currency_id = $object->getOriginalCurrencyId();
$model->currency_rate = $object->getCurrencyRate();
$model->tax = $object->getTax();
$model->service_charge = $object->getServiceCharge();
$model->expires_on = $object->getExpiresOn();
$model->status = $object->getStatus();
$model->payment_reference = $object->getPaymentReference();
$group->morphTransactions()->save($model);
}
}
$group_transfer_fee = $group->morphTransactions()->where('type', TransactionType::TRANSFER_FEE)->first();
$group_transfer_fee_original_amount = 0;
if ($group_transfer_fee) {
$group_transfer_fee_original_amount = $group_transfer_fee->original_amount;
}
$transferFeeTransactions = $group->transactions()->with([
'transactions' => function ($transaction) {
return $transaction->where('type', TransactionType::TRANSFER_FEE);
}])->get()->pluck('transactions')->flatten();
$group->issuer = $supplier->id;
$group->amount = $group->transactions()->sum('amount');
$group->original_amount = $group->transactions()->sum('original_amount') + ((float)$transferFeeTransactions->sum('service_charge') + (float)$group_transfer_fee_original_amount);
$group->amount = $group->transactions()->sum('amount') + (((float)$transferFeeTransactions->sum('service_charge') + (float)$group_transfer_fee_original_amount) / $rate);
$group->currency_rate = $rate;
$group->tax = $group->transactions()->sum('tax');
$group->service_charge = $group->transactions()->sum('service_charge');
@@ -172,12 +232,7 @@ class UpdateGroupLogic extends AbstractControllerLogic
$group->documents()->delete();
$transferFeeTransactions = $group->transactions()->with([
'transactions' => function ($transaction) {
return $transaction->where('type', TransactionType::TRANSFER_FEE);
}])->get()->pluck('transactions')->flatten();
$pdf = LaravelMpdf::loadView('pages.pdfs.currency_vendor_order', ['transactions' => $group->transactions, 'transferFeeTransactions' => $transferFeeTransactions, 'supplier' => $supplier]);
$pdf = LaravelMpdf::loadView('pages.pdfs.currency_vendor_order', ['transactions' => $group->transactions, 'transferFeeTransactions' => $transferFeeTransactions, 'supplier' => $supplier, 'groupTransferFeeOriginalAmount' => $group_transfer_fee_original_amount]);
$object = new DocumentObject(
DocumentType::CURRENCY_VENDOR_ORDER,
@@ -14,13 +14,16 @@ use App\Classes\Modules\Bookings\Services\CalculatesBookingCurrencyAverageRate;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\Modules\Bookings\Services\UpdatesBookingStatus;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\Jobs\CreateMilestoneRewardByTransactionJob;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\SegmentConstants;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Classes\ValueObjects\Constants\Milestones;
use App\Models\Booking;
use App\Models\SegmentConstant;
class CreateInvoiceTransactionProcessor
{
@@ -123,6 +126,7 @@ class CreateInvoiceTransactionProcessor
$transaction = $booking->transactions()
->where('type', TransactionType::PAYMENT)
->latest()->get()[0];
CreateMilestoneRewardByTransactionJob::dispatch($transaction, [Milestones::MILESTONE_1]);
$billNumber = $this->generatesTransactionBillNumber->execute('INV-');
@@ -6,8 +6,9 @@ class MilestoneCreationOptions
{
const OPTIONS_MILESTONE_NAME= [
['text' => 'MILESTONE 1', 'id' => Milestones::MILESTONE_1],
['text' => 'MILESTONE 2', 'id' => Milestones::MILESTONE_2],
['text' => 'MILESTONE 3', 'id' => Milestones::MILESTONE_3],
// ['text' => 'MILESTONE DEMO 1', 'id' => Milestones::MILESTONE_DEMO_1],
// ['text' => 'MILESTONE 2', 'id' => Milestones::MILESTONE_2],
// ['text' => 'MILESTONE 3', 'id' => Milestones::MILESTONE_3],
// ['text' => 'MILESTONE 4', 'id' => Milestones::MILESTONE_4],
// ['text' => 'MILESTONE 5', 'id' => Milestones::MILESTONE_5],
// ['text' => 'MILESTONE 6', 'id' => Milestones::MILESTONE_6],
@@ -4,9 +4,10 @@ namespace App\Classes\ValueObjects\Constants;
class Milestones
{
public const MILESTONE_1 = 'MILESTONE 1'; //Authentication (Sign in) or CreateCustomerLogic (Sign up)
public const MILESTONE_2 = 'MILESTONE 2'; //Create Identification Document
public const MILESTONE_3 = 'MILESTONE 3'; //Assign Company to Segment Logic -segment.assign when accept 1688
public const MILESTONE_1 = 'MILESTONE 1'; //Has 2 successful bookings
// public const MILESTONE_DEMO_1 = 'MILESTONE_DEMO_1 1'; //Authentication (Sign in) or CreateCustomerLogic (Sign up)
// public const MILESTONE_2 = 'MILESTONE 2'; //Create Identification Document
// public const MILESTONE_3 = 'MILESTONE 3'; //Assign Company to Segment Logic -segment.assign when accept 1688
// public const MILESTONE_4 = 'MILESTONE 4'; //Create Bank Logic (Transfer Now from dashboard)
// public const MILESTONE_5 = 'MILESTONE 5'; //Create Booking Logic
// public const MILESTONE_6 = 'MILESTONE 6'; //Create Address Logic (At transfer page before create booking)
@@ -0,0 +1,130 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Classes\Modules\Transactions\ControllersLogic\UpdateGroupLogic;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\BillGroup;
use App\Models\Group;
use App\Models\Transaction;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route as FacadesRoute;
class UpdateBillGroupAndGroupToIncludeTransferFee extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'updateBillGroupAndGroupToIncludeTransferFee';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Update bill group and group to include transfer fee calculation';
/** @var UpdateGroupLogic */
private $updateGroupLogic;
/**
* Create a new command instance.
*
* @param UpdateGroupLogic $updateGroupLogic
*/
public function __construct(UpdateGroupLogic $updateGroupLogic)
{
parent::__construct();
$this->updateGroupLogic = $updateGroupLogic;
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
// update group to include transfer fee
$groups = Group::all();
foreach ($groups as $group) {
$originalTransferFees = (float)Transaction::where('type', TransactionType::TRANSFER_FEE)->whereIn('owner_id', $group->transactions->pluck('id'))->sum('service_charge');
$correctOriginalAmount = $group->transactions()->sum('original_amount');
$correctOriginalAmount += $originalTransferFees;
$correctAmount = $group->transactions()->sum('amount');
$transferFees = $originalTransferFees / $group->currency_rate;
$correctAmount += $transferFees;
if ($group->original_amount != $correctOriginalAmount || $group->amount != $correctAmount) {
$group->original_amount = $correctOriginalAmount;
$group->amount = $correctAmount;
$group->save();
$this->info("updated group id: {$group->id}, added transfer fee CNY {$correctOriginalAmount}");
}
}
// update group calculation to include individual group transfer fee
$groups = Group::whereHas('morphTransactions', function ($q) {
$q->where('type', TransactionType::TRANSFER_FEE);
})->get();
foreach ($groups as $group) {
$route = FacadesRoute::getRoutes()->getByName('api.transaction.group.update');
$request = Request::create(route('api.transaction.group.update', $group->id));
$uri = $route->uri;
$request->setRouteResolver(function () use ($request, $uri) {
// Associate Route to request so we can access route parameters.
return (new Route('PUT', $uri, []))->bind($request);
});
$request['rate'] = $group->currency_rate;
$request['supplier_id'] = $group->issuer;
$this->updateGroupLogic->execute($request);
$group_transfer_fee = $group->morphTransactions()->where('type', TransactionType::TRANSFER_FEE)->first();
$this->info("updated group id: {$group->id}, added transfer fee to individual white form CNY {$group_transfer_fee->original_amount}");
}
// update bill group calculation to include individual group transfer fee
$billGroups = BillGroup::all();
foreach ($billGroups as $billGroup) {
// ignore those has bill group refund
if ($billGroup->billRefunds()->count() > 0) {
continue;
}
$totalOriginal = $billGroup->groups()->sum('original_amount');
$total = $billGroup->groups()->sum('amount');
// update bill group payment transaction amount if there is only 1 payment transaction
$payment_transactions = $billGroup->transactions()->whereIn('status', [ApprovalStatus::PENDING_SUBMISSION, ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])->get();
if ($payment_transactions->count() === 1) {
$payment_transaction = $payment_transactions->first();
if ($payment_transaction->amount === $billGroup->amount) {
$payment_transaction->original_amount = $total;
$payment_transaction->amount = $total;
$payment_transaction->save();
$this->info("updated bill group payment transaction id: {$payment_transaction->id}, update original amount to CNY {$totalOriginal}");
}
}
// update bill group amount and original amount
$billGroup->original_amount = $totalOriginal;
$billGroup->amount = $total;
$billGroup->save();
$this->info("updated bill group id: {$billGroup->id}, added transfer fee, final original amount is CNY {$totalOriginal}");
}
}
}
@@ -1,68 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\SeasonalSegment;
use Illuminate\Console\Command;
use Carbon\Carbon;
use App\Classes\Modules\Companies\Services\RemovesCompanyFromSegment;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Group;
use App\Models\Transaction;
use App\Models\Wallet;
use Illuminate\Support\Facades\Log;
class UpdateGroupWithTransferFee extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'updateGroupWithTransferFee';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Update group with transfer fee';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
ini_set('max_execution_time', 0);
set_time_limit(0);
$groups = Group::whereDate('updated_at', '<', Carbon::now())->get();
foreach ($groups as $group) {
$originalTransferFees = (float)Transaction::where('type', TransactionType::TRANSFER_FEE)->whereIn('owner_id', $group->transactions->pluck('id'))->sum('service_charge');
$correctOriginalAmount = $group->transactions()->sum('original_amount');
$correctOriginalAmount += $originalTransferFees;
$correctAmount = $group->transactions()->sum('amount');
$transferFees = $originalTransferFees / $group->currency_rate;
$correctAmount += $transferFees;
if ($group->original_amount != $correctOriginalAmount) {
$group->original_amount = $correctOriginalAmount;
$group->amount = $correctAmount;
$group->save();
}
}
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers\Open1688;
use App\Classes\Modules\Open1688\ControllersLogic\GetProductDetailLogic;
use App\Classes\Modules\Open1688\ControllersLogic\KeywordSearchProductLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class Open1688Controller
{
/**
* @param Request $request
* @param KeywordSearchProductLogic $logic
* @return JsonResponse
*/
public function keywordSearchProduct(Request $request, KeywordSearchProductLogic $logic): JsonResponse {
return $logic->execute($request);
}
/**
* @param Request $request
* @param GetProductDetailLogic $logic
* @return JsonResponse
*/
public function getProductDetail(Request $request, GetProductDetailLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
+2
View File
@@ -47,12 +47,14 @@ class Kernel extends HttpKernel
VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
// \App\Http\Middleware\WebRouteLogs::class,
\App\Http\Middleware\WebResponseTimeLog::class,
],
'api' => [
'throttle:300,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
// \App\Http\Middleware\ApiRouteLogs::class,
\App\Http\Middleware\ApiResponseTimeLog::class,
],
'apipub' => [
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class ApiResponseTimeLog
{
public function handle(Request $request, Closure $next)
{
// Get route information
$route = $request->route();
$routeName = $route ? $route->getName() : 'undefined';
$uri = $request->getPathInfo();
$startTime = microtime(true); // Start time
$response = $next($request); // Handle the request
$endTime = microtime(true); // End time
$responseTime = $endTime - $startTime; // Calculate the response time
Log::channel('apiResponseTimeLog')->info("\nRequest to route: {$uri} \nRoute name: {$routeName} \nTime Taken: " . number_format($responseTime * 1000, 2) . "ms\n");
return $response;
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class WebResponseTimeLog
{
public function handle(Request $request, Closure $next)
{
// Get route information
$route = $request->route();
$routeName = $route ? $route->getName() : 'undefined';
$uri = $request->getPathInfo();
$startTime = microtime(true); // Start time
$response = $next($request); // Handle the request
$endTime = microtime(true); // End time
$responseTime = $endTime - $startTime; // Calculate the response time
Log::channel('webResponseTimeLog')->info("\nRequest to route: {$uri} \nRoute name: {$routeName} \nTime Taken: " . number_format($responseTime * 1000, 2) . "ms\n");
return $response;
}
}
@@ -14,14 +14,23 @@ class TransactionDetailResource extends JsonResource
*/
public function toArray($request)
{
$item_from_1688 = str_contains($this->product_code, 'offerId');
$offer_id_1688 = null;
if ($item_from_1688) {
$offerId = explode(',', $this->product_code)[0];
$offer_id_1688 = substr($offerId, strpos($offerId, ':') + 1);
}
return [
'id' => $this->id,
'stockCode' => $this->product_code,
'description' => $this->product_name,
'quantity' => $this->quantity,
'unit_price' => (double) $this->price,
'total' => (double) $this->amount
'total' => (double) $this->amount,
'item_from_1688' => $item_from_1688,
'offer_id_1688' => $offer_id_1688
];
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ class VoucherResource extends JsonResource
public function toArray($request)
{
$filteredRedemptions = new ArrayObject([]);
if ($request->has('filters') && str_contains($request->input('filters'), "has_active_reward")) {
if ($request->has('filters') && str_contains($request->input('filters'), "has_vouchers_all")) {
$filteredRedemptions = new ArrayObject([]);
}
else{
+3 -1
View File
@@ -56,7 +56,9 @@ class StatementTransaction extends Model
public function scopeDoesntMapStatement($query)
{
$query->whereDoesntHave('owners', function($query){
$query->whereHas('account', function($query) {
$query->where('statement_accounts.number', 568603010762);
})->whereDoesntHave('owners', function($query){
return $query->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
})->orderBy('posting_date');
}
+16
View File
@@ -112,6 +112,22 @@ return [
'level' => 'info',
],
'apiResponseTimeLog' => [
'driver' => 'single',
'path' => storage_path('logs/apiResponseTime.log'),
'level' => 'info',
],
'webResponseTimeLog' => [
'driver' => 'single',
'path' => storage_path('logs/webResponseTimeLog.log'),
'level' => 'info',
],
'guzzleShippingPortal' => [
'driver' => 'errorlog',
'level' => 'debug',
],
'vue_polling' => [
'driver' => 'single',
'path' => storage_path('logs/laravel_vue_plling.log'),
+11
View File
@@ -0,0 +1,11 @@
<?php
return [
'app_key' => env('OPEN_1688_APP_KEY', ''),
'app_secret' => env('OPEN_1688_APP_SECRET', ''),
'access_token' => env('open_1688_ACCESS_TOKEN', ''),
'refresh_token' => env('open_1688_REFRESH_TOKEN', ''),
'url' => env('OPEN_1688_URL', ''),
'product_search_keyword_api' => env('OPEN_1688_PRODUCT_SEARCH_KEYWORD', ''),
'product_detail_api' => env('OPEN_1688_PRODUCT_DETAIL', ''),
];
@@ -13,21 +13,21 @@ class CreateRouteLogsTable extends Migration
*/
public function up()
{
Schema::create('route_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->unsigned();
$table->string('ip_address');
$table->string('url', 255);
$table->string('request_method');
$table->string('browser')->nullable();
$table->string('platform')->nullable();
$table->string('longitude')->nullable();
$table->string('latitude')->nullable();
$table->string('last_page')->nullable();
$table->string('countryname')->nullable();
$table->integer('route_type');
$table->timestamps();
});
// Schema::create('route_logs', function (Blueprint $table) {
// $table->id();
// $table->foreignId('user_id')->unsigned();
// $table->string('ip_address');
// $table->string('url', 255);
// $table->string('request_method');
// $table->string('browser')->nullable();
// $table->string('platform')->nullable();
// $table->string('longitude')->nullable();
// $table->string('latitude')->nullable();
// $table->string('last_page')->nullable();
// $table->string('countryname')->nullable();
// $table->integer('route_type');
// $table->timestamps();
// });
}
/**
@@ -0,0 +1,58 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateIndexForTransactionTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('transactions', function (Blueprint $table) {
$table->index('status');
$table->index('type');
$table->index('payment_method');
$table->index('payment_reference');
$table->index('bill_no');
$table->index('owner_type');
$table->index('owner_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('transactions', function (Blueprint $table) {
$table->dropIndex('owner_type');
$table->dropIndex('owner_id');
$table->dropIndex('type');
$table->dropIndex('issuer');
$table->dropIndex('receiver');
$table->dropIndex('recipient_bank_account_id');
$table->dropIndex('payment_method');
$table->dropIndex('payment_reference');
$table->dropIndex('bill_no');
$table->dropIndex('amount');
$table->dropIndex('original_amount');
$table->dropIndex('currency_id');
$table->dropIndex('original_currency_id');
$table->dropIndex('currency_rate');
$table->dropIndex('tax');
$table->dropIndex('service_charge');
$table->dropIndex('expires_on');
$table->dropIndex('status');
$table->dropIndex('deleted_at');
$table->dropIndex('created_at');
$table->dropIndex('updated_at');
});
}
}
+2 -1
View File
@@ -32,7 +32,8 @@
"vue": "^2.7.14",
"vue-avatar": "^2.1.8",
"vue-debounce": "^2.6.0",
"vue-template-compiler": "^2.7.14",
"vue-gtag": "^1.16.1",
"vue-template-compiler": "^2.6.10",
"vue-the-mask": "^0.11.1",
"vuelidate": "^0.7.4",
"vuex": "^3.1.1"
+13
View File
@@ -2,6 +2,7 @@
/** Dependencies */
import Vue from 'vue'
import store from './vuex/store';
import VueGtag from 'vue-gtag'
window.route = require('./general/functions/router');
@@ -59,6 +60,18 @@ Vue.component(Avatar);
const files = require.context('./', true, /\.vue$/i);
files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default));
// Google Analytics 4 (GA4) Integration
Vue.use(VueGtag, {
property: {
id: 'G-FEJNZTR0WP',
params: {
user_id: store.getters.getUserId
}
}
});
console.log('Google Tag pushed');
const app = new Vue({
el: '#app',
store,
@@ -136,7 +136,7 @@
case 'Sales':
return ['Exchange', 'Izyim', 'Lite', 'Cntr', 'Probashi', 'Pets'];
case 'Top Up':
return ['Exchange', 'Izyim'];
return ['Exchange', 'Izyim', 'Lite'];
default:
return [];
}
@@ -10,7 +10,8 @@
<div class="row">
<div class="col">{{item.owners.pending_verification[0].system}}</div>
<div class="col">{{ typeString(item.owners.pending_verification[0].type) }}</div>
<div class="col"><a :href="item.owners.pending_verification[0].reference_link" target="_blank">{{item.owners.pending_verification[0].reference}}</a></div>
<div class="col" v-if="item.owners.pending_verification[0].system === 'LITE' && typeString(item.owners.pending_verification[0].type) == 'Wallet Top Up'"><a :href="'https://lite.cief-malaysia.com/admin/index.php?route=sale/topup&filter_topup_id=' + item.owners.pending_verification[0].reference" target="_blank">{{item.owners.pending_verification[0].reference}}</a></div>
<div class="col" v-else><a :href="item.owners.pending_verification[0].reference_link" target="_blank">{{item.owners.pending_verification[0].reference}}</a></div>
</div>
</div>
@@ -76,26 +76,30 @@
</div>
<!-- filter for Pending Export tab -->
<div v-if="stage === 4">
<div class="row pt-3 pb-3">
<div class="col-5 col-md-3 ">
<validation-wrapper-component :validator="$v.parameters.startDate">
<label class="all-caps">Start Date</label>
<date-picker-component :parameters="parameters" v-model.lazy="parameters.startDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col-5 col-md-3 ">
<validation-wrapper-component :validator="$v.parameters.endDate">
<label class="all-caps">End Date</label>
<date-picker-component :parameters="parameters" v-model.lazy="parameters.endDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col-2 col-md-auto">
<div class="btn btn-lg btn-primary fs-11 w-100 h-100 d-flex justify-content-center align-items-center" @click="startMapping(4)">
<span>
Search
</span>
</div>
<div class="row pt-3 pb-3">
<div class="col-5 col-md-3 ">
<validation-wrapper-component :validator="$v.parameters.account" selectable>
<label class="all-caps">Account</label>
<select-component :options="['CIEF WORLDWIDE SDN. BHD. (568603010762)', 'CIEF WORLDWIDE SDN. BHD. AMBANK (8881040198515)']" v-model="parameters.account"></select-component>
</validation-wrapper-component>
</div>
<div class="col-5 col-md-3 ">
<validation-wrapper-component :validator="$v.parameters.startDate">
<label class="all-caps">Start Date</label>
<date-picker-component :parameters="parameters" v-model.lazy="parameters.startDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col-5 col-md-3 ">
<validation-wrapper-component :validator="$v.parameters.endDate">
<label class="all-caps">End Date</label>
<date-picker-component :parameters="parameters" v-model.lazy="parameters.endDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col-2 col-md-auto">
<div class="btn btn-lg btn-primary fs-11 w-100 h-100 d-flex justify-content-center align-items-center" @click="startMapping(stage)">
<span>
Search
</span>
</div>
</div>
</div>
@@ -231,6 +235,7 @@ export default {
parameters: {
startDate: '',
endDate: '',
account: '',
},
type: null,
stage: null,
@@ -253,6 +258,7 @@ export default {
endDate: {
required
},
account: {},
},
files: {
// required // todo-new: set required if is pdf section
@@ -327,6 +333,17 @@ export default {
this.files = [];
this.parameters = {};
},
getAccountId(){
if (this.parameters.account) {
if (this.parameters.account.includes('568603010762')) {
return 4;
}
if (this.parameters.account.includes('8881040198515')) {
return 5;
}
}
return false;
},
startMapping(stage){
this.stage = stage;
@@ -334,16 +351,17 @@ export default {
if(this.type === 1){
switch(this.stage){
case 1:
this.filter = {min_amount: 0, is_mapped: true, is_mapped_with_multiple: false, statement_transaction_owner_type_in: [1, 2], statement_transaction_owner_status_in: [1], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
this.filter = {min_amount: 0, is_mapped: true, is_mapped_with_multiple: false, statement_transaction_owner_type_in: [1, 2], statement_transaction_owner_status_in: [1], ...this.parameters.account && {statement_transaction_account_id: this.getAccountId()}, ...this.parameters.startDate && {statement_transaction_posting_start: this.parameters.startDate}, ...this.parameters.endDate && {statement_transaction_posting_end: this.parameters.endDate}, per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 2:
this.filter = {min_amount: 0, is_mapped: true, is_mapped_with_multiple: true, statement_transaction_owner_type_in: [1, 2], statement_transaction_owner_status_in: [1], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
this.filter = {min_amount: 0, is_mapped: true, is_mapped_with_multiple: true, statement_transaction_owner_type_in: [1, 2], statement_transaction_owner_status_in: [1], ...this.parameters.account && {statement_transaction_account_id: this.getAccountId()}, ...this.parameters.startDate && {statement_transaction_posting_start: this.parameters.startDate}, ...this.parameters.endDate && {statement_transaction_posting_end: this.parameters.endDate}, per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 3:
this.filter = {min_amount: 0, is_mapped_false_or_mapped_but_status_in: [4], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
this.filter = {min_amount: 0, is_mapped_false_or_mapped_but_status_in: [4], ...this.parameters.account && {statement_transaction_account_id: this.getAccountId()}, ...this.parameters.startDate && {statement_transaction_posting_start: this.parameters.startDate}, ...this.parameters.endDate && {statement_transaction_posting_end: this.parameters.endDate}, per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 4:
this.filter = {min_amount: 0, is_mapped: true, statement_transaction_owner_type_in: [1, 2], statement_transaction_owner_status_in: [2], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
this.filter = {min_amount: 0, is_mapped: true, statement_transaction_owner_type_in: [1, 2], statement_transaction_owner_status_in: [2], ...this.parameters.account && {statement_transaction_account_id: this.getAccountId()}, per_page: 100, order_by: {column: 'posting_date', DESC: true}}
if (typeof this.parameters.startDate != 'undefined' && this.parameters.startDate != '') this.filter = {...this.filter, ...{statement_transaction_posting_start: this.parameters.startDate}};
@@ -355,16 +373,16 @@ export default {
if(this.type === 2){
switch(this.stage){
case 1:
this.filter = {max_amount: 0, is_mapped: true, is_mapped_with_multiple: false, statement_transaction_owner_type_in: [3, 5],statement_transaction_owner_status_in: [1], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
this.filter = {max_amount: 0, is_mapped: true, is_mapped_with_multiple: false, statement_transaction_owner_type_in: [3, 5],statement_transaction_owner_status_in: [1], ...this.parameters.account && {statement_transaction_account_id: this.getAccountId()}, ...this.parameters.startDate && {statement_transaction_posting_start: this.parameters.startDate}, ...this.parameters.endDate && {statement_transaction_posting_end: this.parameters.endDate}, per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 2:
this.filter = {max_amount: 0, is_mapped: true, is_mapped_with_multiple: true, statement_transaction_owner_type_in: [3, 5], statement_transaction_owner_status_in: [1], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
this.filter = {max_amount: 0, is_mapped: true, is_mapped_with_multiple: true, statement_transaction_owner_type_in: [3, 5], statement_transaction_owner_status_in: [1], ...this.parameters.account && {statement_transaction_account_id: this.getAccountId()}, ...this.parameters.startDate && {statement_transaction_posting_start: this.parameters.startDate}, ...this.parameters.endDate && {statement_transaction_posting_end: this.parameters.endDate}, per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 3:
this.filter = {max_amount: 0, is_mapped: false, per_page: 100, order_by: {column: 'posting_date', DESC: true}}
this.filter = {max_amount: 0, is_mapped: false, ...this.parameters.account && {statement_transaction_account_id: this.getAccountId()}, ...this.parameters.startDate && {statement_transaction_posting_start: this.parameters.startDate}, ...this.parameters.endDate && {statement_transaction_posting_end: this.parameters.endDate}, per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 4:
this.filter = {max_amount: 0, is_mapped: true, statement_transaction_owner_type_in: [3, 5], statement_transaction_owner_status_in: [2], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
this.filter = {max_amount: 0, is_mapped: true, statement_transaction_owner_type_in: [3, 5], statement_transaction_owner_status_in: [2], ...this.parameters.account && {statement_transaction_account_id: this.getAccountId()}, per_page: 100, order_by: {column: 'posting_date', DESC: true}}
if (typeof this.parameters.startDate != 'undefined' && this.parameters.startDate != '') this.filter = {...this.filter, ...{statement_transaction_posting_start: this.parameters.startDate}};
@@ -0,0 +1,42 @@
<template>
<div class="row h-100 parentContainer">
<div class="col">
<a :href="route('rewards')" >
<div class="text-white all-caps fs-12">Vouchers</div>
<div v-if="showVoucherUpdateIndicator" class="update-indicator"></div>
</a>
</div>
</div>
</template>
<script>
export default {
computed: {
showVoucherUpdateIndicator() {
return this.$store.getters.showVoucherUpdateIndicator;
}
},
created(){
this.submit(this.route('api.voucher.user.list') + '?filters=' + JSON.stringify( { 'has_vouchers_all': true, order_by:{ column:'id', DESC:true }} ), 'get', 'voucherNavigationSection', false, false);
},
methods: {
successHandler(response){
this.$store.dispatch('updateLatestVoucherTimestamp', response.payload.data.length > 0 ? response.payload.data[0].created_at : null)
.then(() => {
this.$store.dispatch('updateVoucherCount', response.payload.data.length);
});
},
}
}
</script>
<style>
.update-indicator {
width: 10px;
height: 10px;
background-color: red;
border-radius: 50%;
position: absolute;
top: 0px;
right: 5px;
}
</style>
@@ -68,11 +68,13 @@
},
successHandler(){
this.$store.dispatch('userAuthentication', {access_token: '', redirect_url: this.route('login')});
this.$store.dispatch('clearVoucher');
},
errorHandler(){
this.$store.dispatch('userAuthentication', {access_token: '', redirect_url: this.route('login')});
this.$store.dispatch('clearVoucher');
}
}
}
</script>
</script>
@@ -0,0 +1,34 @@
<template>
<div class="row p-2 pointer h-100">
<div class="col no-padding card btn btn-raised shadow">
<img :src="itemDetails.imageUrl" class="w-100" alt="productImg"/>
<div class="col p-2 d-flex flex-column">
<div class="row align-top">
<div class="col">
<div class="text-left">{{ itemDetails.subject }}</div>
<div class="muted p-t-10 text-left">复购率{{ itemDetails.repurchaseRate }}</div>
<div class="muted text-left">销量{{ itemDetails.monthSold }}</div>
</div>
</div>
<div class="row" style="margin-top: auto;">
<div class="col">
<div class="fs-20 bold text-right" style="color: #ff6000;">¥{{ itemDetails.priceInfo.price }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import componentHandler from '../../../general/mixins/componentHandler';
export default {
props:{
itemDetails: {
type: Object,
required: true
}
},
mixins: [componentHandler]
}
</script>
@@ -12,11 +12,14 @@
<span>{{ item.voucher.code }}</span>
</div>
<div class="row">
<span v-if="item.voucher.end_date">
<span v-if="item.voucher.end_date && new Date() > new Date(item.voucher.end_date)">
This voucher has expired.
</span>
<span v-else-if="item.voucher.end_date">
Valid till {{ item.voucher.end_date }}
</span>
<span v-else>
No expiry date
Non-expired
</span>
</div>
</div>
@@ -62,7 +65,7 @@
fetchVouchers(){
this.isLoading = true;
if(this.employee){
this.submit(route('api.voucher.user.list') + '?filters=' + JSON.stringify( { 'has_active_reward': this.employee.id} ), 'get', this.section, false, false);
this.submit(route('api.voucher.user.list') + '?filters=' + JSON.stringify( { 'has_vouchers_all': this.employee.id} ), 'get', this.section, false, false);
}
},
successHandler(response){
@@ -15,9 +15,9 @@
</div>
<div class="row m-b-10">
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.fee">
<validation-wrapper-component :validator="$v.parameters.group_transfer_fee">
<label class="all-caps">Transfer Fee</label>
<input type="text" class="form-control" v-model.lazy="parameters.fee" v-money="{decimal: '.',thousands: ',', precision: 2}">
<input type="text" class="form-control" v-model.lazy="parameters.group_transfer_fee" v-money="{decimal: '.',thousands: ',', precision: 2}">
</validation-wrapper-component>
</div>
<div class="col-auto b-r b-t b-b b-grey">
@@ -54,24 +54,34 @@
id: {
type: Number,
required: true
}
},
rate:{
type: Number,
required: true
},
supplier_id: {
type: Number,
required: true
},
},
data(){
return {
error: '',
parameters: {
fee: (Math.round((this.transfer_fee + Number.EPSILON) * 100) / 100).toFixed(2)
supplier_id: this.supplier_id,
rate: this.rate,
group_transfer_fee: (Math.round((this.transfer_fee + Number.EPSILON) * 100) / 100).toFixed(2)
},
}
},
validations: {
parameters: {
fee: { },
group_transfer_fee: { },
},
},
methods: {
submitForm() {
this.submit(route('api.transaction.group.fee.update', this.id), 'put', this.section, true, true);
this.submit(route('api.transaction.group.update', this.id), 'put', this.section, true, true);
}
},
mixins: [ModalFormHandler]
@@ -26,11 +26,14 @@
<p v-if="item.voucher.type == 'AMOUNT'">RM{{ item.voucher.value/100 }} Discount</p>
<p v-if="item.voucher.type == 'PERCENT'">{{ item.voucher.value }}% Discount</p>
</div>
<div class="col" v-if="item.voucher.end_date">
<div class="col" v-if="item.voucher.end_date && new Date() > new Date(item.voucher.end_date)">
This voucher has expired.
</div>
<div class="col" v-else-if="item.voucher.end_date">
Valid till {{ item.voucher.end_date }}
</div>
<div class="col" v-else>
No expiry date
Non-expired
</div>
</div>
</div>
@@ -76,7 +79,7 @@
fetchVouchers(){
this.isLoading = true;
if(this.employee){
this.submit(route('api.voucher.user.list') + '?filters=' + JSON.stringify( { 'has_active_reward': this.employee.id} ), 'get', this.section, false, false);
this.submit(route('api.voucher.user.list') + '?filters=' + JSON.stringify( { 'has_vouchers_all': this.employee.id} ), 'get', this.section, false, false);
}
},
successHandler(response){
@@ -289,6 +289,17 @@
</div>
</div>
</div>
<div class="row m-t-10" v-show="[2, 3].includes(item.status)">
<div class="col" v-if="data.booking.company.shipping_company_module_id">
<button class="btn btn-sm all-caps b-rad-none btn-success btn-block" @click="trackShipment">Track your shipment</button>
</div>
<div class="col" v-else>
<button class="btn btn-sm all-caps b-rad-none btn-success btn-block requestModal" data-type="connectIzyim">Track your shipment with us</button>
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="connectIzyim" size="large">
<connect-izyim-form-component :data="data" :section="section"></connect-izyim-form-component>
</modal-component>
</div>
</div>
<div class="row m-t-10" v-show="[2, 3].includes(item.status) && (totalRequestedRefund + totalRefunds) < data.original_amount">
<div class="col" v-if="$store.getters.isAdmin">
<button class="btn btn-xs all-caps b-rad-none bg-master-lighter btn-block no-border requestModal" data-type="transferSummary">Request Refund</button>
@@ -2,7 +2,7 @@
<div class="row m-b-10 parentContainer">
<div class="col">
<loading-component style="height: 200px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
<div class="row align-items-center pointer p-b-5 b-b b-grey" v-show="!isLoading" @click="activate()">
<div class="row align-items-center pointer p-b-5 b-b b-grey" v-show="!isLoading" @click="activate($event)">
<div class="col-auto p-r-0">
<div class="b-grey b-a fs-10 btn-rounded icon-thumbnail icon-25 m-r-0" :class="[{'bg-primary': active}, {'bg-transparent': !active}]">
<i class="fa fa-check text-white fs-12 fa-fw"></i>
@@ -87,9 +87,12 @@
<button class="btn btn-xs btn-primary b-rad-none requestModal" data-type="editTransactionGroup">
Edit Transfer Fee
</button>
<modal-component small type="editTransactionGroup">
<edit-transfer-fee-form-component :section="section" :transfer_fee="item.transfer_fee" :id="item.id"></edit-transfer-fee-form-component>
</modal-component>
<div class="do-not-activate">
<modal-component small type="editTransactionGroup">
<edit-transfer-fee-form-component :section="section" :transfer_fee="item.transfer_fee" :id="item.id" :supplier_id="item.issuer_id" :rate="item.currency_rate"></edit-transfer-fee-form-component>
</modal-component>
</div>
</div>
</div>
</div>
@@ -123,20 +126,33 @@
payments:{
type: Array,
required: true
}
},
emptyPaymentList: {
type: Function,
required: true
},
},
data(){
return {
expanded: false,
active: this.item ? this.payments.some(payment => payment.id === this.item.id) : false,
// active: this.item ? this.payments.some(payment => payment.id === this.item.id) : false,
}
},
created(){
this.active = this.payments.some(payment => payment.id === this.item.id);
// this.active = this.payments.some(payment => payment.id === this.item.id);
},
computed: {
active() {
return this.payments.some(payment => payment.id === this.item.id);
}
},
methods: {
activate(){
this.active = !this.active;
activate(event){
if ((event.target.tagName.toLowerCase() === 'button' && event.target.getAttribute('data-type') === 'editTransactionGroup') || Boolean(event.target.closest('.do-not-activate'))) {
this.emptyPaymentList();
return;
}
// this.active = !this.active;
this.$emit('input', this.item)
},
expand(event){
@@ -0,0 +1,270 @@
<template>
<div class="row bg-white p-5 overflow-auto" style="height: 90vh;width: 1200px;">
<div class="col">
<loading-component style="height: 300px; top: 0;" key="1" color="success" v-if="isLoading"></loading-component>
<div class="row justify-content-center" v-if="!isLoading && productInfo">
<div class="col-4">
<div class="row b-a b-grey">
<div class="col" style="min-height: 330px;">
<img v-if="!selectedContent.isVideo" :src="selectedContent.link" class="w-100" alt="productImg"/>
<video v-else :src="selectedContent.link" style="width: 100%;height: 100%;object-fit: contain;" controls></video>
</div>
</div>
<div class="row">
<div class="col no-padding card btn btn-raised shadow" v-if="productInfo.mainVideo">
<video :src="productInfo.mainVideo" style="width: 100%;height: 100%;object-fit: contain;" @click="setSelectedContent(productInfo.mainVideo, true)"></video>
</div>
<div class="col no-padding card btn btn-raised shadow" v-for="image in productInfo.productImage.images" @click="setSelectedContent(image)">
<img :src="image" class="w-100" alt="productImg"/>
</div>
</div>
</div>
<div class="col-8">
<div class="row">
<div class="col">
<div class="fs-18">{{ productInfo.subject }}</div>
</div>
</div>
<div class="row mx-1">
<div class="col">
<div class="fs-28 bold" style="color: #ff6000;">{{ variantInfo ? "CNY ¥" + variantInfo.price : productInfo.priceRange }}</div>
</div>
</div>
<div class="row m-b-20 mx-1" v-for="(selections, key) in productInfo.variants">
<div class="col">
<div class="row">
<div class="col">
<div class="fs-18">
{{ key }}:
<span class="text-primary"> {{ selectedVariantAttr[key] }}</span>
</div>
</div>
</div>
<div class="row">
<div v-for="selection in selections">
<div class="m-1">
<button v-if="selection.image" class="col-auto no-padding card btn shadow" :disabled="shouldDisable(key, selection.value)" :class="[{'disabled': shouldDisable(key, selection.value)}, {'b-success': selectedVariantAttr[key] === selection.value}]" @click="onClickAttr(key, selection)">
<img :src="selection.image" style="object-fit: cover;width: 65px;height: 65px;border-radius: 3px;" alt="selectionImg"/>
</button>
<button v-else class="col-auto no-padding card btn btn-raised btn shadow" :disabled="shouldDisable(key, selection.value)" :class="[{'disabled': shouldDisable(key, selection.value)}, {'b-success': selectedVariantAttr[key] === selection.value}]" style="height: 65px;" @click="onClickAttr(key, selection)">
<div class="fs-14 text-center px-3" :class="[{'text-success': selectedVariantAttr[key] === selection.value}]" style="margin-top: auto;margin-bottom: auto;">{{ selection.value }}</div>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="col-12 pl-md-1">
<div class="form-group form-group-default b-rad-none no-padding no-border">
<div class="row no-margin">
<div id="minus-quantity" name="minus-quantity" class="col-auto bg-master-lightest pointer" @click="updateQuantity('minus')">
<div class="row h-100 align-items-center">
<div class="col">
<i class="fa fa-minus"></i>
</div>
</div>
</div>
<div class="col-auto no-padding">
<label class="p-t-5 p-l-5 text-center">Quantity</label>
<input id="quantity" name="quantity" type="text" class="form-control m-b-5 b-rad-none no-border text-center fs-18" v-model.lazy="quantity" v-mask="'#########'"/>
</div>
<div id="add-quantity" name="add-quantity" class="col-auto bg-master-lightest pointer" @click="updateQuantity('add')">
<div class="row h-100 align-items-center">
<div class="col">
<i class="fa fa-plus"></i>
</div>
</div>
</div>
<div class="col-auto d-flex align-items-center muted" v-if="productInfo.variantInfo[this.uniqueVariantKey]">
<div class="fs-16">库存: {{ productInfo.variantInfo[this.uniqueVariantKey].amountOnSale }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<button class="btn btn-lg all-caps b-rad-none btn-success btn-block" @click="addToPurchaseOrder()">Add to purchase order</button>
</div>
</div>
</div>
<div class="col-12 tabsContainer b-a b-grey mt-5">
<div class="row">
<div class="col bg-master-lighter card btn shadow active tabButton padding-15" tab-name="description">
<div class="row">
<div class="col">
<div class="fs-12 m-t-5 all-caps">商品详情</div>
</div>
</div>
</div>
<div class="col bg-master-lighter card btn shadow tabButton padding-15" tab-name="property">
<div class="row">
<div class="col">
<div class="fs-12 m-t-5 all-caps">规格参数</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col tabsContainer tabContent active" tab-name="description">
<div class="row py-3">
<div class="col-12 d-flex justify-content-center" v-if="productInfo.detailVideo">
<video :src="productInfo.detailVideo" style="width: 85%;height: 100%;object-fit: contain;" controls></video>
</div>
</div>
<div class="row">
<div class="col-12 d-flex justify-content-center">
<div v-html="productInfo.description"></div>
</div>
</div>
</div>
<div class="col tabsContainer tabContent hide d-flex justify-content-center" tab-name="property">
<table class="w-100 table table-bordered my-3">
<tr v-for="(value, key) in productInfo.productAttribute">
<td>
{{ key }}
</td>
<td>
{{ value }}
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import componentHandler from '../../../general/mixins/componentHandler';
export default {
props: {
bookingId: {
required: true,
type: Number
}
},
data(){
return {
section: 'bookingDetailSection',
isLoading: true,
isFetchingProduct: true,
productInfo: null,
priceSection: '',
selectedVariantAttr: {},
uniqueVariantKey: '',
variantInfo: null,
selectedContent: {},
quantity: 0
}
},
watch: {
'data': function() {
this.fetchProduct();
},
},
methods: {
updateQuantity(type){
type === 'add' ? this.quantity++ : this.quantity > 0 ? this.quantity-- : null
},
setSelectedContent(link, isVideo = false) {
this.selectedContent = {
link,
isVideo
}
},
shouldDisable(key, value) {
let combineKey = '';
this.productInfo.variantKeyCombinationOrder.forEach((variantKey) => {
if (variantKey !== key) {
combineKey += this.selectedVariantAttr[variantKey];
} else {
combineKey += value;
}
});
if (combineKey.includes("undefined")) {
return false;
} else {
return this.productInfo.variantInfo[combineKey] ? false : true;
}
},
onClickAttr(key, selection) {
if (selection.image) {
this.setSelectedContent(selection.image)
}
this.$set(this.selectedVariantAttr, key, selection.value);
this.uniqueVariantKey = '';
this.productInfo.variantKeyCombinationOrder.forEach((variantKey) => {
this.uniqueVariantKey += this.selectedVariantAttr[variantKey];
});
this.variantInfo = this.productInfo.variantInfo[this.uniqueVariantKey];
},
addToPurchaseOrder() {
this.variantInfo = this.productInfo.variantInfo[this.uniqueVariantKey];
if (this.variantInfo && this.quantity > 0) {
const variantdescription = Object.keys(this.selectedVariantAttr).map((variantKey) => `${variantKey}:${this.selectedVariantAttr[variantKey]}`).join(',')
const productDescription = `${this.productInfo.subject}:{${variantdescription}}`;
let product = {
stockCode: `offerId:${this.productInfo.offerId},skuId:${this.variantInfo.skuId}`,
description: productDescription,
quantity: this.quantity,
unit_price: parseFloat((this.variantInfo.price).toString().replaceAll(',', '')),
total: this.quantity * parseFloat((this.variantInfo.price).toString().replace(',', ''))
}
this.parameters = {
products: [product],
pushToExisting: true,
};
this.isFetchingProduct = false;
this.submit(route('api.transaction.po.create', this.bookingId), 'post', 'bookingDetailSection', true, true);
}
},
fetchProduct() {
this.isFetchingProduct = true;
this.parameters = null;
this.isLoading = true;
this.submit(route('api.open1688.getProductDetail', this.data.offerId), 'get', 'bookingDetailSection', false, true)
},
successHandler(response){
if (this.isFetchingProduct) {
this.productInfo = response.payload.data;
this.selectedVariantAttr = {};
this.priceSection = response.payload.data.priceRange;
this.setSelectedContent(response.payload.data.productImage.images[0])
this.uniqueVariantKey = '';
this.variantInfo = null;
this.quantity = 0;
this.isLoading = false;
} else {
this.closeModal();
this.updateList();
}
},
errorHandler(error){
if (this.isFetchingProduct) {
this.productInfo = null;
this.selectedVariantAttr = {};
this.priceSection = '';
this.setSelectedContent({})
this.uniqueVariantKey = '';
this.variantInfo = null;
this.quantity = 0;
this.isLoading = false;
}
},
},
mixins: [componentHandler]
}
</script>
@@ -93,8 +93,8 @@
</div>
<div class="row m-b-20">
<div class="col">
<p class="no-margin" v-if="serviceType.id === 1">The recipient can expect to receive the transfer within <span class="text-success bold">1-3 working days</span>. Explore our BANK TRANSFER (SAVER) option for a better rate!</p>
<p class="no-margin" v-if="serviceType.id === 3">Enjoy a <span class="bold text-underline">better rate</span> with this option! The recipient will receive the transfer after <span class="text-success bold">3-5 working days.</span>.</p>
<p class="no-margin" v-if="serviceType.id === 1">The recipient can expect to receive the transfer within <span class="text-success bold">3-5 working days</span>. Explore our BANK TRANSFER (SAVER) option for a better rate!</p>
<p class="no-margin" v-if="serviceType.id === 3">Enjoy a <span class="bold text-underline">better rate</span> with this option! The recipient will receive the transfer after <span class="text-success bold">5-7 working days.</span>.</p>
</div>
</div>
<div class="row">
@@ -268,7 +268,8 @@
<div class="row m-l-0 m-r-0" v-show="showApplyVoucher" style="height: 20px">
<i class="fa fa-spinner fa-spin m-b-5" v-if="voucherIsChecking"></i>
<span class="text-danger" v-if="voucherCodeFailedReason">{{ voucherCodeFailedReason }}</span>
<span class="text-success" v-if="voucherCodeFailedReason === '' && voucherValidated">Voucher applied</span>
<span class="text-success" v-if="voucherCodeFailedReason === '' && voucherValidated && voucherCode.trim()">Voucher applied</span>
<span class="text-danger" v-else-if="voucherCodeFailedReason === '' && voucherValidated ">No voucher applied</span>
</div>
<div class="row m-l-0 m-r-0" v-show="showApplyVoucher">
<div class="col">
@@ -22,7 +22,10 @@
<div class="col">
<div class="row">
<div class="col">
<p class="no-margin bold fs-12">{{product.description}}</p>
<a :href="`https://detail.1688.com/offer/${product.offer_id_1688}.html`" target="_blank" v-if="product.item_from_1688">
<p class="no-margin bold fs-12">{{product.description}}</p>
</a>
<p class="no-margin bold fs-12" v-else>{{product.description}}</p>
</div>
</div>
<div class="row">
@@ -0,0 +1,147 @@
<template>
<div class="row">
<div class="col">
<div class="row p-b-5">
<div class="col-auto">
<div class="row d-flex align-items-center">
<div class="col">
<img src="/images/1688_logo.png" alt="" width="40">
</div>
<div class="col-auto">
<div class="fs-14 bold all-caps">1688 Product Search</div>
</div>
</div>
</div>
</div>
<div class="row" @keyup.enter="searchProducts">
<div class="col-8">
<validation-wrapper-component :validator="$v.keyword">
<label class="all-caps">Keyword</label>
<input type="text" class="form-control" v-model.lazy="keyword">
</validation-wrapper-component>
</div>
<div class="col-2 d-flex align-items-center justify-content-center mt-2 mt-md-0">
<button type="button" class="btn btn-lg btn-primary fs-11 w-100 d-block mr-2 mr-md-0" @click="searchProducts()">Search</button>
</div>
<div class="col-2 d-flex align-items-center justify-content-center mt-2 mt-md-0">
<button type="button" class="btn btn-lg btn-primary fs-11 w-100 d-block mr-2 mr-md-0" @click="showProductList = !showProductList">{{ showProductList ? "Hide" : "Show" }} Section</button>
</div>
</div>
<div v-show="showProductList">
<div class="row">
<div class="col-12" style="min-height: 20px;" v-show="isLoading">
<loading-component style="height: 470px; top: 0;" key="1" color="success"></loading-component>
</div>
<div class="col-12 m-t-10" v-show="products.length === 0 && !isLoading">
<div class="row align-items-center justify-content-center hint-text">
<div class="col-4 hint-text"><img src="/images/not-found-illustration.png" class="w-100 hint-text"/></div>
</div>
<div class="row text-center">
<div class="col">
<div class="row m-t-20">
<div class="col">
<p class="all-caps no-margin fs-11" style="letter-spacing: 2px;">Nothing To Show Here</p>
</div>
</div>
<div class="row m-t-5 align-items-center justify-content-center">
<div class="col">
<small class="fs-9 muted all-caps font-lato" style="letter-spacing: 2px">There is no results found, Try adjusting your filters to find what you are looking for.</small>
</div>
</div>
</div>
</div>
</div>
<div class="col" v-show="products.length > 0">
<div class="row" style="padding-left: 15px;padding-right: 15px;">
<div class="col-lg-2 col-md-3 col-sm-6 requestModal" data-type="alibabaProduct" @click="selectedProduct=product" v-for="product in products">
<alibaba-product-overview-component :itemDetails="product"></alibaba-product-overview-component>
</div>
</div>
<div>
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="alibabaProduct">
<alibaba-product-detail-form-component :data="selectedProduct" :bookingId="bookingId" section="alibabaProductDetailSection"></alibaba-product-detail-form-component>
</modal-component>
</div>
</div>
</div>
<div class="row" v-if="products.length > 0">
<div class="col-12 p-t-20 d-flex align-items-center justify-content-center">
<nav>
<ul class="pagination">
<li class="page-item pointer" @click="setPage(1)">
<div class="page-link" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
<span class="sr-only">Previous</span>
</div>
</li>
<li class="page-item pointer" @click="setPage(currentPage - 2)"><div class="page-link" v-if="currentPage === totalPage">{{ currentPage - 2 }}</div></li>
<li class="page-item pointer" @click="setPage(currentPage - 1)"><div class="page-link" v-if="currentPage - 1 > 0">{{ currentPage - 1 }}</div></li>
<li class="page-item pointer active" @click="setPage(currentPage)"><div class="page-link">{{ currentPage }}</div></li>
<li class="page-item pointer" @click="setPage(currentPage + 1)"><div class="page-link" v-if="currentPage !== totalPage">{{ currentPage + 1 }}</div></li>
<li class="page-item pointer" @click="setPage(currentPage + 2)"><div class="page-link" v-if="currentPage === 1">{{ currentPage + 2 }}</div></li>
<li class="page-item pointer" @click="setPage(totalPage)">
<div class="page-link" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
<span class="sr-only">Next</span>
</div>
</li>
</ul>
</nav>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { required } from "vuelidate/lib/validators";
export default {
props: {
bookingId: {
required: true,
type: Number
}
},
data(){
return {
keyword: '',
isLoading: false,
products: [],
currentPage: 1,
totalPage: 0,
selectedProduct: null,
showProductList: false,
}
},
validations: {
keyword: {
required
}
},
methods: {
setPage(newPage) {
this.currentPage = newPage;
this.searchProducts()
},
searchProducts(){
if (this.keyword) {
this.showProductList = true;
this.products = [];
this.isLoading = true;
}
this.submit(route('api.open1688.keywordSearchProduct') + '?page=' + this.currentPage + '&keyword=' + this.keyword, 'get', 'alibabaSiteSection', false, true);
},
successHandler(response){
this.isLoading = false;
this.totalPage = response.payload.totalPage;
if (response.payload.totalRecords > 0) {
this.products = response.payload.data;
}
},
errorHandler(error){
this.isLoading = false;
},
}
}
</script>
@@ -110,10 +110,15 @@
</div>
</div>
</div>
<div class="row" v-if='booking.service.id === 4'>
<div class="col">
<alibaba-site-section-component :bookingId="booking.id"></alibaba-site-section-component>
</div>
</div>
<div class="row">
<div class="col-12 col-sm-12 col-md">
<div class="row no-margin relative b-b b-dashed b-grey">
<div class="absolute" style="top: -5px;right: -5px;width: 0px;height: 0px;border-style: solid;border-width: 0px 80px 80px 0px;border-color: transparent rgb(249, 249, 249) transparent transparent;z-index: 2;"></div>
<div class="absolute" style="top: 0;right: -5px;width: 0px;height: 0px;border-style: solid;border-width: 0px 80px 80px 0px;border-color: transparent rgb(249, 249, 249) transparent transparent;z-index: 2;"></div>
<div class="absolute shadow-sm " style="top: 0px; right: 0px; width: 0px; height: 0px; border-style: solid; border-width: 70px 0px 0px 70px; border-color: transparent transparent transparent rgb(255, 255, 255); z-index: 1;"></div>
<div class="col bg-white padding-25">
<div class="row">
@@ -460,6 +465,7 @@
</div>
</div>
<div class="col col-sm-12 col-md-3">
<vouchers-summary-component></vouchers-summary-component>
<wallet-component class="m-b-20" :data="booking.company"></wallet-component>
<verification-warning-component :data="booking.company"></verification-warning-component>
</div>
@@ -48,7 +48,7 @@
<div class="col">
<list-component :key="currencyOrderKey" section="transactionGroupsListPaymentSection" :options="{'per_page': 20, 'without_bill_group': true, 'issuer_in': [this.selectedSupplier.id]}" :endpoint="route('api.transaction.group.list')">
<template slot="list" slot-scope="{data}">
<transaction-group-payment-component section="transactionGroupsListPaymentSection" :data="data" :payments="payments" v-on:input="updateOrder($event)"></transaction-group-payment-component>
<transaction-group-payment-component section="transactionGroupsListPaymentSection" :data="data" :payments="payments" v-on:input="updateOrder($event)" :emptyPaymentList="emptyPaymentList"></transaction-group-payment-component>
</template>
</list-component>
</div>
@@ -103,6 +103,9 @@
this.selectedSupplier.status = false;
this.payments = [];
},
emptyPaymentList() {
this.payments = [];
},
updateOrder(payment){
this.payments.some(item => item.id === payment.id) ? this.payments = this.payments.filter(item => item.id !== payment.id) : this.payments.push(payment);
}
@@ -117,16 +117,16 @@
</div>
<div class="row">
<div class="col">
<!-- <list-component ref="pendingOrdersList" section="pendingOrdersSection" :endpoint="route('api.transaction.list')" :options="{per_page: 5, status: 2, owner_type: 'App\\Models\\Booking', type: 1, original_currency_id_in: [selectedCurrency.id], transaction_service_id: selectedService.id, does_not_have_refund_in_progress: true}">
<list-component ref="pendingOrdersList" section="pendingOrdersSection" :endpoint="route('api.transaction.list')" :options="{per_page: 5, status: 2, owner_type: 'App\\Models\\Booking', type: 1, original_currency_id_in: [selectedCurrency.id], transaction_service_id: selectedService.id, does_not_have_refund_in_progress: true}">
<template slot="list" slot-scope="{data}">
<supplier-pending-order-component :data="data" v-on:input="updateOrder($event)"></supplier-pending-order-component>
</template>
</list-component> -->
<list-polling-component ref="pendingOrdersList" section="pendingOrdersSection" :endpoint="route('api.transaction.list.job')" :options="{per_page: 5, status: 2, owner_type: 'App\\Models\\Booking', type: 1, original_currency_id_in: [selectedCurrency.id], transaction_service_id: selectedService.id, does_not_have_refund_in_progress: true}">
</list-component>
<!-- <list-polling-component ref="pendingOrdersList" section="pendingOrdersSection" :endpoint="route('api.transaction.list.job')" :options="{per_page: 5, status: 2, owner_type: 'App\\Models\\Booking', is_not_fully_refunded: true, type: 1, original_currency_id_in: [selectedCurrency.id], transaction_service_id: selectedService.id}">
<template slot="list" slot-scope="{data}">
<supplier-pending-order-component :data="data" v-on:input="updateOrder($event)"></supplier-pending-order-component>
</template>
</list-polling-component>
</list-polling-component> -->
</div>
</div>
</div>
@@ -17,11 +17,14 @@
<p v-if="item.voucher && item.voucher.is_redeemed">
Voucher claimed
</p>
<p v-else-if="item.voucher.end_date && new Date() > new Date(item.voucher.end_date)">
This voucher has expired.
</p>
<p v-else-if="item.voucher && item.voucher.end_date">
Valid till {{ item.voucher.end_date }}
</p>
<p v-else>
-
Non-expired
</p>
</div>
</div>
@@ -201,6 +201,7 @@
</div>
</div>
<div class="col-12 col-lg-3">
<vouchers-summary-component></vouchers-summary-component>
<wallet-component :data="company"></wallet-component>
<verification-warning-component v-if="!isLoading" :data="company"></verification-warning-component>
<new-service-announcement-component :data="company"></new-service-announcement-component>
@@ -141,6 +141,7 @@
<div class="col">
<div class="row">
<div class="col m-b-15">
<vouchers-summary-admin-component v-if="company" :company="company"></vouchers-summary-admin-component>
<wallet-component :data="company" :creditable=true></wallet-component>
</div>
</div>
@@ -213,4 +214,4 @@
}
}
}
</script>
</script>
@@ -52,7 +52,7 @@
<div class="col">
<div class="row">
<div class="col" v-if="this.$store.getters.getUserId">
<list-component section="customerVouchersListSection" :endpoint="route('api.voucher.user.list')" :options="{ 'has_active_reward': id }">
<list-component section="customerVouchersListSection" :endpoint="route('api.voucher.user.list')" :options="{ 'has_vouchers_all': id }">
<template slot="list" slot-scope="{data}">
<single-user-reward-item-component :data="data"></single-user-reward-item-component>
</template>
@@ -69,10 +69,15 @@
<div class="col">
<div class="row">
<div class="col" v-if="this.$store.getters.getUserId">
<list-component section="customerRewardsListSection" :endpoint="route('api.reward.list.details.admin', id)" :options="{'is_active': true, order_by:{ column:'order', DESC:false}}">
<!-- <list-component section="customerRewardsListSection" :endpoint="route('api.reward.list.details.admin', id)" :options="{'is_active': true, order_by:{ column:'order', DESC:false}}">
<template slot="list" slot-scope="{data}">
<single-reward-details-item-component :data="data"></single-reward-details-item-component>
</template>
</list-component> -->
<list-component section="customerRewardsListSection" :endpoint="route('api.voucher.user.list')" :options="{'has_active_reward_for_admin': id}">
<template slot="list" slot-scope="{data}">
<single-user-reward-item-component :data="data"></single-user-reward-item-component>
</template>
</list-component>
</div>
</div>
@@ -19,7 +19,7 @@
</div>
</div>
</div>
<div class="col m-r-5 d-none d-md-inline">
<!-- <div class="col m-r-5 d-none d-md-inline">
<div class="row fs-12 text-center">
<div class="col p-t-20 p-b-20 bg-master-lighter tabButton" tab-name="rewards">
<div class="row">
@@ -29,7 +29,7 @@
</div>
</div>
</div>
</div>
</div> -->
<div class="col m-r-5 d-none d-md-inline">
<div class="row fs-12 text-center">
<div class="col p-t-20 p-b-20 bg-master-lighter tabButton" tab-name="used">
@@ -52,7 +52,7 @@
<div class="col">
<div class="row">
<div class="col" v-if="this.$store.getters.getUserId">
<list-component section="customerVouchersListSection" :endpoint="route('api.voucher.user.list')" :options="{ 'has_active_reward': true }">
<list-component section="customerVouchersListSection" :endpoint="route('api.voucher.user.list')" :options="{ 'has_vouchers_all': true }">
<template slot="list" slot-scope="{data}">
<single-user-reward-item-component :data="data"></single-user-reward-item-component>
</template>
@@ -69,11 +69,16 @@
<div class="col">
<div class="row">
<div class="col" v-if="this.$store.getters.getUserId">
<list-component section="customerRewardsListSection" :endpoint="route('api.reward.list.details')" :options="{'is_active': true, order_by:{ column:'order', DESC:false}}">
<!-- <list-component section="customerRewardsListSection" :endpoint="route('api.reward.list.details')" :options="{'is_active': true, order_by:{ column:'order', DESC:false}}">
<template slot="list" slot-scope="{data}">
<single-reward-details-item-component :data="data"></single-reward-details-item-component>
</template>
</list-component>
</list-component> -->
<!-- <list-component section="customerRewardsListSection" :endpoint="route('api.voucher.user.list')" :options="{'has_active_reward': true}">
<template slot="list" slot-scope="{data}">
<single-user-reward-item-component :data="data"></single-user-reward-item-component>
</template>
</list-component> -->
</div>
</div>
</div>
@@ -108,5 +113,9 @@
</template>
<script>
export default {}
export default {
created(){
this.$store.dispatch('markVouchersAsSeen');
}
}
</script>
@@ -0,0 +1,42 @@
<template>
<div class="row parentContainer" style="margin-bottom: 8px;">
<div class="col">
<div class="bg-complete">
<div class="row align-items-end" style="padding: 8px 25px;">
<div class="col-auto">
<a :href="route('customer.reward', company.reference)" target="_blank">
<i class="fa fa-spinner fa-spin m-b-5 text-white" v-show="$store.getters.isLoading(section)"></i>
<div v-show="!$store.getters.isLoading(section)" class="text-primary-lighter fs-10 text-uppercase">Vouchers: <span class="text-white no-margin bold">{{ vouchersCount }}</span><i class="fa fa-external-link text-white m-l-10"></i></div>
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
company: {
type: Object,
required: true
}
},
data(){
return {
section: 'customerVouchersSummaryAdminSection',
vouchersCount: 0,
}
},
created(){
this.submit(this.route('api.voucher.user.list') + '?filters=' + JSON.stringify( { 'has_vouchers_all': this.company.employee.id, order_by:{ column:'id', DESC:true }} ), 'get', this.section, false, false);
},
methods: {
successHandler(response){
this.vouchersCount = response.payload.data.length;
},
}
}
</script>
@@ -0,0 +1,26 @@
<template>
<div class="row parentContainer" style="margin-bottom: 8px;">
<div class="col">
<div class="bg-complete">
<div class="row align-items-end" style="padding: 8px 25px;">
<div class="col-auto">
<a :href="route('rewards')" >
<div class="text-primary-lighter fs-10 text-uppercase">Vouchers: <span class="text-white no-margin bold">{{ vouchersCount }}</span></div>
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
vouchersCount() {
return this.$store.getters.getVouchersCount;
}
},
}
</script>
@@ -47,12 +47,7 @@
methods: {
submitForm() {
this.submit(this.data ? this.route('api.remark.update', this.data.id) : this.route('api.remark.create', this.id), this.data ? 'put' : 'post', this.section, true, true);
this.$emit('submit')
},
successHandler(response){
this.parameters.content = '';
this.closeModal();
}
},
mixins: [modalFormHandler]
}
+60
View File
@@ -0,0 +1,60 @@
const initialState = {
vouchersCount: localStorage.getItem('voucher-count') || 0,
latestVoucherTimestamp: localStorage.getItem('latest-voucher-timestamp') || null,
lastSeenVoucherTimestamp: localStorage.getItem('last-seen-voucher-timestamp') || null,
};
export default {
state: { ...initialState },
getters: {
getVouchersCount: state => {
return state.vouchersCount;
},
showVoucherUpdateIndicator: state => {
if (!state.lastSeenVoucherTimestamp && state.latestVoucherTimestamp && state.voucherCount > 0) {
return true;
}
if (!state.latestVoucherTimestamp) {
return false;
}
const latestTime = new Date(state.latestVoucherTimestamp).getTime();
const lastSeenTime = new Date(state.lastSeenVoucherTimestamp).getTime();
return latestTime > lastSeenTime;
}
},
mutations: {
SET_VOUCHER_LATEST_TIMESTAMP(state, timestamp) {
state.latestVoucherTimestamp = timestamp;
},
SET_VOUCHER_LAST_SEEN_TIMESTAMP(state, timestamp) {
state.lastSeenVoucherTimestamp = timestamp;
},
SET_VOUCHER_COUNT(state, count) {
state.vouchersCount = count;
},
RESET_VOUCHER_STATE(state) {
Object.assign(state, initialState);
}
},
actions: {
updateLatestVoucherTimestamp(store, timestamp){
localStorage.setItem('latest-voucher-timestamp', timestamp);
store.commit('SET_VOUCHER_LATEST_TIMESTAMP', timestamp);
},
markVouchersAsSeen(store) {
const latestVoucherTimestamp = store.state.latestVoucherTimestamp;
localStorage.setItem('last-seen-voucher-timestamp', latestVoucherTimestamp);
store.commit('SET_VOUCHER_LAST_SEEN_TIMESTAMP', latestVoucherTimestamp);
},
updateVoucherCount(store, count){
localStorage.setItem('voucher-count', count);
store.commit('SET_VOUCHER_COUNT', count);
},
clearVoucher(store, count){
localStorage.removeItem('voucher-count');
localStorage.removeItem('latest-voucher-timestamp');
localStorage.removeItem('last-seen-voucher-timestamp');
store.commit('RESET_VOUCHER_STATE');
},
}
}
+3 -1
View File
@@ -7,6 +7,7 @@ import crudRequest from './modules/crudRequest'
import crudRequestV2 from './modules/crudRequestV2'
import authentication from './modules/authentication'
import loadRequestQueue from './modules/loadRequestQueue'
import voucherUpdates from './modules/voucherUpdates'
import jobPolling from './modules/jobPolling'
Vue.use(Vuex);
@@ -20,6 +21,7 @@ export default new Vuex.Store({
crudRequest,
crudRequestV2,
authentication,
jobPolling
voucherUpdates,
jobPolling,
}
})
-4
View File
@@ -9,10 +9,6 @@
}
</style>
<body class="horizontal-menu horizontal-app-menu bg-master-lightest overflow-hidden">
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-TQKCPCD"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<div id="app" style="min-height: 100%;">
<div id="grecaptcha_container"></div>
@yield('content')
+1 -1
View File
@@ -3,7 +3,7 @@
<div class="row" v-if="$store.getters.isAdmin">
<div class="col p-t-15 p-b-15">
<billing-component></billing-component>
<admin-payments-billing-section-component></admin-payments-billing-section-component>
<admin-payments-billing-section-polling-component></admin-payments-billing-section-polling-component>
</div>
</div>
@endsection
@@ -59,7 +59,7 @@
<tr>
<td width="70%" style="text-align: right;">Transfer fee: </td>
@php
$transfer_fee = number_format((float)$transferFeeTransactions->sum('service_charge'), 2, '.', '');
$transfer_fee = number_format(((float)$transferFeeTransactions->sum('service_charge') + (float)($groupTransferFeeOriginalAmount ?? 0)), 2, '.', '');
@endphp
<td>{{$transaction->original_currency->short_code}} {{$transfer_fee}}</td>
</tr>
+4 -3
View File
@@ -66,9 +66,10 @@
<div v-if="$store.getters.isCustomer" class="col-auto p-r-20">
<a href="{{route('banks')}}"><div class="text-white all-caps fs-12">Bank Accounts</div></a>
</div>
<!-- <div v-if="$store.getters.isCustomer" class="col-auto p-r-20">
<a href="{{route('rewards')}}"><div class="text-white all-caps fs-12">Vouchers</div></a>
</div> -->
<div v-if="$store.getters.isCustomer" class="col-auto p-r-20">
<vouchers-navigation-bar-component></vouchers-navigation-bar-component>
<!-- <a href="{{route('rewards')}}"><div class="text-white all-caps fs-12">Vouchers</div></a> -->
</div>
</div>
</div>
</div>
-8
View File
@@ -1,11 +1,3 @@
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-TQKCPCD');</script>
<!-- End Google Tag Manager -->
<script>window.LARAVEL_VAPOR_ENABLED=@json(env('LARAVEL_VAPOR_ENABLED', false));</script>
<meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
<meta charset="utf-8"/>
<title>Exchange - Overseas Money Transfers</title>
+8 -4
View File
@@ -17,7 +17,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
require __DIR__ . '/account.php';
// Route::get('/rate/calculate', 'RateCalculateCurrencyController@convert')->name('calculate');
// Route::get('/rate/calculate', 'RateCalculateCurrencyController@convert')->name('calculate');
Route::group(['prefix' => 'service', 'as' => 'service.'], function () {
Route::get('countries/list', 'Services\CountriesListController@index')->name('list.countries');
@@ -78,12 +78,16 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
require __DIR__ . '/milestone.php';
require __DIR__ . '/remark.php';
require __DIR__ . '/open1688.php';
require __DIR__.'/remark.php';
// require __DIR__ . '/accounting.php'; //cief todo: To check if this is needed
require __DIR__ . '/job.php';
// require __DIR__ . '/rate.php';
// require __DIR__ . '/receipt.php';
// require __DIR__ . '/rate.php';
// require __DIR__ . '/receipt.php';
});
});
+8
View File
@@ -0,0 +1,8 @@
<?php
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'open1688', 'namespace' => 'Open1688', 'as' => 'open1688.'], function () {
Route::get('/', 'Open1688Controller@keywordSearchProduct')->name('keywordSearchProduct');
Route::get('/{offer_id}', 'Open1688Controller@getProductDetail')->name('getProductDetail');
});