Compare commits

..

125 Commits

Author SHA1 Message Date
Bayu Lukman Yusuf 0336c1862f Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-03-03 17:18:17 +07:00
Bayu Lukman Yusuf c6298206d2 menu category 2026-03-03 17:17:53 +07:00
Bayu Lukman Yusuf 1bccc8c2c0 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-03-03 17:16:01 +07:00
Bayu Lukman Yusuf e173b611e3 top menu 2026-03-03 17:14:10 +07:00
Bayu Lukman Yusuf c69e9b8407 max width container-xxl 2026-03-03 15:59:44 +07:00
Bayu Lukman Yusuf 00a3db901e brand home 2026-03-03 15:47:48 +07:00
Bayu Lukman Yusuf 3e7a62be44 special product ful width 2026-03-03 15:34:04 +07:00
Bayu Lukman Yusuf cecc894944 home category full width 2026-03-03 15:09:10 +07:00
Bayu Lukman Yusuf e12f00cf96 popular grid 6 xxl screen 2026-03-03 15:01:31 +07:00
Bayu Lukman Yusuf 06ab0f42a5 banner aspect ratio 2026-03-03 14:52:32 +07:00
Bayu Lukman Yusuf 0f6dbb95e7 item visitor log 2026-03-03 11:54:40 +07:00
Bayu Lukman Yusuf 8cd0c19876 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-03-02 16:38:49 +07:00
Bayu Lukman Yusuf 089308d86c feedback list 2026-03-02 16:38:30 +07:00
Bayu Lukman Yusuf 479eeafaaa setup menu footer 2026-03-02 14:54:55 +07:00
Bayu Lukman Yusuf c204d6d834 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-03-02 12:02:52 +07:00
Bayu Lukman Yusuf 484c2c23c5 update address map 2026-03-02 12:02:34 +07:00
Bayu Lukman Yusuf f93d237c79 fix cart price 2026-03-02 11:44:56 +07:00
Bayu Lukman Yusuf 12679dffe8 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-03-02 10:47:12 +07:00
Bayu Lukman Yusuf 3c38abfd6a searching location 2026-03-02 10:46:38 +07:00
Bayu Lukman Yusuf 06285fc533 map input 2026-03-02 10:32:10 +07:00
Bayu Lukman Yusuf 0aec36908b checkout fix 2026-03-02 09:24:10 +07:00
Bayu Lukman Yusuf 9430b4895e Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-27 16:30:28 +07:00
Bayu Lukman Yusuf f78819c9b9 search 2026-02-27 16:29:57 +07:00
Bayu Lukman Yusuf f2c2cf11ed wishlist 2026-02-27 16:19:51 +07:00
Bayu Lukman Yusuf 8ef3a2783a discount - use point 2026-02-27 11:57:16 +07:00
Bayu Lukman Yusuf 7aa57f6082 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-26 16:16:37 +07:00
Bayu Lukman Yusuf a60ca44ca1 refresh page after input new address 2026-02-26 14:20:07 +07:00
Bayu Lukman Yusuf 0dfdfb2912 show description address info 2026-02-26 13:16:45 +07:00
Husnu Setiawan b9bbdc78a2 remove fix permission (2)
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-26 09:53:47 +07:00
Husnu Setiawan 1b70438c7a remove fix permission
WMS API/ECOMMERCE/pipeline/head There was a failure building this commit Details
2026-02-26 09:51:28 +07:00
Bayu Lukman Yusuf 44ef52febc Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head There was a failure building this commit Details
2026-02-26 09:38:45 +07:00
Bayu Lukman Yusuf f24e86061e setting language 2026-02-26 09:38:22 +07:00
Bayu Lukman Yusuf f05b7378dc tagline title 2026-02-26 09:30:16 +07:00
Bayu Lukman Yusuf c78a53c710 fix permission
WMS API/ECOMMERCE/pipeline/head There was a failure building this commit Details
2026-02-26 09:09:17 +07:00
Bayu Lukman Yusuf d0b41535b8 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head There was a failure building this commit Details
2026-02-26 09:04:12 +07:00
Bayu Lukman Yusuf ca4b0f15d8 update 2026-02-26 09:04:00 +07:00
Bayu Lukman Yusuf 95d0b6cdd6 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head There was a failure building this commit Details
2026-02-26 08:47:45 +07:00
Bayu Lukman Yusuf 65367bd8dc cart checkout 2026-02-26 08:47:24 +07:00
Bayu Lukman Yusuf a24ca12c65 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head There was a failure building this commit Details
2026-02-25 10:58:42 +07:00
Bayu Lukman Yusuf aba2dcb939 update link account 2026-02-25 10:58:31 +07:00
Bayu Lukman Yusuf 8da7a23581 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-25 10:51:13 +07:00
Bayu Lukman Yusuf 459172dc73 fix navbar 2026-02-25 10:50:50 +07:00
Bayu Lukman Yusuf 0ca2f2d15a change header 2026-02-25 08:52:54 +07:00
Bayu Lukman Yusuf 7e544e2f30 hide brand if no logo 2026-02-24 16:30:55 +07:00
Bayu Lukman Yusuf c42d359db1 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-24 16:27:58 +07:00
Bayu Lukman Yusuf 2ffe527661 update brand 2026-02-24 16:27:20 +07:00
Bayu Lukman Yusuf df229af134 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-24 15:27:59 +07:00
Bayu Lukman Yusuf 08f79062e9 update home 2026-02-24 15:27:26 +07:00
Bayu Lukman Yusuf 5b54da590c remove annoucement 2026-02-24 14:06:33 +07:00
Bayu Lukman Yusuf 7fa95775f3 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-24 11:50:44 +07:00
Bayu Lukman Yusuf b3630efd2e link product 2026-02-24 11:50:14 +07:00
Bayu Lukman Yusuf 7e820e7b45 init home store/marketplace 2026-02-24 11:30:42 +07:00
Bayu Lukman Yusuf e62c7b7e47 remove clear cache
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-23 13:02:51 +07:00
Bayu Lukman Yusuf 29d93d0022 add clear cache
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-23 13:01:11 +07:00
Bayu Lukman Yusuf 3fd0938641 add clear cache
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-23 12:58:28 +07:00
Bayu Lukman Yusuf 939010063b add chmod bootstrap cache
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-23 12:56:40 +07:00
Bayu Lukman Yusuf c9e35653db add php artisan optimize:clear
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-23 12:53:15 +07:00
Bayu Lukman Yusuf e3b9dbea60 rename meta tag 2026-02-23 12:51:25 +07:00
Bayu Lukman Yusuf f114a9bfd0 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-23 11:49:59 +07:00
Bayu Lukman Yusuf ffdf041acf rename 2026-02-23 11:47:52 +07:00
Bayu Lukman Yusuf 8344a81e2d check https
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-23 10:29:11 +07:00
Bayu Lukman Yusuf 30ea286214 Merge branch 'feature-product' into development
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-23 08:59:38 +07:00
Bayu Lukman Yusuf a733b90d46 add https 2026-02-23 08:58:46 +07:00
Husnu Setiawan 2184c50a07 fix bug file Jenkinsfile (2)
WMS API/ECOMMERCE/pipeline/head This commit looks good Details
2026-02-23 02:31:14 +07:00
Husnu Setiawan 472770e367 fix bug file Jenkinsfile
WMS API/ECOMMERCE/pipeline/head There was a failure building this commit Details
2026-02-23 02:26:46 +07:00
Husnu Setiawan df52a79f6d add cd/ci dev
WMS API/ECOMMERCE/pipeline/head There was a failure building this commit Details
2026-02-23 02:21:40 +07:00
Husnu Setiawan 52f65befbe add cd/ci dev 2026-02-23 02:20:36 +07:00
Husnu Setiawan b1c6d37a5d Merge branch 'feature-product' into development 2026-02-23 02:06:59 +07:00
Bayu Lukman Yusuf 7baffc9cb5 add category, brands, gender navbar 2026-02-20 11:24:25 +07:00
Bayu Lukman Yusuf 87096be7cc contact us 2026-02-20 11:07:26 +07:00
Bayu Lukman Yusuf 7017d38f12 footer brands 2026-02-20 10:08:34 +07:00
Bayu Lukman Yusuf 91e325b248 update info, instagram feed dummy, navbar 2026-02-20 09:19:42 +07:00
Bayu Lukman Yusuf b3f3c2b115 update bootstrap build vite 2026-02-20 08:34:31 +07:00
Bayu Lukman Yusuf be7aabbcb3 fix step otp phone, email, tambah nama di produk populer home, brands home, hapus newsletter di cart (hide) 2026-02-19 16:06:55 +07:00
Bayu Lukman Yusuf d58c8d7ca2 list voucher & fix address 2026-02-18 12:57:30 +07:00
Bayu Lukman Yusuf 9ec2918fb4 product grid 2026-02-16 15:15:47 +07:00
Bayu Lukman Yusuf e23744e810 home banner 2026-02-16 11:14:42 +07:00
Bayu Lukman Yusuf 097c9f12ee fix waybill, transaction status 2026-02-12 16:18:16 +07:00
Bayu Lukman Yusuf f547c6e158 fix checkout 2026-02-11 21:09:09 +07:00
Bayu Lukman Yusuf 9288a13e07 footer cateory 2026-02-06 17:12:02 +07:00
Bayu Lukman Yusuf ce5580acd7 review component 2026-02-06 15:38:52 +07:00
Bayu Lukman Yusuf 6e546098b7 translate language home slider 2026-02-06 15:26:55 +07:00
Bayu Lukman Yusuf 835148d78c hep, faq, navbar 2026-02-05 15:40:07 +07:00
Bayu Lukman Yusuf 8f40ce9ea8 fix order detail, disable store pickup 2026-01-30 20:05:45 +07:00
Bayu Lukman Yusuf e56dca452b update order detail 2026-01-29 16:15:47 +07:00
Bayu Lukman Yusuf f1e0577bef order history 2026-01-29 11:13:50 +07:00
Bayu Lukman Yusuf fa00ee377a checkout & create payment 2026-01-29 08:26:00 +07:00
Bayu Lukman Yusuf d80b42c297 pricing shipping list 2026-01-28 11:20:20 +07:00
Bayu Lukman Yusuf d298649398 checkout page, fill address, choose shipping, calculation 2026-01-27 21:41:25 +07:00
Bayu Lukman Yusuf 9a23e75f5b state cart null 2026-01-26 11:26:57 +07:00
Bayu Lukman Yusuf 156978e669 fix cart 2026-01-26 08:44:25 +07:00
Bayu Lukman Yusuf 8c21d4dc7c cart 2026-01-21 15:20:35 +07:00
Bayu Lukman Yusuf a86ba91db7 load address using ajax 2026-01-20 16:31:17 +07:00
Bayu Lukman Yusuf 10f3abc047 form address 2026-01-20 16:12:23 +07:00
Bayu Lukman Yusuf a2caec5b74 address 2026-01-20 12:24:31 +07:00
Bayu Lukman Yusuf b0eda5e389 profile 2026-01-19 14:38:16 +07:00
Bayu Lukman Yusuf b8bc23ee1f profile page 2026-01-19 09:07:01 +07:00
Bayu Lukman Yusuf 2f293642ea fix login google without customers data 2026-01-15 14:34:25 +07:00
Bayu Lukman Yusuf f8af8541f6 google auth login 2026-01-15 11:32:30 +07:00
Bayu Lukman Yusuf 7c322017da otp email 2026-01-15 09:00:14 +07:00
Bayu Lukman Yusuf d21b26a5d2 language login 2026-01-13 14:56:47 +07:00
Bayu Lukman Yusuf 9c8ebdfabe otp wa 2026-01-13 13:45:34 +07:00
Bayu Lukman Yusuf b8c34bf00a ui login email / phone 2026-01-13 02:20:37 +07:00
Bayu Lukman Yusuf faf6e4df07 auth register 2026-01-13 01:45:32 +07:00
Bayu Lukman Yusuf 36a763d6c0 top header annoucement 2026-01-09 13:48:19 +07:00
Bayu Lukman Yusuf 3a66f08579 brands category 2026-01-09 13:33:47 +07:00
Bayu Lukman Yusuf 3b91c813da popular product, brand 2026-01-09 10:20:44 +07:00
Bayu Lukman Yusuf 6117a3580a setup PWA 2026-01-09 08:50:04 +07:00
Bayu Lukman Yusuf c7eba16c5f search 2026-01-08 14:06:58 +07:00
Bayu Lukman Yusuf c71b7ff5c2 filter with ajax 2026-01-08 12:54:55 +07:00
Bayu Lukman Yusuf 80f6dc8612 load product ajax 2026-01-07 14:01:43 +07:00
Bayu Lukman Yusuf 8548016200 filter query 2026-01-07 11:58:22 +07:00
Bayu Lukman Yusuf 4a32c25337 fix filter 2026-01-06 16:15:55 +07:00
Bayu Lukman Yusuf e0e853e2b8 list product, show list filter category, gender 2026-01-06 14:12:36 +07:00
Bayu Lukman Yusuf 64892821d9 navbar menu category & gender 2026-01-06 11:32:08 +07:00
Bayu Lukman Yusuf ddcea440af component viewed products, navbar description, delivery retur 2026-01-05 16:18:06 +07:00
Bayu Lukman Yusuf 695494a2d9 price variant 2026-01-05 10:52:01 +07:00
Bayu Lukman Yusuf 7173d2c117 change price, image if variant click and show description 2026-01-02 15:01:00 +07:00
Bayu Lukman Yusuf c8c96556c0 product description + variant 2026-01-02 11:07:44 +07:00
bayu 7b38c511ce init language 2025-12-31 13:49:49 +07:00
bayu 2ab0549f02 add new arrivals 2025-12-31 13:14:30 +07:00
bayu a3675b87c9 location & language 2025-12-31 11:04:13 +07:00
bayu 496f7a9179 setup language switch 2025-12-31 09:55:38 +07:00
bayu b00378ba8f product grid 2025-12-30 16:08:16 +07:00
bayu c384576119 init brand 2025-12-30 11:18:57 +07:00
359 changed files with 29492 additions and 15139 deletions

View File

@ -1,8 +1,9 @@
APP_NAME=Laravel
APP_NAME=AsiaGolf
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_TAGLINE=Tagline
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
@ -20,14 +21,14 @@ LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=asiagolf
DB_USERNAME=postgres
DB_PASSWORD=12345678
SESSION_DRIVER=database
SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
@ -37,7 +38,7 @@ BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
CACHE_STORE=file
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
@ -63,3 +64,19 @@ AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
WMS_ASSET_URL="https://dev.smgdev.top/api/storage"
BITESHIP_URL=
BITESHIP_KEY=
BITESHIP_COURIER=grab,gojek,tiki,jnt,anteraja
GOOGLE_CLIENT_ID=1023147148625-3igh5253tr1lkflmbfj76iopvp2n52od.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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
GOOGLE_REDIRECT_URI=http://localhost:8000/login/google/callback
XENDIT_PRIVATE_KEY=

1
.php-version Normal file
View File

@ -0,0 +1 @@
8.4

79
Envoy.blade.php Normal file
View File

@ -0,0 +1,79 @@
@servers(['prod' => 'ubuntu@172.26.12.217', 'dev' => 'ubuntu@smgdev.top'])
@setup
$repository = 'git@172.26.1.255:SMG_DEV/ECOMMERCE.git';
$folder = isset($folder) ? $folder : 'store';
$releases_dir = '/var/www/'.$folder.'/releases';
$app_dir = '/var/www/'.$folder;
$release = date('YmdHis');
$branch = isset($branch) ? $branch : 'production';
$new_release_dir = $releases_dir .'/'. $release;
$php_bin = 'php8.2';
$composer_bin = 'php8.2 /usr/local/bin/composer'
@endsetup
@task('clone_repository')
echo 'Cloning repository'
[ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
git clone --depth 1 --single-branch --branch {{ $branch }} {{ $repository }} {{ $new_release_dir }}
cd {{ $new_release_dir }}
@endtask
@task('run_composer')
echo "Starting deployment ({{ $release }})"
cd {{ $new_release_dir }}
{{ $composer_bin}} install --prefer-dist --no-scripts -q -o
npm install --legacy-peer-deps
npm run build
@endtask
@task('update_symlinks')
echo "Linking storage directory"
rm -rf {{ $new_release_dir }}/storage
ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage
echo 'Linking .env file'
ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env
echo 'Linking current release'
ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current
@endtask
@task('setup_laravel')
cd {{ $app_dir }}/current
{{ $php_bin}} artisan storage:link
@endtask
@task('clean_old_releases')
# This lists our releases by modification time and delete all but the 3 most recent.
purging=$(ls -dt {{ $releases_dir }}/* | tail -n +5);
if [ "{{ $releases_dir }}" != "" ]; then
if [ "$purging" != "" ]; then
echo Purging old releases: $purging;
rm -rf $purging;
else
echo "No releases found for purging at this time";
fi
fi
@endtask
@story('deploy',["on" => "prod"])
clone_repository
run_composer
update_symlinks
setup_laravel
clean_old_releases
@endstory
@story('deploy-dev',["on" => "dev"])
clone_repository
run_composer
update_symlinks
setup_laravel
clean_old_releases
@endstory

53
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,53 @@
pipeline {
agent any
stages
{
stage("Deploy Dev"){
when {
branch 'development'
}
steps {
sshagent(credentials: ['dev-id_rsa']) {
sh "php8.2 /usr/local/bin/composer install"
sh "php8.2 ./vendor/bin/envoy run deploy-dev --branch=development"
}
}
}
stage("Deploy Stage"){
when {
branch 'stage'
}
steps {
sshagent(credentials: ['dev-id_rsa']) {
sh "php8.2 /usr/local/bin/composer install"
sh "php8.2 ./vendor/bin/envoy run deploy-dev --branch=stage"
}
}
}
stage("Deploy Prod"){
when {
branch 'production'
}
steps {
sshagent(credentials: ['dev-id_rsa']) {
sh "php8.2 /usr/local/bin/composer install"
sh "php8.2 ./vendor/bin/envoy run deploy --branch=production"
}
}
}
stage("Deploy Mirror"){
when {
branch 'production-mirror'
}
steps {
sshagent(credentials: ['dev-id_rsa']) {
sh "php8.2 /usr/local/bin/composer install"
sh "php8.2 ./vendor/bin/envoy run deploy --branch=production-mirror --folder=pos-api-mirror"
}
}
}
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Helpers;
use Illuminate\Support\Facades\DB;
class AutoNumbering
{
function __construct($params){
$this->type = @$params["type"];
$this->prefix = @$params["prefix"];
$this->location_id = (int) @$params["location_id"];
$this->pad = @$params["pad"] ?? 12;
}
function getCurrent(){
$numbering = (array) @DB::select("SELECT id, transaction, location_id, prefix, pad, current
FROM numbering
WHERE transaction = ? AND location_id = ?
FOR UPDATE
", [$this->type, $this->location_id])[0];
if ($numbering == null) {
$numbering = DB::table("numbering")->insert([
"transaction" => $this->type,
"location_id" => $this->location_id,
"prefix" => $this->prefix,
"pad" => $this->pad,
"current" => 0
]);
$numbering = (array) DB::select("SELECT id, transaction, location_id, prefix, pad, current
FROM numbering
WHERE id = ?
FOR UPDATE
", [DB::getPdo()->lastInsertId()])[0];
}
$prefix_number = $numbering["prefix"];
$next_number = $numbering["current"] + 1;
$pad_number = $numbering["pad"] - strlen($prefix_number);
$number = $prefix_number . str_pad($next_number, $pad_number, 0, STR_PAD_LEFT);
$this->id = $numbering["id"];
DB::statement("UPDATE numbering SET current = current+1 WHERE id = ?", [$this->id]);
return $number;
}
}

View File

@ -0,0 +1,218 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\Province;
use App\Models\District;
use App\Models\Subdistrict;
use App\Models\City;
use Illuminate\Http\Request;
class AddressController extends Controller
{
public function index(Request $request)
{
$addresses = auth()->user()->addresses()->orderBy('is_primary', 'desc')->get();
// If AJAX request, return JSON
if ($request->ajax() || $request->wantsJson()) {
$addressesData = $addresses->map(function ($address) {
return [
'id' => $address->id,
'label' => $address->label,
'name' => $address->name,
'location' => $address->location,
'address' => $address->address,
'is_primary' => $address->is_primary,
'province_id' => $address->province_id,
'city_id' => $address->city_id,
'district_id' => $address->district_id,
'subdistrict_id' => $address->subdistrict_id,
'postal_code' => $address->postal_code,
'latitude' => $address->latitude,
'longitude' => $address->longitude,
'phone' => $address->phone,
];
});
return response()->json([
'success' => true,
'addresses' => $addressesData
]);
}
// For regular page load, return view
return view('account.addresses', compact('addresses'));
}
public function provinces()
{
$provinces = Province::orderBy('name')->get();
return [
'data' => $provinces,
];
}
public function cities($provinceId)
{
$cities = City::where('province_id', $provinceId)->orderBy('name')->get();
return [
'data' => $cities,
];
}
public function districts($city_id)
{
$districts = District::where('city_id', $city_id)->orderBy('name')->get();
return [
'data' => $districts,
];
}
public function villages($districtId)
{
$villages = Subdistrict::where('district_id', $districtId)->orderBy('name')->get();
return [
'data' => $villages,
];
}
public function update(Request $request, $id)
{
$request->validate([
'label' => 'required|string|max:255',
'name' => 'required|string|max:255',
'phone' => 'required|string|max:255',
'province_id' => 'required|exists:provinces,id',
'city_id' => 'required|exists:cities,id',
'district_id' => 'required|exists:districts,id',
'subdistrict_id' => 'required|exists:subdistricts,id',
'postal_code' => 'required|string|max:10',
'address' => 'required|string|max:255',
'latitude' => 'required|numeric|between:-90,90',
'longitude' => 'required|numeric|between:-180,180',
'is_primary' => 'boolean'
]);
$address = auth()->user()->addresses()->findOrFail($id);
$address->update([
'label' => $request->label,
'name' => $request->name,
'phone' => $request->phone,
'province_id' => $request->province_id,
'city_id' => $request->city_id,
'district_id' => $request->district_id,
'subdistrict_id' => $request->subdistrict_id,
'postal_code' => $request->postal_code,
'address' => $request->address,
'latitude' => $request->latitude,
'longitude' => $request->longitude,
'is_primary' => $request->is_primary ?? $address->is_primary,
]);
// Update location names based on selected IDs
$province = Province::find($request->province_id);
$city = City::find($request->city_id);
$district = District::find($request->district_id);
$subdistrict = Subdistrict::find($request->subdistrict_id);
$address->update([
'province_name' => $province?->name,
'regency_name' => $city?->name,
'district_name' => $district?->name,
'village_name' => $subdistrict?->name,
]);
// If set as primary, unset other primary addresses
if ($request->boolean('is_primary')) {
auth()->user()->addresses()
->where('id', '!=', $address->id)
->update(['is_primary' => false]);
}
return response()->json([
'success' => true,
'message' => __('addresses.address_updated_successfully'),
'address' => $address->fresh()
]);
}
public function store(Request $request)
{
$request->validate([
'label' => 'required|string|max:255',
'name' => 'required|string|max:255',
'phone' => 'required|string|max:255',
'province_id' => 'required|exists:provinces,id',
'city_id' => 'required|exists:cities,id',
'district_id' => 'required|exists:districts,id',
'subdistrict_id' => 'required|exists:subdistricts,id',
'address' => 'required|string|max:255',
'postal_code' => 'required|string|max:10',
'latitude' => 'nullable|numeric|between:-90,90',
'longitude' => 'nullable|numeric|between:-180,180',
'is_primary' => 'boolean'
]);
// Get location names
$province = Province::find($request->province_id);
$city = City::find($request->city_id);
$district = District::find($request->district_id);
$subdistrict = Subdistrict::find($request->subdistrict_id);
$address = auth()->user()->addresses()->create([
'label' => $request->label,
'name' => $request->name,
'phone' => $request->phone,
'province_id' => $request->province_id,
'city_id' => $request->city_id,
'district_id' => $request->district_id,
'subdistrict_id' => $request->subdistrict_id,
'address' => $request->address,
'postal_code' => $request->postal_code,
'latitude' => $request->latitude,
'longitude' => $request->longitude,
'is_primary' => $request->is_primary ?? false,
]);
// If set as primary, unset other primary addresses
if ($request->boolean('is_primary')) {
auth()->user()->addresses()
->where('id', '!=', $address->id)
->update(['is_primary' => false]);
}
return response()->json([
'success' => true,
'message' => __('addresses.address_created_successfully'),
'address' => $address
]);
}
public function destroy($id)
{
$address = auth()->user()->addresses()->findOrFail($id);
// Don't allow deletion if it's the only address
if (auth()->user()->addresses()->count() === 1) {
return response()->json([
'success' => false,
'message' => __('addresses.cannot_delete_only_address')
], 400);
}
$address->delete();
return response()->json([
'success' => true,
'message' => __('addresses.address_deleted_successfully')
]);
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Customer;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
use Laravel\Socialite\Facades\Socialite;
use App\Repositories\Member\Auth\MemberAuthRepository;
class GoogleController extends Controller
{
protected $memberAuthRepository;
public function __construct(MemberAuthRepository $memberAuthRepository)
{
$this->memberAuthRepository = $memberAuthRepository;
}
/**
* Redirect the user to the Google authentication page.
*
* @return \Illuminate\Http\RedirectResponse
*/
public function redirectToGoogle()
{
return Socialite::driver('google')->redirect();
}
/**
* Obtain the user information from Google.
*
* @return \Illuminate\Http\RedirectResponse
*/
public function handleGoogleCallback()
{
try{
$googleUser = Socialite::driver('google')->stateless()->user();
// Log::info($googleUser);
$email = $googleUser->email;
$name = $googleUser->name;
$google_id = $googleUser->id;
$avatar = $googleUser->avatar;
// $user = User::updateOrCreate(
// ['email' => $googleUser->email],
// [
// 'name' => $googleUser->name,
// 'google_id' => $googleUser->id,
// 'avatar' => $googleUser->avatar,
// ]
// );
$user = $this->memberAuthRepository->check(['email' => $email]);
if (!$user) {
// auto register
$user = $this->memberAuthRepository->loginGoogle($name, $email, $avatar);
}
Auth::login($user, true);
return redirect()->route('home')->with('info', __('signin.google_coming_soon'));
} catch (\Exception $e) {
Log::error('Google callback failed: '.$e->getMessage());
Log::info($e);
return redirect()->route('login')->with('error', __('signin.google_failed'));
}
}
}

View File

@ -0,0 +1,142 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Repositories\Member\Auth\MemberAuthRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class LoginEmailController extends Controller
{
protected $memberAuthRepository;
public function __construct(MemberAuthRepository $memberAuthRepository)
{
$this->memberAuthRepository = $memberAuthRepository;
}
public function index()
{
return view('account.signin',[
'type' => 'email',
]);
}
public function otp(Request $request)
{
$validator = Validator::make($request->all(), [
'identity' => 'required|email',
]);
$email = $request->identity;
try {
// Find user by email
$user = $this->memberAuthRepository->check(['email' => $email]);
if (!$user) {
return response()->json([
'success' => false,
'message' => __('signin.user_not_found'),
], 404);
}
try {
// Use MemberAuthRepository to generate OTP
$otp = $this->memberAuthRepository->emailOtp(['email' => $email]);
// TODO: Integrate with WhatsApp API to send OTP
// For now, we'll just log it (remove in production)
Log::info("OTP for {$email}: {$otp->otp}");
return response()->json([
'success' => true,
'message' => __('otp.sent'),
'redirect' => route('login-email.otp.view', ['identity' => $email]),
]);
} catch (\Exception $e) {
Log::error('OTP generation failed: '.$e->getMessage());
return response()->json([
'success' => false,
'message' => __('otp.generate_failed'),
], 500);
}
} catch (\Exception $e) {
Log::error('Email login failed: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => __('signin.login_failed')
], 500);
}
}
public function otpView($identity)
{
return view('account.otp', [
'identity' => $identity,
'type' => 'email'
]);
}
public function verify(Request $request)
{
$validator = Validator::make($request->all(), [
'identity' => 'required|email',
'otp' => 'required|string|size:6',
]);
if ($validator->fails()) {
return back()
->withErrors($validator)
->withInput();
}
$identity = $request->identity;
$otp = $request->otp;
try {
// Use MemberAuthRepository to verify OTP for email
$result = $this->memberAuthRepository->emailOtpConfirm([
'email' => $identity,
'otp' => $otp,
]);
$check = $this->memberAuthRepository->check(['email' => $identity]);
$loginData = [
'fcm_token' => null,
'device' => 'web',
];
$this->memberAuthRepository->getAuth([
'user_id' => $check->id,
'device' => 'web',
]);
Auth::login($check, true);
return redirect()->route('home')->with('success', __('otp.login_success'));
} catch (\Illuminate\Validation\ValidationException $e) {
return back()
->withErrors(['otp' => $e->getMessage()])
->withInput();
} catch (\Exception $e) {
Log::error('Email OTP verification failed: '.$e->getMessage());
return back()
->withErrors(['otp' => __('otp.verification_failed')])
->withInput();
}
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Repositories\Member\Auth\MemberAuthRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
class LoginWaController extends Controller
{
protected $memberAuthRepository;
public function __construct(MemberAuthRepository $memberAuthRepository)
{
$this->memberAuthRepository = $memberAuthRepository;
}
public function index()
{
return view('account.signin', [
'type' => 'phone',
]);
}
public function otp(Request $request)
{
$validator = Validator::make($request->all(), [
'identity' => 'required|string|min:10|max:15',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => __('otp.invalid_phone'),
'errors' => $validator->errors(),
], 422);
}
$identity = $request->identity;
// Find or create user by phone number
$user = $this->memberAuthRepository->check(['phone' => $identity]);
if (! $user) {
return response()->json([
'success' => false,
'message' => __('otp.user_not_found'),
], 404);
}
try {
// Use MemberAuthRepository to generate OTP
$otp = $this->memberAuthRepository->waOtp(['phone' => $identity]);
// TODO: Integrate with WhatsApp API to send OTP
// For now, we'll just log it (remove in production)
Log::info("OTP for {$identity}: {$otp->otp}");
return response()->json([
'success' => true,
'message' => __('otp.sent'),
'redirect' => route('login-phone.otp.view', ['identity' => $identity]),
]);
} catch (\Exception $e) {
Log::error('OTP generation failed: '.$e->getMessage());
return response()->json([
'success' => false,
'message' => __('otp.generate_failed'),
], 500);
}
}
public function otpView($identity)
{
return view('account.otp', [
'identity' => $identity,
'type' => 'phone',
]);
}
public function verify(Request $request)
{
$validator = Validator::make($request->all(), [
'identity' => 'required|string|min:10|max:15',
'otp' => 'required|string|size:6',
]);
if ($validator->fails()) {
return back()
->withErrors($validator)
->withInput();
}
$identity = $request->identity;
$otp = $request->otp;
try {
// Use MemberAuthRepository to verify OTP
$result = $this->memberAuthRepository->waOtpConfirm([
'phone' => $identity,
'otp' => $otp,
]);
$check = $this->memberAuthRepository->check(['phone' => $identity]);
$loginData = [
'fcm_token' => null,
'device' => 'web',
];
$this->memberAuthRepository->getAuth([
'user_id' => $check->id,
'device' => 'web',
]);
Auth::login($check, true);
return redirect()->route('home')->with('success', __('otp.login_success'));
} catch (\Illuminate\Validation\ValidationException $e) {
return back()
->withErrors(['otp' => $e->getMessage()])
->withInput();
} catch (\Exception $e) {
Log::error('OTP verification failed: '.$e->getMessage());
return back()
->withErrors(['otp' => __('otp.verification_failed')])
->withInput();
}
}
}

View File

@ -0,0 +1,133 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Helpers\AutoNumbering;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Drivers\Imagick\Driver;
use Intervention\Image\ImageManager;
class ProfileController extends Controller
{
public function index(Request $request)
{
if (! auth()->check()) {
return redirect()->route('login');
}
return view('account.info');
}
public function update(Request $request)
{
try {
$request->validate([
'name' => 'required|string|max:255',
'birth_date' => 'nullable|date',
'email' => 'required|email|max:255',
'phone' => 'required|string|max:255',
'photo' => 'required|image|mimes:jpg,jpeg,png,webp|max:2048',
]);
$user = auth()->user();
$user->name = $request->name;
$user->email = $request->email;
$user->phone = $request->phone;
// Handle avatar upload
if ($request->hasFile('photo')) {
$ext = $request->file('photo')->extension();
$filename = $request->file('photo')->storeAs("profile", $user->id.".".$ext, "public");
$user->photo = asset('storage/' . $filename);
}
$user->save();
$customer = $user->customer;
if ($user->customer == null) {
$customer = new Customer;
$autoNumbering = new AutoNumbering([
'type' => 'CUST',
'prefix' => 'CAPP',
'location_id' => 0,
'pad' => 9,
]);
do {
$number = $autoNumbering->getCurrent();
$count = Customer::where('number', $number)->count();
} while ($count > 0);
$customer->number = $number;
$customer->user_id = $user->id;
}
if ($request->name){
$customer->name = $request->name;
}
if ($request->email) {
$customer->email = $request->email;
}
if ($request->phone) {
$customer->phone = $request->phone;
}
if ($request->birth_date != null) {
$customer->date_of_birth = $request->birth_date;
}
$customer->save();
return back()->with('success', 'Profile updated successfully!');
} catch (Exception $e) {
Log::error($e);
return back()->with('error', $e->getMessage());
}
}
public function updatePassword(Request $request)
{
try {
$request->validate([
'current_password' => 'required|string',
'password' => 'required|string|min:8|confirmed',
]);
$user = auth()->user();
// Verify current password
if (!Hash::check($request->current_password, $user->password)) {
return back()->with('error', 'Current password is incorrect.');
}
$user->password = bcrypt($request->password);
$user->save();
return back()->with('success', 'Password updated successfully!');
} catch (Exception $e) {
return back()->with('error', $e->getMessage());
}
}
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('login');
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Repositories\Member\Auth\MemberAuthRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class RegisterController extends Controller
{
public function index()
{
return view('account.signup');
}
public function register(Request $request, MemberAuthRepository $memberAuthRepository)
{
$validated = $request->validate([
'name' => 'required|string',
'referral' => 'nullable|string',
'phone' => 'string',
'email' => 'nullable|email',
'gender' => 'nullable|in:LAKI-LAKI,PEREMPUAN',
'date_of_birth' => 'nullable|date'
]);
try {
$customer = $memberAuthRepository->register($validated);
$check = $request->all();
$check["user_id"] = $customer->user_id;
$auth = $memberAuthRepository->getAuth($check);
return redirect('/')->with('success', 'Registration successful!');
} catch (\Exception $e) {
Log::info($e);
return redirect()->back()->with('error', $e->getMessage());
}
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\Member\Cart\MemberCartRequest;
use App\Http\Requests\Member\Cart\UpdateMemberCartRequest;
use App\Models\Cart;
use App\Repositories\Member\Cart\MemberCartRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class CartController extends Controller
{
public function count(Request $request, MemberCartRepository $repository)
{
$count = $repository->getCount($request->input('location_id'));
return response()->json([
'count' => $count,
]);
}
public function index(Request $request, MemberCartRepository $repository)
{
$request->merge(['location_id' => $request->input('location_id', session('location_id', 22))]);
$carts = $repository->getList($request);
// Log::info($items);
return view('checkout.v1-cart',[
'carts' => $carts
]);
}
public function add(MemberCartRequest $request, MemberCartRepository $repository)
{
// Log::info($request->all());
$data = $request->validated();
$item = $repository->create($data);
if ($request->expectsJson()) {
return response()->json([
'success' => true,
'message' => 'Item added to cart successfully',
'item' => $item
]);
}
return redirect()->route('cart.index');
}
public function update($cart_id, UpdateMemberCartRequest $request, MemberCartRepository $repository)
{
$data = $request->validated();
$item = $repository->update($cart_id, $data);
if ($request->expectsJson()) {
return response()->json([
'success' => true,
'message' => 'Cart updated successfully',
'item' => $item
]);
}
return redirect()->route('cart.index');
}
public function delete($cart_id, MemberCartRepository $repository)
{
$repository->delete($cart_id);
if (request()->expectsJson()) {
return response()->json([
'success' => true,
'message' => 'Item removed from cart successfully'
]);
}
return redirect()->route('cart.index');
}
public function clear(MemberCartRepository $repository)
{
$repository->clearAll();
if (request()->expectsJson()) {
return response()->json([
'success' => true,
'message' => 'Cart cleared successfully'
]);
}
return redirect()->route('cart.index');
}
}

View File

@ -0,0 +1,319 @@
<?php
namespace App\Http\Controllers;
use App\Models\Address;
use App\Models\Location;
use App\Repositories\Member\Cart\MemberCartRepository;
use App\Repositories\Member\ShippingRepository;
use App\Repositories\Member\Transaction\TransactionRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class CheckoutController extends Controller
{
public function index(Request $request, MemberCartRepository $memberCartRepository)
{
$request->merge(['location_id' => $request->input('location_id', session('location_id', 22))]);
$store = Location::find($request->input('location_id'));
$subtotal = $memberCartRepository->getSubtotal($request->input('location_id'));
$address_list = Address::where('user_id', auth()->user()->id)->orderBy('is_primary', 'desc')->get();
$total = $subtotal;
$carts = $memberCartRepository->getList($request);
return view('checkout.v1-delivery-1', [
'carts' => $carts,
'subtotal' => $subtotal,
'total' => $total,
'store' => $store,
'address_list' => $address_list,
]);
}
public function indexProcess(Request $request)
{
$delivery_method = $request->input('delivery_method') ?? 'shipping';
$address_id = $request->input('address_id');
if ($address_id == null) {
$address_list = Address::where('user_id', $request->user()->id)->orderBy('is_primary', 'desc')->get();
$address_id = $address_list->first()->id;
}
if ($delivery_method == null || $address_id == null) {
return redirect()->back()->with('error', 'Delivery method or address is required');
}
if ($delivery_method == 'shipping') {
session(['checkout_delivery_method' => $delivery_method]);
session(['checkout_address_id' => $address_id]);
return redirect()->route('checkout.shipping');
}
if ($delivery_method == 'pickup') {
session(['checkout_delivery_method' => $delivery_method]);
session(['checkout_address_id' => null]);
return redirect()->route('checkout.payment');
}
return redirect()->back()->with('error', 'Delivery method is not valid');
}
public function chooseShipping(Request $request, MemberCartRepository $memberCartRepository, ShippingRepository $shippingRepository)
{
try {
$delivery_method = session('checkout_delivery_method');
$address_id = session('checkout_address_id');
$location_id = session('location_id', 22);
if ($delivery_method == null || $address_id == null || $location_id == null) {
return redirect()->route('checkout.delivery');
}
$subtotal = $memberCartRepository->getSubtotal($location_id);
$total = $subtotal;
$request->merge(['location_id' => $location_id]);
$carts = $memberCartRepository->getList($request);
try {
$shipping_list = collect($shippingRepository->getList([
'location_id' => $location_id,
'address_id' => $address_id,
'items' => $carts,
])['pricing'] ?? [])->map(function ($row) {
return [
'courier' => $row['courier_code'],
'service' => $row['courier_service_code'],
'title' => $row['courier_name'].' - '.$row['courier_service_name'],
'description' => $row['duration'],
'cost' => $row['shipping_fee'],
];
});
if (count($shipping_list) == 0) {
throw ValidationException::withMessages([
'message' => 'Tidak dapat menghitung ongkir',
]);
}
} catch (\Exception $e) {
$message = $e->getMessage();
// if contain Failed due to invalid or missing postal code
if (str_contains($message, 'Failed due to invalid or missing postal code')) {
$message = 'Kode pos tidak valid atau tidak ditemukan';
} else if (str_contains($message, 'support@biteship.com')) {
$message = 'Layanan pengiriman tidak tersedia untuk alamat ini';
}
throw ValidationException::withMessages([
'message' => $message,
]);
}
return view('checkout.v1-delivery-1-shipping', [
'carts' => $request->user()->carts,
'subtotal' => $subtotal,
'total' => $total,
'delivery_method' => $delivery_method,
'address_id' => $address_id,
'address' => Address::find($address_id),
'shipping_list' => $shipping_list,
]);
} catch (\Exception $e) {
Log::info($e);
return redirect()->route('checkout.delivery')->with('error', $e->getMessage() ?? 'Invalid checkout data');
}
}
public function chooseShippingProcess(Request $request)
{
$shipping_option = $request->input('shipping_option');
// Parse shipping option (format: courier|service|cost)
$shipping_data = explode('|', $shipping_option);
$courier = $shipping_data[0] ?? '';
$service = $shipping_data[1] ?? '';
$cost = $shipping_data[2] ?? 0;
session(['checkout_courier' => $courier]);
session(['checkout_service' => $service]);
// session(['checkout_shipping_cost' => $cost]);
return redirect()->route('checkout.payment');
}
public function choosePayment(Request $request, TransactionRepository $repository, MemberCartRepository $memberCartRepository)
{
try {
$address_id = session('checkout_address_id');
$courier = session('checkout_courier');
$service = session('checkout_service');
$location_id = session('location_id', 22);
$use_point = session('use_point') ?? 0;
$items = [];
$request->merge(['location_id' => $location_id]);
$carts = $memberCartRepository->getList($request);
if (count($carts) == 0) {
return redirect()->route('checkout.delivery')->with('error', 'No items in cart');
}
foreach ($carts as $cart) {
$items[] = [
'item_reference_id' => $cart->item_reference_id,
'qty' => $cart->qty,
];
}
$data = [
'address_id' => $address_id,
'note' => '',
'courier_company' => $courier,
'courier_type' => $service,
'location_id' => $location_id,
'items' => $items,
'vouchers' => [],
'use_customer_points' => $use_point ?? 0,
];
dd($data);
$item = $repository->create($data);
$notification = new \App\Notifications\Member\Transaction\OrderWaitPayment($item);
$user = auth()->user();
$user->notify($notification->delay(now()->addMinutes(1)));
// return new CheckoutResource($item);
// proses payment
$payment = $item->payments()->where('method_type', 'App\Models\XenditLink')
->where('status', 'PENDING')
->first();
$invoice_url = $payment ? @$payment->method->invoice_url : '';
// reset state session
session()->forget(['checkout_delivery_method', 'checkout_address_id', 'checkout_courier', 'checkout_service', 'use_point']);
return redirect()->to($invoice_url)->withHeaders([
'Cache-Control' => 'no-cache, no-store, must-revalidate',
'Pragma' => 'no-cache',
'Expires' => '0',
]);
} catch (\Exception $e) {
Log::info($e);
return redirect()->route('checkout.delivery')->with('error', 'Invalid checkout data');
}
}
/**
* Apply points to session
*/
public function applyPoint(Request $request)
{
try {
$usePoint = $request->input('use_point');
// Validate input
if (!$usePoint || !is_numeric($usePoint) || $usePoint < 0) {
return response()->json([
'success' => false,
'message' => 'Invalid points value'
]);
}
$usePoint = (int) $usePoint;
// Get user's available points
$userPoints = auth()->user()->customer->point ?? 0;
// Check if user has enough points
if ($usePoint > $userPoints) {
return response()->json([
'success' => false,
'message' => 'You cannot use more points than available'
]);
}
// Set points in session
session(['use_point' => $usePoint]);
return response()->json([
'success' => true,
'message' => 'Points applied successfully',
'points_applied' => $usePoint,
'remaining_points' => $userPoints - $usePoint
]);
} catch (\Exception $e) {
Log::error('Error applying points: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while applying points'
]);
}
}
/**
* Remove points from session
*/
public function removePoint(Request $request)
{
try {
// Check if points are currently applied
$currentPoints = session('use_point', 0);
if ($currentPoints <= 0) {
return response()->json([
'success' => false,
'message' => 'No points are currently applied'
]);
}
// Remove points from session
session()->forget('use_point');
// Get user's available points
$userPoints = auth()->user()->customer->point ?? 0;
return response()->json([
'success' => true,
'message' => 'Points removed successfully',
'points_removed' => $currentPoints,
'available_points' => $userPoints
]);
} catch (\Exception $e) {
Log::error('Error removing points: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while removing points'
]);
}
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ComponentController extends Controller
{
public function load(Request $request, $component)
{
$template = $request->get('template');
// Map component names to their blade views
$componentMap = [
'home-popular-products' => 'components.home.home-popular-products',
'product-highlight' => 'components.home.product-highlight',
'new-arrivals' => 'components.home.new-arrivals',
'brand-home' => 'components.home.brand-home-' . ($template ?? 'fashion-v1'),
];
$view = $componentMap[$component] ?? null;
if (!$view) {
return response()->json(['error' => 'Component not found'], 404);
}
// For brand-home component, we need to pass the template parameter
if ($component === 'brand-home') {
return view($view, ['template' => $template]);
}
return view($view);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ContactController extends Controller
{
public function index(Request $request)
{
return view('contact.v1');
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class HelpController extends Controller
{
//
public function index()
{
return view('help.topics-v2');
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class HomeController extends Controller
{
public function fashion(Request $request)
{
return view('home.fashion-v1');
}
public function store(Request $request)
{
return view('home.grocery');
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Session;
class LocaleController extends Controller
{
/**
* Handle language switching request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function switch(Request $request): JsonResponse
{
$locale = $request->input('locale');
// Validate locale
if (in_array($locale, ['en', 'id'])) {
App::setLocale($locale);
Session::put('locale', $locale);
}
return response()->json(['success' => true]);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class LocationController extends Controller
{
/**
* Handle location selection request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function select(Request $request): JsonResponse
{
$locationId = $request->input('location_id');
// Store location ID in session
session(['location_id' => $locationId]);
return response()->json(['success' => true]);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers;
use App\Models\Transaction;
use Illuminate\Http\Request;
class OrderController extends Controller
{
public function index(Request $request)
{
$orders = Transaction::where('user_id', auth()->user()->id)
->orderBy('id','desc')
->paginate();
return view('account.orders', compact('orders'));
}
}

View File

@ -0,0 +1,554 @@
<?php
namespace App\Http\Controllers;
use App\Models\Gender;
use App\Models\StoreCategory;
use App\Repositories\Catalog\CategoryRepository;
use App\Repositories\Catalog\GenderRepository;
use App\Repositories\AnalyticsProductVisitRepository;
use App\Repositories\Catalog\ProductRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class ProductController extends Controller
{
public function genders(Request $request)
{
$genderRepository = new GenderRepository;
$genders = $genderRepository->getList([]);
// Render gender links HTML
$genderHtml = '';
$currentGenderId = $request->input('current_gender');
foreach ($genders as $gender) {
$isActive = $currentGenderId == $gender->id;
$genderHtml .= '<li class="nav-item mb-1">';
$genderHtml .= '<a class="nav-link d-block fw-normal p-0 '.($isActive ? 'active text-primary' : '').'" ';
$genderHtml .= 'href="#" data-gender-id="'.$gender->id.'">';
$genderHtml .= $gender->name;
$genderHtml .= '</a></li>';
}
return response()->json([
'success' => true,
'genders' => $genderHtml,
]);
}
public function categories(Request $request)
{
$categoryRepository = new CategoryRepository;
$categories = $categoryRepository->getList([]);
// Render category links HTML
$categoryHtml = '';
$currentCategoryId = $request->input('current_category');
foreach ($categories as $category) {
$isActive = $currentCategoryId == $category->id;
$categoryHtml .= '<li class="nav-item mb-1">';
$categoryHtml .= '<a class="nav-link d-block fw-normal p-0 '.($isActive ? 'active text-primary' : '').'" ';
$categoryHtml .= 'href="#" data-category-id="'.$category->id.'">';
$categoryHtml .= $category->name;
$categoryHtml .= '</a></li>';
}
return response()->json([
'success' => true,
'categories' => $categoryHtml,
]);
}
public function ajax(Request $request)
{
$limit = 20;
$page = $request->page ?? 1;
$search = $request->search;
$filter = $request->filter ?? [];
$sortBy = $request->sort_by ?? 'relevance';
$price_range_start = $request->price_range_start ?? null;
$price_range_end = $request->price_range_end ?? null;
$user = auth()->user();
$userId = $user ? $user->id : 0;
[$location_id, $is_consignment] = Cache::remember('employee_user_'.$userId, 60 * 60 * 24, function () use ($user) {
if ($user == null) {
return [10, false];
}
$employee = @$user->employee;
$location_id = @$employee->location_id;
$location = @$employee->location;
$is_consignment = (bool) @$location->is_consignment;
return [$location_id, $is_consignment];
});
$productRepository = new ProductRepository;
$products = $productRepository->getList([
'limit' => $page * $limit,
'sort' => $sortBy,
'category_id' => $filter['category'] ?? null,
'gender_id' => $filter['gender'] ?? null,
'brand_id' => $filter['brand'] ?? null,
'search' => $search,
'location_id' => $location_id,
'is_consignment' => $is_consignment,
'price_range_start' => $price_range_start,
'price_range_end' => $price_range_end,
]);
// Check if there are more products
$hasMore = count($products) >= $limit;
// Render product cards HTML
$productHtml = '';
if (count($products) == 0) {
$productHtml = '<div class="col-12">';
$productHtml .= 'Pencarian tidak ditemukan';
$productHtml .= '</div>';
} else {
foreach ($products as $product) {
$productHtml .= '<div class="col">';
$productHtml .= view('components.home.product-card', ['product' => $product])->render();
$productHtml .= '</div>';
}
}
// filter
$filter = $request->filter ?? [];
if (isset($filter['category']) && $filter['category']) {
$category = StoreCategory::find($filter['category']);
if ($category) {
$filter['category'] = $category->name;
} else {
unset($filter);
}
}
if (isset($filter['gender']) && $filter['gender']) {
$gender = Gender::find($filter['gender']);
if ($gender) {
$filter['gender'] = $gender->name;
} else {
unset($filter);
}
}
if (isset($filter['brand']) && $filter['brand']) {
$brand = \App\Models\Brand::find($filter['brand']);
if ($brand) {
$filter['brand'] = $brand->name;
} else {
unset($filter);
}
}
$filters = $filter;
return response()->json([
'success' => true,
'filters' => $filters,
'products' => $productHtml,
'count' => count($products),
'has_more' => $hasMore,
'current_page' => $page,
]);
}
public function index(Request $request)
{
$productRepository = new ProductRepository;
$products = [];
$filters = [];
$min_max_price = $productRepository->getMinMaxPrice();
return view('shop.catalog-fashion', [
'products' => $products,
'min_max_price' => $min_max_price,
]);
}
public function brands(Request $request)
{
$brandRepository = new \App\Repositories\Catalog\BrandRepository;
$brands = $brandRepository->getList([]);
// Render brand links HTML
$brandHtml = '';
$currentBrandId = $request->input('current_brand');
foreach ($brands as $brand) {
$isActive = $currentBrandId == $brand->id;
$brandHtml .= '<li class="nav-item mb-1">';
$brandHtml .= '<a class="nav-link d-block fw-normal p-0 '.($isActive ? 'active text-primary' : '').'" ';
$brandHtml .= 'href="#" data-brand-id="'.$brand->id.'">';
$brandHtml .= $brand->name;
$brandHtml .= '</a></li>';
}
return response()->json([
'success' => true,
'brands' => $brandHtml,
]);
}
public function brandsWithImages(Request $request)
{
$brandRepository = new \App\Repositories\Catalog\BrandRepository;
$brands = $brandRepository->getList([]);
// Render brand links HTML with images as swiper slides
$brandHtml = '';
$currentBrandId = $request->input('current_brand');
foreach ($brands as $brand) {
$isActive = $currentBrandId == $brand->id;
// Only show brands that have images
if ($brand->image_url) {
$brandHtml .= '<a class="swiper-slide text-body" href="#!" aria-label="'.$brand->name.'" role="group" style="width: 170.2px; margin-right: 20px;">';
$brandHtml .= '<img src="'.$brand->image_url.'" alt="'.$brand->name.'" class="me-2" width="100" height="100">';
$brandHtml .= '</a>';
}
}
return response()->json([
'success' => true,
'brands' => $brandHtml,
]);
}
public function announcements(Request $request)
{
// Static announcements for now - can be moved to database later
$announcements = [
// [
// 'icon' => '🎉',
// 'message' => 'Free Shipping on orders over $250',
// 'detail' => "Don't miss a discount!",
// ],
// [
// 'icon' => 'đź’°',
// 'message' => 'Money back guarantee',
// 'detail' => 'We return money within 30 days',
// ],
// [
// 'icon' => 'đź’Ş',
// 'message' => 'Friendly 24/7 customer support',
// 'detail' => "We've got you covered!",
// ],
];
// Render announcement slides HTML
$announcementHtml = '';
foreach ($announcements as $announcement) {
$announcementHtml .= '<div class="swiper-slide text-truncate text-center">';
$announcementHtml .= $announcement['icon'].' '.$announcement['message'].' <span class="d-none d-sm-inline">'.$announcement['detail'].'</span>';
$announcementHtml .= '</div>';
}
return response()->json([
'success' => true,
'announcements' => $announcementHtml,
]);
}
public function highlights(Request $request)
{
$type = $request->input('type', 'new');
$limit = 8;
$user = auth()->user();
$userId = $user ? $user->id : 0;
[$location_id, $is_consignment] = Cache::remember('employee_user_'.$userId, 60 * 60 * 24, function () use ($user) {
if ($user == null) {
return [10, false];
}
$employee = @$user->employee;
$location_id = @$employee->location_id;
$location = @$employee->location;
$is_consignment = (bool) @$location->is_consignment;
return [$location_id, $is_consignment];
});
$productRepository = new ProductRepository;
// Set parameters based on type
$params = [
'limit' => $limit,
'location_id' => $location_id,
'is_consignment' => $is_consignment,
];
switch ($type) {
case 'best_sellers':
$params['sort'] = 'random';
break;
case 'new_arrivals':
$params['sort'] = 'new';
break;
case 'sale_items':
$params['event'] = 'special-offer';
break;
case 'top_rated':
$params['sort'] = 'random';
break;
default:
$params['sort'] = 'new';
break;
}
$params['limit'] = 10;
$products = $productRepository->getList($params);
// Render product cards HTML
$productHtml = '';
if (count($products) == 0) {
$productHtml = '<div class="col-12 text-center py-4">';
$productHtml .= 'No products found';
$productHtml .= '</div>';
} else {
foreach ($products as $product) {
$productHtml .= '<div class="col">';
$productHtml .= view('components.home.product-card', ['product' => $product])->render();
$productHtml .= '</div>';
}
}
return response()->json([
'success' => true,
'products' => $productHtml,
'count' => count($products),
'type' => $type,
]);
}
public function populers(Request $request)
{
$type = $request->input('type', 'new');
$limit = 6;
$user = auth()->user();
$userId = $user ? $user->id : 0;
[$location_id, $is_consignment] = Cache::remember('employee_user_'.$userId, 60 * 60 * 24, function () use ($user) {
if ($user == null) {
return [10, false];
}
$employee = @$user->employee;
$location_id = @$employee->location_id;
$location = @$employee->location;
$is_consignment = (bool) @$location->is_consignment;
return [$location_id, $is_consignment];
});
$productRepository = new ProductRepository;
// Set parameters based on type
$params = [
'limit' => $limit,
'location_id' => $location_id,
'is_consignment' => $is_consignment,
];
switch ($type) {
case 'new':
$params['sort'] = 'new';
break;
case 'best_sellers':
$params['sort'] = 'best_sellers';
break;
case 'special-offer':
$params['event'] = 'special-offer';
break;
case 'top_rated':
$params['sort'] = 'random';
break;
default:
$params['sort'] = 'new';
break;
}
$products = $productRepository->getList($params);
// Render product cards HTML
$productHtml = '';
if (count($products) == 0) {
$productHtml = '<div class="col-12 text-center py-4">';
$productHtml .= 'No products found';
$productHtml .= '</div>';
} else {
foreach ($products as $product) {
$productHtml .= '<div class="col">';
$productHtml .= view('components.home.product-card', ['product' => $product])->render();
$productHtml .= '</div>';
}
}
return response()->json([
'success' => true,
'products' => $productHtml,
'count' => count($products),
'type' => $type,
]);
}
public function populersJson(Request $request)
{
$type = $request->input('type', 'new');
$limit = 6;
$user = auth()->user();
$userId = $user ? $user->id : 0;
[$location_id, $is_consignment] = Cache::remember('employee_user_'.$userId, 60 * 60 * 24, function () use ($user) {
if ($user == null) {
return [10, false];
}
$employee = @$user->employee;
$location_id = @$employee->location_id;
$location = @$employee->location;
$is_consignment = (bool) @$location->is_consignment;
return [$location_id, $is_consignment];
});
$productRepository = new ProductRepository;
// Set parameters based on type
$params = [
'limit' => $limit,
'location_id' => $location_id,
'is_consignment' => $is_consignment,
];
switch ($type) {
case 'new':
$params['sort'] = 'new';
break;
case 'best_sellers':
$params['sort'] = 'best_sellers';
break;
case 'special-offer':
$params['event'] = 'special-offer';
break;
case 'top_rated':
$params['sort'] = 'random';
break;
default:
$params['sort'] = 'new';
break;
}
$params['limit'] = 10;
$products = $productRepository->getList($params);
$p = $products->map(function($row){
$row->image_url = $row->image_url;
return $row;
});
return response()->json([
'data' =>$p,
]);
}
/**
* Track product visit analytics
*/
private function trackProductVisit(int $productId, Request $request): void
{
try {
$analyticsRepository = new AnalyticsProductVisitRepository();
$visitData = [
'item_id' => $productId,
'user_id' => auth()->id(),
'session_id' => session()->getId(),
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
'started_at' => now(),
'duration_seconds' => 0, // Will be updated when user leaves
'device_type' => $this->getDeviceType($request->userAgent()),
];
$analyticsRepository->create($visitData);
} catch (\Exception $e) {
// Log error but don't break the application
\Log::error('Failed to track product visit: ' . $e->getMessage());
}
}
/**
* Detect device type from user agent
*/
private function getDeviceType(string $userAgent): string
{
$userAgent = strtolower($userAgent);
if (strpos($userAgent, 'mobile') !== false) {
return 'Mobile';
}
if (strpos($userAgent, 'tablet') !== false) {
return 'Tablet';
}
return 'Desktop';
}
public function detailFashion($slug, Request $request, ProductRepository $productRepository)
{
$product = $productRepository->show($slug);
// Track product visit analytics
$this->trackProductVisit($product->id, $request);
$complete_look_products_data = $productRepository->getList([
'category_id' => $product->category1_id,
'limit' => 4,
]);
$complete_look_products = collect($complete_look_products_data->items())->chunk(2);
$variants = $product->publishedItemVariants ?? [];
if (count($variants) == 0) {
$variants = collect([$product]);
}
return view('shop.product-fashion', [
'product' => $product,
'variants' => $variants,
'complete_look_products' => $complete_look_products,
]);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Repositories\Crm\SurveyFeedbackRepository;
class ReviewController extends Controller
{
public function index(Request $request, SurveyFeedbackRepository $surveyFeedbackRepository)
{
$list = $surveyFeedbackRepository->getList([]);
// dd($list);
return view('account.reviews',[
'list' => $list
]);
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace App\Http\Controllers;
use App\Models\Items;
use App\Models\StoreCategory;
use App\Models\Gender;
use Illuminate\Http\Request;
class SearchController extends Controller
{
public function search(Request $request)
{
$query = $request->get('q');
if (empty($query) || strlen($query) < 2) {
return response()->json([
'success' => true,
'results' => [],
'message' => 'Please enter at least 2 characters'
]);
}
// Search products
$products = Items::where('is_publish', true)
->where('deleted_at', null)
->where(function($q) use ($query) {
$q->where('name', 'ILIKE', "%{$query}%")
->orWhere('number', 'ILIKE', "%{$query}%")
->orWhere('description', 'ILIKE', "%{$query}%");
})
->select('id', 'name', 'number', 'slug')
->limit(8)
->get();
// Search categories
$categories = StoreCategory::where('name', 'ILIKE', "%{$query}%")
->select('id', 'name')
->limit(3)
->get();
// Search genders
$genders = Gender::where('name', 'ILIKE', "%{$query}%")
->select('id', 'name')
->limit(3)
->get();
// Format results
$results = [];
if ($products->isNotEmpty()) {
$results['products'] = $products->map(function($product) {
$price = $product->display_price;
return [
'id' => $product->id,
'name' => $product->name,
'number' => $product->number,
'slug' => $product->slug ?? $product->id,
'type' => 'product',
'route' => route('product.detail', $product->slug)
];
});
}
if ($categories->isNotEmpty()) {
$results['categories'] = $categories->map(function($category) {
return [
'id' => $category->id,
'name' => $category->name,
'slug' => $category->slug,
'type' => 'category',
'route' => route('product.index',['filter[category]'=>$category->id])
];
});
}
if ($genders->isNotEmpty()) {
$results['genders'] = $genders->map(function($gender) {
return [
'id' => $gender->id,
'name' => $gender->name,
'slug' => $gender->slug,
'type' => 'gender',
'route' => route('product.index',['filter[gender]'=>$gender->id])
];
});
}
return response()->json([
'success' => true,
'results' => $results,
'query' => $query
]);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TncController extends Controller
{
public function index(Request $request)
{
return view('terms-and-conditions');
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers;
use App\Repositories\Member\VoucherEvent\VoucherEventRepository;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
class VoucherEventController extends Controller
{
protected $voucherEventRepository;
public function __construct(VoucherEventRepository $voucherEventRepository)
{
$this->voucherEventRepository = $voucherEventRepository;
}
/**
* Redeem a voucher event
*
* @param int $voucherEvent
* @param Request $request
* @return JsonResponse
*/
public function redeem($voucherEvent, Request $request): JsonResponse
{
try {
// Get the authenticated user
$user = auth()->user();
if (!$user) {
return response()->json([
'success' => false,
'message' => 'User not authenticated'
], 401);
}
// Call the repository's redeem method
$result = $this->voucherEventRepository->redeem($voucherEvent, $user);
if ($result) {
return response()->json([
'success' => true,
'message' => $result['message'] ?? 'Voucher redeemed successfully!',
'data' => $result['data'] ?? null
]);
} else {
return response()->json([
'success' => false,
'message' => $result['message'] ?? 'Failed to redeem voucher'
], 400);
}
} catch (\Exception $e) {
Log::error($e);
return response()->json([
'success' => false,
'message' => $e->getMessage()
], 500);
}
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace App\Http\Controllers;
use App\Repositories\Member\WishlistRepository;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
class WishController extends Controller
{
protected $wishlistRepository;
public function __construct(WishlistRepository $wishlistRepository)
{
$this->wishlistRepository = $wishlistRepository;
}
/**
* Display the wishlist page
*/
public function index(Request $request)
{
try {
$wishlists = $this->wishlistRepository->getList($request);
return view('account.wishlist', [
'wishlists' => $wishlists,
'user' => Auth::user()
]);
} catch (\Exception $e) {
return redirect()->back()->with('error', 'Failed to load wishlist: ' . $e->getMessage());
}
}
/**
* Add item to wishlist
*/
public function store(Request $request): JsonResponse
{
$request->validate([
'item_id' => 'required|exists:items,id'
]);
try {
$wishlist = $this->wishlistRepository->create([
'item_id' => $request->item_id
]);
return response()->json([
'success' => true,
'message' => 'Item added to wishlist successfully',
'wishlist' => $wishlist
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Failed to add item to wishlist: ' . $e->getMessage()
], 500);
}
}
/**
* Remove item from wishlist
*/
public function destroy(Request $request, $id): JsonResponse
{
try {
$this->wishlistRepository->delete($id);
return response()->json([
'success' => true,
'message' => 'Item removed from wishlist successfully'
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Failed to remove item from wishlist: ' . $e->getMessage()
], 500);
}
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Session;
class SetLocale
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
// Check if locale is in request
if ($request->has('locale')) {
$locale = $request->get('locale');
// Validate locale
if (in_array($locale, ['en', 'id'])) {
App::setLocale($locale);
Session::put('locale', $locale);
}
}
// Check if locale is in session
elseif (Session::has('locale')) {
$locale = Session::get('locale');
if (in_array($locale, ['en', 'id'])) {
App::setLocale($locale);
}
}
// Set default locale to 'id' if not set
else {
$locale = 'id';
App::setLocale($locale);
Session::put('locale', $locale);
}
return $next($request);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Member\Cart;
use Illuminate\Foundation\Http\FormRequest;
class MemberCartRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'item_reference_id' => 'required|integer|exists:item_reference,id',
'qty' => 'required|numeric|min:0',
'location_id' => 'required|numeric',
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Member\Cart;
use Illuminate\Foundation\Http\FormRequest;
class UpdateMemberCartRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'qty' => 'required|numeric|min:0'
];
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Member\Transaction;
use Illuminate\Foundation\Http\FormRequest;
class CancelRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$transaction = $this->transaction;
$user = auth()->user();
$isAdmin = auth()->user()->role->permissions->contains(function($value){
return $value->code == "transaction.online";
});
$isOwner = (@$transaction->customer->user->id == @$user->id) && ($transaction->status == 'WAIT_PAYMENT' || $transaction->status == 'WAIT_PROCESS');
return $isAdmin || $isOwner;
}
public function rules()
{
return [
'note' => 'nullable|string',
];
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Member\Transaction;
use Illuminate\Foundation\Http\FormRequest;
class CloseRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$transaction = $this->transaction;
$user = auth()->user();
$isAdmin = auth()->user()->role->permissions->contains(function($value){
return $value->code == "transaction.online";
});
$isOwner = (@$transaction->customer->user->id == @$user->id) && ($transaction->status == 'WAIT_PAYMENT' || $transaction->status == 'WAIT_PROCESS');
return $isAdmin || $isOwner;
}
public function rules()
{
return [
'note' => 'nullable|string',
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Member\Transaction;
use Illuminate\Foundation\Http\FormRequest;
class DeliverRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$transaction = $this->transaction;
$user = auth()->user();
$isAdmin = auth()->user()->role->permissions->contains(function($value){
return $value->code == "transaction.online";
});
return $isAdmin;
}
public function rules()
{
return [
'note' => 'nullable|string',
];
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Member\Transaction;
use Illuminate\Foundation\Http\FormRequest;
class DetailRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$transaction = $this->transaction;
$user = auth()->user();
$isAdmin = auth()->user()->role->permissions->contains(function($value){
return $value->code == "transaction.online";
});
$isOwner = (@$transaction->customer->user->id == @$user->id);
return $isAdmin || $isOwner;
}
public function rules()
{
return [
'note' => 'nullable|string',
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Member\Transaction;
use Illuminate\Foundation\Http\FormRequest;
class ProcessRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$transaction = $this->transaction;
$user = auth()->user();
$isAdmin = auth()->user()->role->permissions->contains(function($value){
return $value->code == "transaction.online";
});
return $isAdmin;
}
public function rules()
{
return [
'note' => 'nullable|string',
];
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\Member\Transaction;
use Illuminate\Foundation\Http\FormRequest;
class TransactionRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
public function rules()
{
return [
'address_id' => 'required|integer|exists:address,id',
'location_id' => 'required|integer',
'courier_company' => 'required|string',
'courier_type' => 'required|string',
'vouchers' => 'nullable|array',
'vouchers.*' => 'required|integer',
'items' => 'nullable|array',
'items.*.item_reference_id' => 'required|integer',
'items.*.qty' => 'required|integer',
'use_customer_points' => 'nullable|integer',
];
}
}

80
app/Models/Address.php Normal file
View File

@ -0,0 +1,80 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Address extends Model
{
use HasFactory;
use SoftDeletes;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $table = 'address';
protected $fillable = [
'label',
'name',
'address',
'province_id',
'city_id',
'district_id',
'subdistrict_id',
'province_name',
'city_name',
'district_name',
'subdistrict_name',
'postal_code',
'phone',
'longitude',
'latitude',
'user_id',
'is_primary'
];
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function province()
{
return $this->belongsTo(Province::class, 'province_id', 'id');
}
public function city()
{
return $this->belongsTo(City::class, 'city_id', 'id');
}
public function district()
{
return $this->belongsTo(District::class, 'district_id', 'id');
}
public function subdistrict()
{
return $this->belongsTo(Subdistrict::class, 'subdistrict_id', 'id');
}
public function getLocationAttribute()
{
$province = $this->province?->name;
$city = $this->city?->name;
$district = $this->district?->name;
$subdistrict = $this->subdistrict?->name;
$postalCode = $this->postal_code;
return "{$province}, {$city}, {$district}, {$subdistrict}, {$postalCode}";
}
}

83
app/Models/Affiliator.php Normal file
View File

@ -0,0 +1,83 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Affiliator extends Model
{
use HasFactory;
use LogsActivity;
public static $STATUS_PENDING = 'pending';
public static $STATUS_APPROVED = 'approved';
public static $STATUS_REJECTED = 'rejected';
public static $STATUS_CANCELLED = 'cancelled';
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $fillable = [
'name',
'code',
'email',
'phone',
'user_id',
'bank',
'account_name',
'account_number',
'fee_percentage',
'dob',
'coaching_status',
'coaching_area',
'caddy_area',
'caddy_los',
'trainee_count',
'instagram',
'youtube',
'tiktok',
'type',
'verified_at',
'status'
];
protected $casts = [
'dob' => 'date',
'verified_at' => 'datetime',
];
public function user()
{
return $this->belongsTo(User::class);
}
public function withdraws()
{
return $this->hasMany(AffiliatorWithdraw::class);
}
public function feeLedgers()
{
return $this->hasMany(AffiliatorFeeLedger::class);
}
public function getBalanceAttribute() : float
{
return $this->feeLedgers()->sum('amount');
}
public function routeNotificationForEmail()
{
$email = $this->email;
return $email;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class AffiliatorFeeLedger extends Model
{
use HasFactory;
protected $table = 'affiliator_fee_ledgers';
protected $fillable = [
'affiliator_id',
'amount',
'status',
'time',
'transaction_type',
'transaction_id'
];
protected $casts = [
'amount' => 'integer',
];
public function affiliator()
{
return $this->belongsTo(Affiliator::class);
}
public function transaction()
{
return $this->morphTo();
}
public function getTitleAttribute()
{
if ($this->transaction_type == "App\Models\AffiliatorWithdraw") {
return "Penarikan Saldo";
}
return "-";
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class AffiliatorItem extends Model
{
use HasFactory, SoftDeletes;
protected $table = 'affiliator_items';
protected $primaryKey = 'id';
public $incrementing = true;
protected $keyType = 'int';
protected $fillable = [
'item_reference_id',
'discount',
'fee',
'qty',
];
public function item()
{
return $this->belongsTo(ItemReference::class, 'item_reference_id', 'id');
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class AffiliatorItemCode extends Model
{
use HasFactory;
protected $table = 'affiliator_item_codes';
protected $primaryKey = 'id';
public $incrementing = true;
protected $keyType = 'int';
protected $fillable = [
'code',
'codeable_id',
'codeable_type',
'affiliator_item_id',
'affiliator_id',
];
public function affiliator()
{
return $this->belongsTo(Affiliator::class);
}
public function affiliatorItem()
{
return $this->belongsTo(AffiliatorItem::class);
}
public function codeable()
{
return $this->morphTo();
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class AffiliatorWithdraw extends Model
{
use HasFactory;
protected $table = 'affiliator_withdraws';
protected $fillable = [
'affiliator_id',
'amount',
'status',
'time',
];
public function affiliator()
{
return $this->belongsTo(Affiliator::class);
}
public function feeLedger()
{
return $this->morphOne(AffiliatorFeeLedger::class, 'transaction');
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AnalyticsProductVisit extends Model
{
protected $connection = 'ecommerce';
protected $table = 'analytics_product_visits';
protected $fillable = [
'item_id',
'user_id',
'session_id',
'started_at',
'ended_at',
'duration_seconds',
'ip_address',
'device_type',
'user_agent',
'referrer',
];
protected $casts = [
'started_at' => 'datetime',
'ended_at' => 'datetime',
];
public function calculateDuration(): void
{
if ($this->started_at && $this->ended_at) {
$this->duration_seconds =
$this->ended_at->diffInSeconds($this->started_at);
}
}
}

21
app/Models/Banner.php Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Storage;
class Banner extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = ["filename","content_id","content_type","caption","is_active"];
public $appends = ['url'];
public function getUrlAttribute(){
return ($this->filename) ? Storage::disk("public")->url($this->filename): null;
}
}

48
app/Models/Brand.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Storage;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Brand extends Model
{
use HasFactory, SoftDeletes;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $table = 'brands';
protected $primaryKey = 'id';
protected $fillable = [
'name',
'code',
'image',
'hide_point',
'priority'
];
public function items()
{
return $this->hasMany(Items::class, 'brand_id', 'id');
}
public function getImageUrlAttribute()
{
return $this->image ? Storage::disk('wms')->url($this->image) : null;
}
public function getImageDarkUrlAttribute()
{
return $this->image ? Storage::disk('wms')->url($this->image) : null;
}
}

39
app/Models/Cart.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Cart extends Model
{
use HasFactory;
protected $fillable = ['item_id', 'price','is_checked', 'item_variant_id', 'item_reference_id', 'user_id', 'qty','location_id'];
public function item()
{
return $this->belongsTo(Items::class,"item_id");
}
public function itemVariant()
{
return $this->belongsTo(ItemVariant::class, 'item_variant_id', 'id');
}
public function itemReference()
{
return $this->belongsTo(ItemReference::class, 'item_reference_id', 'id');
}
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function getDisplayPriceAttribute()
{
return $this->itemReference->item->display_price;
}
}

29
app/Models/Categories.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Categories extends Model
{
use HasFactory;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $table = 'categories';
protected $primaryKey = 'id';
protected $fillable = [
'code',
'name',
'image',
'is_publish'
];
}

18
app/Models/City.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class City extends Model
{
use HasFactory;
protected $fillable = ['province_id', 'name'];
public function province()
{
return $this->belongsTo(Province::class, 'province_id', 'id');
}
}

118
app/Models/Customer.php Normal file
View File

@ -0,0 +1,118 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
use Illuminate\Notifications\Notifiable;
class Customer extends Model
{
use HasFactory, SoftDeletes, Sluggable, Notifiable;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $table = 'customers';
protected $primaryKey = 'id';
protected $fillable = [
'id',
'number',
'name',
'phone',
'email',
'address',
'country',
'province_id',
'city_id',
'district_id',
'village_id',
'postal_code',
'customer_group_id',
'location_id',
'referal',
'is_active',
'verified_at',
'user_id',
'organization',
'prefix',
'channel',
'creator_id',
'gender',
'date_of_birth',
'profilling_id'
];
public function sluggable(): array
{
return [
'number' => [
'source' => 'number'
]
];
}
public function scopeFilter(Builder $query, array $filters)
{
$query->when($filters['search'] ?? false, function ($query, $search) {
return $query
->where('name', 'iLIKE', '%' . $search . '%')
->orWhere('phone', 'LIKE', '%' . $search . '%');
});
}
public function locations()
{
return $this->belongsTo(Location::class, 'location_id', 'id');
}
public function customerGroup()
{
return $this->belongsTo(CustomerGroup::class, 'customer_group_id', 'id');
}
// public function proffiling()
// {
// return $this->hasMany(FittingOrderCustomer::class);
// }
public function user()
{
return $this->belongsTo(User::class);
}
// public function chat()
// {
// return $this->hasMany(Chat::class);
// }
// public function profillings()
// {
// return $this->hasMany(Profilling::class);
// }
// public function clubMembers()
// {
// return $this->hasMany(ClubMember::class, 'customer_id', 'id');
// }
public function getPointAttribute()
{
return $this->hasMany(CustomerPoint::class)->sum("point");
}
public function routeNotificationForEmail()
{
$email = $this->email;
return $email;
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class CustomerGroup extends Model
{
use HasFactory, SoftDeletes;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $table = 'customer_groups';
protected $primaryKey = 'id';
protected $fillable = [
'code',
'name'
];
public function scopeFilter(Builder $query, array $filters)
{
$query->when($filters['search'] ?? false, function ($query, $search) {
return $query
->where('name', 'iLIKE', '%' . $search . '%')
->orWhere('code', 'LIKE', '%' . $search . '%');
});
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CustomerPoint extends Model
{
use HasFactory;
protected $fillable = [
"point",
"customer_id",
"description",
"reference_type",
"reference_id",
"description"
];
public function reference() {
return $this->morphTo();
}
public function customer() {
return $this->belongsTo(Customer::class);
}
}

33
app/Models/Discount.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Discount extends Model
{
use HasFactory;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $fillable = ['name', 'valid_at', 'expired_at', 'customer_group_id', 'discount_percent','location_id', 'type', 'apply_point', 'apply_incentive'];
public function discountItems() {
return $this->hasMany(DiscountItem::class);
}
public function customerGroup() {
return $this->belongsTo(CustomerGroup::class);
}
public function location() {
return $this->belongsTo(Location::class);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class DiscountItem extends Model
{
use HasFactory;
protected $fillable = ['item_reference_id', 'price', 'discount_id', 'percent', 'original_price'];
public function itemReference()
{
return $this->belongsTo(ItemReference::class);
}
public function discount()
{
return $this->belongsTo(Discount::class);
}
}

18
app/Models/District.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class District extends Model
{
use HasFactory;
protected $fillable = ['city_id', 'name'];
public function city()
{
return $this->belongsTo(City::class, 'city_id', 'id');
}
}

36
app/Models/Gender.php Normal file
View File

@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Gender extends Model
{
use HasFactory;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $table = 'genders';
protected $primaryKey = 'id';
protected $fillable = ['name', 'image'];
public function items()
{
return $this->hasMany(Items::class, 'gender_id', 'id');
}
public function productCount()
{
return $this->items()->count();
}
}

13
app/Models/ItemImage.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ItemImage extends Model
{
use HasFactory;
protected $fillable = ["filename", "item_id", "item_reference_id", "item_variant_id", "is_main"];
}

View File

@ -0,0 +1,204 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Awobaz\Compoships\Compoships;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class ItemReference extends Model
{
use HasFactory, Compoships;
protected $table = 'item_reference';
protected $primaryKey = 'id';
protected $fillable = ['item_id','item_variant_id','number','unit'];
public function images()
{
return $this->hasMany(ItemImage::class, 'item_id', 'item_id');
}
public function item()
{
return $this->belongsTo(Items::class)->with("brand", "gender", "category");
}
public function itemVariant()
{
return $this->belongsTo(ItemVariant::class);
}
public function variants()
{
$model = ItemVariant::where('item_id', $this->item_id)->get();
return $model;
}
public function location()
{
return $this->belongsTo(Location::class, "location_id");
}
public function stocks()
{
return $this->hasMany(Stock::class, ["item_id", "item_variant_id"], ["item_id", "item_variant_id"])
->with("location");
}
public function bucketStocks()
{
return $this->hasMany(BucketStock::class)->with("bucket");
// ->where("quantity",">",0);
}
public function promotion()
{
return $this->belongsToMany(Promotion::class, "promotion_item", "item_id", "promotion_id");
}
public function price()
{
$user = auth()->user();
[$location_id, $is_consignment] = Cache::remember(
"employee_user_" . optional($user)->id,
60,
function () use ($user) {
if (!$user) {
return [10, false];
}
$employee = $user->employee;
$location_id = $employee->location_id ?? 10;
$location = $employee->location;
$is_consignment = (bool) optional($location)->is_consignment
&& optional($location)->id != 9;
return [$location_id, $is_consignment];
}
);
return $this->hasOne(Discount::class, 'id', 'item_reference_id')
->leftJoin('discount_items', 'discount_items.discount_id', '=', 'discounts.id')
->where('discounts.type', 'price')
->where(function ($query) {
$query->where('valid_at', '<=', now())
->orWhereNull('valid_at');
})
->where(function ($query) {
$query->where('expired_at', '>', now())
->orWhereNull('expired_at');
})
->where(function ($query) use ($location_id, $is_consignment) {
if (!$is_consignment) {
$query->whereNull('discounts.location_id');
}
if ($location_id) {
$query->orWhere('discounts.location_id', $location_id);
}
})
->orderByDesc('discounts.created_at')
->select([
'discount_items.item_reference_id',
'discount_items.price',
]);
}
public function discount()
{
$user = auth()->user();
list($location_id, $is_consignment) = Cache::remember("employee_user_".$user->id, 60 * 60 * 24, function(){
$employee = @$user->employee;
$location_id = @$employee->location_id ?? 10;
$location = @$employee->location;
$is_consignment = (boolean) @$location->is_consignment && $location->id != 9;
return [$location_id, $is_consignment];
});
return $this->hasOne(Discount::class, 'id', 'item_reference_id')
->leftJoin("discount_items","discounts.id","=","discount_id")
->where("discounts.type","discount")
->orderBy("discounts.created_at","desc")
->where(function($query){
$query->where("valid_at", "<=", Carbon::now())
->orWhereNull("valid_at");
})
->where(function($query){
$query->where("expired_at", ">", Carbon::now())
->orWhereNull("expired_at");
})
->where(function($query) use ($location_id, $is_consignment){
if (!$is_consignment)
$query->whereNull("discounts.location_id");
if ($location_id)
$query->orWhere("discounts.location_id", $location_id);
})
->select("item_reference_id","price");
}
public function point()
{
return $this->hasOne(CustomerPointRule::class);
}
public function promotion3()
{
$now = Carbon::now()->format('Y-m-d');
$promotions = "select DISTINCT ON (item_id) item_id as item_reference_id, name, note from
promotions left join promotion_item on promotions.id = promotion_id
where (valid_date is null or valid_date::date <= '$now')
and (expired_date is null or expired_date::date >= '$now')
and type <> 'POINT'";
$points = "select item_reference_id, array_agg((case when qty is null then '' else (qty || ': ') end) || point || ' Point' order by point asc) as name
from customer_point_rules
group by item_reference_id";
return $this->hasOne(ItemReference::class,"id")
->select('item_reference.id as item_reference_id',DB::raw("(case when points.name is null then promotions.name else array_to_string(points.name,', ') end) as promotion"),'promotions.note as promotion_note')
->leftJoin(DB::raw("(" . $promotions . ") as promotions"), "promotions.item_reference_id", "=", "item_reference.id")
->leftJoin(DB::raw("(" . $points . ") as points"), "points.item_reference_id", "=", "item_reference.id");
}
public function promotion2()
{
$now = Carbon::now()->format('Y-m-d');
$promotions = "select DISTINCT ON (item_id) item_id, name, note from
promotions left join promotion_item on promotions.id = promotion_id
where (valid_date is null or valid_date::date <= '$now')
and (expired_date is null or expired_date::date >= '$now')
and item_id = $this->id
and type <> 'POINT'";
$points = "select item_reference_id, array_agg((case when qty is null then '' else (qty || ': ') end) || point || ' Point' order by point asc) as name
from customer_point_rules
where item_reference_id = $this->id
group by item_reference_id";
$result = DB::table('item_reference')
->select(DB::raw("(case when points.name is null then promotions.name else array_to_string(points.name,', ') end) as promotion"),
'promotions.note as promotion_note')
->leftJoin(DB::raw("(" . $promotions . ") as promotions"), "promotions.item_id", "=", "item_reference.id")
->leftJoin(DB::raw("(" . $points . ") as points"), "points.item_reference_id", "=", "item_reference.id")
->where('item_reference.id', $this->id)
->first();
return $result;
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Awobaz\Compoships\Compoships;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class ItemVariant extends Model
{
use HasFactory;
use Compoships;
protected $table = 'item_variants';
protected $primaryKey = 'id';
protected $fillable = [
'id',
'code',
'description',
'item_id',
'display_name',
'is_publish',
'barcode',
'variant_name',
];
public function reference()
{
return $this->belongsTo(ItemReference::class, ["item_id", "id"], ["item_id", "item_variant_id"]);
}
public function variables()
{
return $this->belongsToMany(Variant::class, "variant_value")->select("id", "name", "value");
}
public function image()
{
return $this->hasOne(ItemImage::class)->where('item_id', $this->item_id);
}
public function images()
{
return $this->hasMany(ItemImage::class)->where('item_id', $this->item_id);
}
public function variantValue()
{
return $this->hasMany(\App\Models\VariantValue::class, 'item_variant_id');
}
public function getImageUrlAttribute()
{
$image = $this->images->first()->filename ?? null;
if (str_contains($image,"http")) {
return $image;
}
return $image ? Storage::disk('wms')->url($image) : null;
}
public function item()
{
return $this->belongsTo(Items::class, 'item_id');
}
public function getDisplayPriceAttribute()
{
return $this->reference->item->display_price;
}
}

364
app/Models/Items.php Normal file
View File

@ -0,0 +1,364 @@
<?php
namespace App\Models;
use Awobaz\Compoships\Compoships;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Spatie\Activitylog\LogOptions;
class Items extends Model
{
use Compoships;
use HasFactory, SoftDeletes;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
// protected $with = ['brands', 'categories'];
protected $table = 'items';
protected $primaryKey = 'id';
protected $guarded = ['id'];
protected $fillable = [
'number',
'name',
'brand_id',
'category_id',
'image',
'description',
'weight',
'is_publish',
// BC data
'type',
'unit',
'display_unit',
'unit_cost',
'costing_method',
'last_direct_cost',
'unit_price',
'price_include_vat',
'sales_blocked',
'purchasing_blocked',
'profit_percent',
'net_weight',
'gross_weight',
'unit_volume',
'net_price',
'ref_id',
// purchase,
'alias_name',
'group_type',
'model_type',
'slug',
];
public function scopeFilter(Builder $query, array $filters)
{
$query->when($filters['search'] ?? false, function ($query, $search) {
return $query
->where('number', 'iLIKE', '%'.$search.'%')
->orWhere('name', 'iLIKE', '%'.$search.'%');
});
}
public function order()
{
return $this->hasMany(SaleOrderDetail::class, 'item_id', 'id');
}
public function dimension()
{
return $this->hasOne(ItemDimension::class, 'no', 'number');
}
public function reference()
{
return $this->hasOne(ItemReference::class, 'item_id')
->where(function ($query) {
$query->whereNull('item_variant_id')->orWhere('item_variant_id', 0);
});
}
public function variants()
{
return $this->hasMany(ItemVariant::class, 'item_id')
->leftJoin('stocks', 'item_variant_id', '=', 'item_variants.id')
->select('item_variants.id', 'item_variants.code', 'item_variants.display_name', 'item_variants.is_publish', DB::raw('SUM(quantity) as stock'), 'description', 'item_variants.item_id')
->groupBy('item_variants.id', 'item_variants.code', 'description', 'item_variants.item_id', 'item_variants.display_name', 'item_variants.is_publish');
}
public function images()
{
return $this->hasMany(ItemImage::class, 'item_id')->whereNull('item_variant_id');
}
public function itemVariants()
{
return $this->hasMany(ItemVariant::class, 'item_id')->with('reference');
}
public function publishedItemVariants()
{
return $this->hasMany(ItemVariant::class, 'item_id')->with('reference')->where('is_publish', true);
}
public function brands()
{
return $this->belongsTo(Brand::class, 'brand_id', 'id');
}
public function categories()
{
return $this->belongsTo(Categories::class, 'categories_id', 'id');
}
public function brand()
{
return $this->belongsTo(Brand::class, 'brand_id', 'id');
}
public function gender()
{
return $this->belongsTo(Gender::class, 'gender_id', 'id');
}
public function category()
{
return $this->belongsTo(Categories::class, 'category_id', 'id');
}
public function discount()
{
$user = auth()->user();
[$location_id, $is_consignment] = Cache::remember(
'employee_user_'.optional($user)->id,
60 * 60 * 24,
function () use ($user) {
if (! $user) {
return [10, false];
}
$employee = $user->employee;
$location = $employee?->location;
return [
$employee?->location_id,
(bool) optional($location)->is_consignment,
];
}
);
// return $this->hasOne(Discount::class,DB::raw("item_reference.item_id"))
// ->leftJoin('discount_items', 'discount_items.discount_id', '=', 'discounts.id')
// ->leftJoin('item_reference', 'item_reference.id', '=', 'discount_items.item_reference_id')
// ->where('discounts.type', 'discount')
// ->where(function ($query) {
// $query->whereNull('valid_at')
// ->orWhere('valid_at', '<=', now());
// })
// ->where(function ($query) {
// $query->whereNull('expired_at')
// ->orWhere('expired_at', '>=', now());
// })
// ->where(function ($query) use ($location_id, $is_consignment) {
// if (!$is_consignment) {
// $query->whereNull('discounts.location_id');
// }
// if ($location_id) {
// $query->orWhere('discounts.location_id', $location_id);
// }
// })
// ->orderByDesc('discounts.created_at')
// ->select([
// 'item_reference.item_id',
// 'discount_items.price',
// ]);
return $this->hasOne(DiscountItem::class, 'item_reference_id', 'id')
->leftJoin('discounts', 'discounts.id', '=', 'discount_items.discount_id')
->leftJoin('item_reference', 'item_reference.id', '=', 'discount_items.item_reference_id')
->where('discounts.type', 'discount')
->where(function ($q) {
$q->whereNull('discounts.valid_at')
->orWhere('discounts.valid_at', '<=', now());
})
->where(function ($q) {
$q->whereNull('discounts.expired_at')
->orWhere('discounts.expired_at', '>=', now());
})
->where(function ($q) use ($location_id, $is_consignment) {
if (! $is_consignment) {
$q->whereNull('discounts.location_id');
}
if ($location_id) {
$q->orWhere('discounts.location_id', $location_id);
}
})
->orderByDesc('discounts.created_at')
->select([
'item_reference.item_id',
'discount_items.price',
]);
}
public function price()
{
$user = auth()->user();
$userId = $user ? $user->id : 0;
[$location_id, $is_consignment] = Cache::remember('employee_user_'.$userId, 60 * 60 * 24, function () use ($user) {
if ($user == null) {
return [10, false];
}
$employee = @$user->employee;
$location_id = @$employee->location_id;
$location = @$employee->location;
$is_consignment = (bool) @$location->is_consignment;
return [$location_id, $is_consignment];
});
return $this->hasOne(Discount::class, 'id', 'item_reference_id')
->leftJoin('discount_items', 'discount_items.discount_id', '=', 'discounts.id')
->where('discounts.type', 'price')
->where(function ($query) {
$query->where('valid_at', '<=', now())
->orWhereNull('valid_at');
})
->where(function ($query) {
$query->where('expired_at', '>', now())
->orWhereNull('expired_at');
})
->where(function ($query) use ($location_id, $is_consignment) {
if (! $is_consignment) {
$query->whereNull('discounts.location_id');
}
if ($location_id) {
$query->orWhere('discounts.location_id', $location_id);
}
})
->orderByDesc('discounts.created_at')
->select([
'discount_items.item_reference_id',
'discount_items.price',
]);
}
public function stock()
{
return $this->hasOne(Stock::class, 'item_id')
->leftJoin('locations', 'locations.id', '=', 'location_id')
->whereNotNull('locations.display_name')
->where('locations.display_name', '<>', '')
->where('quantity', '>', '0')
->groupBy('item_id')
->select('item_id', DB::raw('SUM(quantity) as quantity'));
}
public function attributes()
{
return $this->hasMany(ItemAttribute::class, 'item_id');
}
public function getImageUrlAttribute()
{
$image = $this->images->first()->filename ?? null;
if (str_contains($image, 'http')) {
return $image;
}
return $image ? Storage::disk('wms')->url($image) : null;
}
public function getImageUrlsAttribute()
{
$images = $this->images ?? [];
$imgs = [];
foreach ($images as $img) {
$image = $img->filename;
if (str_contains($image, 'http')) {
$imgs[] = $image;
} else {
$imgs[] = $image ? Storage::disk('wms')->url($image) : null;
}
}
return $imgs;
}
public function conversion_value()
{
$convertion = 1;
if (($this->display_unit != $this->unit) and ($this->display_unit)) {
$convertions = DB::select('select to_qty / from_qty as conv
from item_convertions where from_unit = ? and to_unit = ?',
[$this->display_unit, $this->unit]);
$convertion = max((float) @$convertions[0]->conv, 1);
}
return $convertion;
}
public function getDisplayPriceAttribute()
{
try {
$convertion = $this->conversion_value();
$price = @$this->variants->first()->reference->price->price ?? null;
$price = $price ? $price : $this->net_price;
return (float) $price * $convertion;
} catch (\Exception $e) {
return 0;
}
}
public function getDisplayPriceCurrencyAttribute()
{
return 'Rp ' . number_format($this->display_price, 0, ',', '.');
}
public function getDisplayDiscountPriceAttribute()
{
try {
$convertion = $this->conversion_value();
$discountPrice = @$this->discount->price ?? 0;
return (float) $discountPrice * $convertion;
} catch (\Exception $e) {
return 0;
}
}
public function isWishlist() : bool
{
return $this->hasOne(Wishlist::class, 'item_id', 'id')->where('customer_id', auth()->user()->customer->id)->exists();
}
}

88
app/Models/Location.php Normal file
View File

@ -0,0 +1,88 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Location extends Model
{
use HasFactory, SoftDeletes;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $table = 'locations';
protected $primaryKey = 'id';
protected $fillable = [
'code',
'name',
'address',
'city',
'phone',
'longitude',
'latitude',
'md_id',
'display_name',
'is_consignment',
'postal_code'
];
public function scopeFilter(Builder $query, array $filters)
{
$query->when($filters['search'] ?? false, function ($query, $search) {
return $query
->where('name', 'iLIKE', '%' . $search . '%')
->orWhere('code', 'LIKE', '%' . $search . '%');
});
}
public function md()
{
return $this->belongsTo(Employee::class);
}
public function storeCategory()
{
return $this->hasMany(StoreCapacity::class, "location_id", "id")
->selectRaw("
store_category.id,
store_category.name,
store_capacity.max::int,
COALESCE(b.qty, 0)::int as qty,
COALESCE(b.sku, 0)::int as sku,
(CASE WHEN COALESCE(b.qty, 0) = 0 OR store_capacity.max = 0 THEN 0 ELSE (qty / max * 100) END) as percentage
")
->leftJoin("store_category", "store_capacity.store_category_id", "store_category.id")
->leftJoin(DB::raw("(SELECT
c.store_category_id,
SUM(stocks.quantity) as qty,
count(distinct item_reference.number) as sku
FROM (SELECT item_dimension.no, store_category_map.store_category_id FROM store_category_map
LEFT JOIN item_dimension
ON (store_category_map.category1 = item_dimension.category1 OR store_category_map.category1 is null)
AND (store_category_map.category2 = item_dimension.category2 OR store_category_map.category2 is null)
AND (store_category_map.category3 = item_dimension.category3 OR store_category_map.category3 is null)
AND (store_category_map.category4 = item_dimension.category4 OR store_category_map.category4 is null)
GROUP BY item_dimension.no, store_category_map.store_category_id
) c
LEFT JOIN items
ON items.number = c.no
LEFT JOIN item_reference
ON items.id = item_reference.item_id
LEFT JOIN stocks
ON stocks.item_id = item_reference.item_id AND stocks.item_variant_id = item_reference.item_variant_id
WHERE stocks.location_id = $this->id and stocks.quantity > 0
GROUP BY c.store_category_id
) b"), "store_capacity.store_category_id", "b.store_category_id");
}
}

23
app/Models/LuckyWheel.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LuckyWheel extends Model
{
use HasFactory;
public function prizes(){
return $this->hasMany(LuckyWheelPrize::class);
}
public function gets(){
return $this->hasMany(LuckyWheelGet::class);
}
public function tickets(){
return $this->hasMany(LuckyWheelTicket::class);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LuckyWheelGet extends Model
{
use HasFactory;
protected $fillable = ['prize_id','lucky_wheel_id','voucher_id','nominal','redeem_at'];
public function prize(){
return $this->belongsTo(LuckyWheelPrize::class,'prize_id');
}
public function voucher(){
return $this->belongsTo(Voucher::class,'voucher_id');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
class LuckyWheelPrize extends Model
{
use HasFactory;
protected $fillable = ['name','voucher_event_id'];
public function voucher(){
return $this->belongsTo(Voucher::class);
}
public function voucherEvent(){
return $this->belongsTo(VoucherEvent::class);
}
public function gets(){
return $this->hasMany(LuckyWheelGet::class,"prize_id")
->whereDate("created_at", Carbon::now());
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LuckyWheelTicket extends Model
{
use HasFactory;
protected $fillable = ['customer_id','invoice_id','max_times'];
public function luckyWheel()
{
return $this->belongsTo(LuckyWheel::class);
}
public function customer()
{
return $this->belongsTo(Customer::class);
}
public function prize()
{
return $this->hasOne(LuckyWheelPrize::class,"ticket_id")->with('voucher');
}
public function gets()
{
return $this->hasMany(LuckyWheelGet::class,"ticket_id")->with("prize","voucher")->orderBy("created_at","desc");
}
public function get()
{
return $this->hasOne(LuckyWheelGet::class,"ticket_id")->whereNotNull("redeem_at")->with("voucher","prize");
}
}

40
app/Models/PosCode.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PosCode extends Model
{
use HasFactory;
protected $fillable = [
'code',
'village',
'district',
'regency',
'province',
'latitude',
'longitude',
'elevation',
'timezone',
];
public function provinceRef(){
return $this->belongsTo(Province::class,"provice_id","id");
}
public function cityRef(){
return $this->belongsTo(City::class,"city_id","id");
}
public function districtRef(){
return $this->belongsTo(City::class,"district_id","id");
}
public function subdistrictRef(){
return $this->belongsTo(City::class,"subdistrict_id","id");
}
}

82
app/Models/PosInvoice.php Normal file
View File

@ -0,0 +1,82 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class PosInvoice extends Model
{
use HasFactory;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $fillable = ["location_id","customer_id","sales_id","sales2_id",
"user_id","time","number",
"note","subtotal", "voucher", "discount","tax","total",
"vouchers","canceled_at","canceled_note","canceled_by", "note_internal"];
public function ticket()
{
return $this->hasOne(LuckyWheelTicket::class, 'invoice_id')->with("prize","gets");
}
public function customer(){
return $this->belongsTo(Customer::class);
}
public function sales(){
return $this->belongsTo(Sales::class);
}
public function sales2(){
return $this->belongsTo(Sales::class, 'sales2_id', 'id');
}
public function location(){
return $this->belongsTo(Location::class);
}
public function user(){
return $this->belongsTo(User::class);
}
public function details(){
return $this->hasMany(PosInvoiceDetail::class,"invoice_id")
->orderBy("line_no","asc");
}
public function payments(){
return $this->hasMany(PosInvoicePayment::class,"invoice_id");
}
public function canceledBy(){
return $this->belongsTo(User::class,"canceled_by");
}
public function vouchers(){
return $this->morphMany(Voucher::class,'reference_used');
}
public function discountVouchers(){
return $this->hasMany(PosInvoiceVoucher::class, 'pos_invoice_id');
}
public function getPoint() {
$total = CustomerPoint::where('reference_type', 'App\Models\PosInvoice')
->where('reference_id', $this->id)
->sum('point');
return $total;
}
public function feedback(){
return $this->hasOne(SurveyFeedback::class,"invoice_id");
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PosInvoiceDetail extends Model
{
use HasFactory;
protected $fillable = ['item_reference_id','point', 'invoice_id', 'item_id','item_variant_id','note','qty','unit_price','unit','reference','discount','vat','total','unit_cost','line_no',
'description','item_number','variant_code','invoice_discount', 'line_discount', 'net_price', 'pricelist_discount','serial_number'
];
public function itemReference()
{
return $this->belongsTo(ItemReference::class);
}
public function item()
{
return $this->belongsTo(Items::class);
}
public function itemVariant()
{
return $this->belongsTo(ItemVariant::class);
}
public function invoice()
{
return $this->belongsTo(PosInvoice::class, 'invoice_id');
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PosInvoicePayment extends Model
{
use HasFactory;
protected $fillable = ['method', 'amount', 'bank', 'card_number', 'remarks', 'installment', 'bank_id'];
public function bank_raw()
{
return $this->belongsTo(Bank::class, 'bank_id', 'id');
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PosInvoiceVoucher extends Model
{
use HasFactory;
protected $table = 'pos_invoice_vouchers';
protected $fillable = [
'pos_invoice_id',
'voucher_id',
'nominal'
];
public function invoice()
{
return $this->belongsTo(PosInvoice::class, 'pos_invoice_id');
}
public function voucher()
{
return $this->belongsTo(Voucher::class, 'voucher_id');
}
}

13
app/Models/Province.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Province extends Model
{
use HasFactory;
protected $fillable = ['name'];
}

32
app/Models/Role.php Normal file
View File

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Role extends Model
{
use HasFactory;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $fillable = [
'name'
];
public function permissions(){
return $this->belongsToMany(Permission::class, "role_permission")
->select("id","code","name");
}
public function users(){
return $this->hasMany(User::class,"role_id");
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class RolePermission extends Model
{
use HasFactory;
protected $table = 'role_permission';
protected $primaryKey = null;
public $incrementing = false;
public $timestamps = false;
protected $fillable = [
'role_id',
'permission_id'
];
}

70
app/Models/Sales.php Normal file
View File

@ -0,0 +1,70 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Sales extends Model
{
use HasFactory, SoftDeletes, Sluggable;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $table = 'sales';
protected $primaryKey = 'id';
protected $fillable = [
'id',
'number',
'name',
'phone',
'email',
'address',
'country',
'province_id',
'city_id',
'district_id',
'village_id',
'postal_code',
'customer_group_id',
'location_id',
];
public function sluggable(): array
{
return [
'number' => [
'source' => 'number'
]
];
}
public function scopeFilter(Builder $query, array $filters)
{
$query->when($filters['search'] ?? false, function ($query, $search) {
return $query
->where('name', 'iLIKE', '%' . $search . '%')
->orWhere('phone', 'LIKE', '%' . $search . '%');
});
}
public function locations()
{
return $this->belongsTo(Location::class, 'location_id', 'id');
}
public function customerGroup()
{
return $this->belongsTo(CustomerGroup::class, 'customer_group_id', 'id');
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class SerialNumber extends Model
{
use HasFactory, SoftDeletes;
protected $table = 'sn_batches';
protected $fillable = ['item_reference_id', 'reference_number', 'item_number', 'variant_code',
'description', 'user_id', 'qty','status','closed_at'];
public function details() {
return $this->hasMany(SerialNumberDetail::class, 'sn_batch_id', 'id');
}
public function user() {
return $this->belongsTo(User::class);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class SerialNumberDetail extends Model
{
use HasFactory;
protected $table = 'sn_batch_details';
protected $fillable = ['sn_batch_id', 'number','activated_at','invoice_id','activated_by'];
public function data(){
return $this->belongsTo(SerialNumber::class,"sn_batch_id");
}
public function invoice(){
return $this->belongsTo(PosInvoice::class,"invoice_id");
}
public function activatedBy(){
return $this->belongsTo(User::class,"activated_by");
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class SerialNumberLog extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = ['reference_number','item_number','variant_code','description','brand','activated_at','invoice_no'];
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class StoreCapacity extends Model
{
use HasFactory;
protected $table = 'store_capacity';
protected $fillable = ['store_category_id', 'location_id', 'max'];
public function location() {
return $this->belongsTo(Location::class);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
class StoreCategory extends Model
{
use HasFactory;
protected $table = 'store_category';
protected $fillable = ['name', 'image'];
public function categories()
{
return $this->hasMany(StoreCategoryMap::class);
}
public function locations()
{
return $this->hasMany(StoreCapacity::class);
}
public function items()
{
return $this->hasMany(Items::class, 'category_id', 'id');
}
public function productCount()
{
return $this->items()->count();
}
public function getImageUrlAttribute()
{
return $this->image ? Storage::disk('wms')->url($this->image) : null;
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class StoreCategoryMap extends Model
{
use HasFactory;
protected $table = "store_category_map";
protected $fillable = ['store_category_id', 'category1', 'category2', 'category3', 'category4'];
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Subdistrict extends Model
{
use HasFactory;
protected $fillable = ['district_id', 'name'];
public function district()
{
return $this->belongsTo(District::class, 'district_id', 'id');
}
}

24
app/Models/Survey.php Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Survey extends Model
{
use HasFactory, SoftDeletes;
protected $table = 'survey';
protected $fillable = ['code', 'name', 'title', 'content', 'voucher_event_id'];
public function detail() {
return $this->hasMany(SurveyQuestion::class);
}
public function voucherEvent() {
return $this->belongsTo(VoucherEvent::class);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class SurveyFeedback extends Model
{
use HasFactory;
protected $fillable = ['code','survey_id', 'channel', 'time', 'customer_id', 'invoice_id', 'ip_address', 'agent'];
public function detail() {
return $this->hasMany(SurveyFeedbackDetail::class);
}
public function survey() {
return $this->belongsTo(Survey::class);
}
public function customer() {
return $this->belongsTo(Customer::class);
}
public function invoice() {
return $this->belongsTo(PosInvoice::class);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class SurveyFeedbackDetail extends Model
{
use HasFactory;
protected $fillable = ['survey_question_id', 'survey_feedback_id', 'value', 'description'];
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class SurveyQuestion extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = ['survey_id', 'type', 'data', 'description', 'order'];
public $timestamps = false;
}

124
app/Models/Transaction.php Normal file
View File

@ -0,0 +1,124 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Transaction extends Model
{
use HasFactory, SoftDeletes;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $fillable = [
'number',
'user_id',
'customer_id',
'address_id',
'location_id',
'time',
'note',
'amount',
'shipping_price',
'courier_company',
'courier_type',
'subtotal',
'discount',
'point',
'status',
'waybill_number',
];
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function customer()
{
return $this->belongsTo(Customer::class, 'customer_id', 'id');
}
public function address()
{
return $this->belongsTo(Address::class, 'address_id', 'id');
}
public function vouchers()
{
return $this->morphMany(Voucher::class,'reference_used');
}
public function shipping()
{
return $this->hasOne(TransactionShipping::class);
}
public function location()
{
return $this->belongsTo(Location::class, 'location_id', 'id');
}
public function detailTransaction()
{
return $this->hasMany(TransactionDetail::class, 'transaction_id', 'id');
}
public function details()
{
return $this->hasMany(TransactionDetail::class, 'transaction_id', 'id');
}
public function xendits()
{
return $this->hasMany(TransactionPayment::class, 'transaction_id', 'id')
->where("method_type",XenditLink::class);
}
public function payments()
{
return $this->hasMany(TransactionPayment::class, 'transaction_id', 'id');
}
public function statuses()
{
return $this->hasMany(TransactionStatus::class, 'transaction_id', 'id');
}
public function invoice()
{
return $this->belongsTo(PosInvoice::class, 'invoice_id', 'id');
}
public function getStatusTitleAttribute()
{
return __('order.status.'.strtolower($this->attributes['status']));
}
public function getStatusColorAttribute()
{
return match($this->attributes['status']) {
'wait_payment' => 'warning',
'wait_process' => 'info',
'on_process' => 'primary',
'on_delivery' => 'success',
'closed' => 'success',
'cancelled' => 'danger',
default => 'secondary',
};
}
public function getTotalAttribute()
{
return $this->attributes['subtotal'] + $this->attributes['shipping_price'];
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TransactionDetail extends Model
{
use HasFactory;
protected $table = 'transaction_details';
protected $fillable = [
'transaction_id',
'item_id',
'item_variant_id',
'item_reference_id',
'qty',
'unit',
'unit_price',
'unit_cost',
'point',
'discount',
'total',
];
public function item()
{
return $this->belongsTo(Items::class, 'item_id', 'id');
}
public function reference() {
return $this->belongsTo(ItemReference::class, 'item_reference_id', 'id');
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TransactionPayment extends Model
{
use HasFactory;
protected $table = 'transaction_payments';
protected $fillable = [
'transaction_id',
'amount',
'status',
'method_type',
'method_id'
];
public function transaction()
{
return $this->belongsTo(Transaction::class, 'transaction_id', 'id');
}
public function method()
{
return $this->morphTo();
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TransactionShipping extends Model
{
use HasFactory;
protected $fillable = [
"transaction_id",
"uid",
"tracking_id",
"waybill_id",
"company",
"type",
"driver_name",
"driver_phone",
"driver_photo_url",
"driver_plate_number",
"insurance_amount",
"insurance_fee",
"weight_total",
"price",
"note",
"status",
"origin_address",
"origin_name",
"origin_phone",
"origin_postal_code",
"origin_latitude",
"origin_longitude",
"origin_note",
"destination_address",
"destination_name",
"destination_phone",
"destination_postal_code",
"destination_latitude",
"destination_longitude",
"destination_note",
"shipper_name",
"shipper_phone",
"shipper_email",
];
public function tracks(){
return $this->hasMany(TransactionShippingTrack::class,"transaction_shipping_id");
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TransactionShippingTrack extends Model
{
use HasFactory;
protected $fillable = [
"status",
"note",
"created_at"
];
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TransactionStatus extends Model
{
use HasFactory;
protected $table = 'transaction_status';
protected $fillable = [
'transaction_id',
'user_id',
'status',
'time',
'note',
];
public function transaction()
{
return $this->belongsTo(Transaction::class, 'transaction_id', 'id');
}
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
}

View File

@ -21,6 +21,11 @@ class User extends Authenticatable
'name',
'email',
'password',
'role_id',
'photo',
'fcm_token',
'phone',
'phone_verified_at',
];
/**
@ -45,4 +50,25 @@ class User extends Authenticatable
'password' => 'hashed',
];
}
public function customer()
{
return $this->hasOne(Customer::class);
}
public function addresses()
{
return $this->hasMany(Address::class);
}
public function primary_address()
{
return $this->hasOne(Address::class)->where('is_primary', true);
}
public function getPhotoUrlAttribute()
{
return $this->photo ? (str_contains($this->photo, 'http') ? $this->photo : env('WMS_ASSET_URL') . '/' . $this->photo) : asset('img/photo-placeholder.png');
}
}

13
app/Models/UserDevice.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UserDevice extends Model
{
use HasFactory;
protected $fillable = ['user_id','user_agent','ip_address','last_login_at','device','fcm_token'];
}

37
app/Models/UserOtp.php Normal file
View File

@ -0,0 +1,37 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UserOtp extends Model
{
use HasFactory;
protected $table = 'user_otps';
protected $fillable = [
'otp',
'expired_at',
'user_id',
'user_identity',
];
public function user()
{
return $this->hasOne(User::class, 'id', 'user_id');
}
public function format() : array{
return [
'id' => $this->id,
'otp' => $this->otp,
'expired_at' => $this->expired_at,
'user_id' => $this->user_id,
'user' => $this->user,
];
}
}

81
app/Models/Voucher.php Normal file
View File

@ -0,0 +1,81 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Voucher extends Model
{
use HasFactory;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $table = 'vouchers';
protected $primaryKey = 'id';
protected $fillable = [
'number',
'nominal',
'calculation_type',
'description',
'customer_id',
'expired_at',
'voucher_event_id',
'affiliator_id',
'reference_issued_id',
'item_reference_id',
'reference_issued_type',
'reference_used_id',
'reference_used_type',
'used_at',
'max_nominal',
'percent',
'min_sales'
];
public function scopeFilter(Builder $query, array $filters)
{
$query->when($filters['search'] ?? false, function ($query, $search) {
return $query
->where('number', 'iLIKE', '%' . $search . '%')
->orWhere('nominal', 'LIKE', '%' . $search . '%');
});
}
public function customer() {
return $this->belongsTo(Customer::class, 'customer_id', 'id');
}
public function event(){
return $this->belongsTo(VoucherEvent::class,"voucher_event_id");
}
public function location(){
return $this->belongsTo(Location::class,"location_id");
}
public function affiliator(){
return $this->belongsTo(Affiliator::class);
}
public function referenceIssued(){
return $this->morphTo();
}
public function referenceUsed(){
return $this->morphTo();
}
public function item()
{
return $this->belongsTo(ItemReference::class, 'item_reference_id', 'id');
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class VoucherClaim extends Model
{
use HasFactory;
protected $table = 'voucher_claims';
protected $primaryKey = 'id';
public $incrementing = true;
protected $keyType = 'int';
protected $fillable = [
'time',
'voucher_id',
'user_id',
'claimable_id',
'claimable_type',
'claimer_id',
'claimer_type'
];
public function user() {
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function claimable(){
return $this->morphTo();
}
public function claimer(){
return $this->morphTo();
}
public function voucher(){
return $this->belongsTo(Voucher::class);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class VoucherCoaching extends Model
{
use HasFactory;
protected $fillable = ['code','released_by','released_at',
'name', 'phone', 'email', 'submitted_at', 'scheduled_at', 'scheduled_by',
'schedule_date'
];
public function releasedBy(){
return $this->belongsTo(User::class,"released_by");
}
public function scheduledBy(){
return $this->belongsTo(User::class,"scheduled_by");
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class VoucherEvent extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = ['name', 'referral','nominal','calculation_type','code', 'redeem_point','for_affiliator','start_at','end_at','claim_limit','min_sales'];
public function items(){
return $this->belongsToMany(ItemReference::class,"voucher_items");
}
}

27
app/Models/Wishlist.php Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Wishlist extends Model
{
use HasFactory;
protected $fillable = [
'customer_id',
'item_id',
];
public function customer(): BelongsTo
{
return $this->belongsTo(Customer::class);
}
public function item(): BelongsTo
{
return $this->belongsTo(Items::class);
}
}

34
app/Models/XenditLink.php Normal file
View File

@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class XenditLink extends Model
{
use HasFactory;
protected $fillable = [
"uid",
"invoice_url",
"user_id",
"external_id",
"status",
"amount",
"received_amount",
"expiry_date",
"payment_id",
"paid_amount",
"payment_method",
"bank_code",
"payment_channel",
"payment_destination",
"paid_at"
];
public function payment()
{
return $this->morphOne(TransactionPayment::class,"method");
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Notifications;
use Illuminate\Notifications\Notification;
use Kreait\Firebase\Messaging\CloudMessage;
use App\Models\User;
use App\Models\UserDevice;
class FcmChannel
{
/**
* Send the given notification.
*/
public function send(object $notifiable, Notification $notification): void
{
// $payload = $notification->toFcm($notifiable);
// if ($notifiable->fcm_token != null){
// $this->sendNotification($payload, $notifiable->fcm_token, $notifiable);
// }
// foreach($notifiable->devices as $device){
// if ($device->fcm_token == $notifiable->fcm_token)
// continue;
// $this->sendNotification($payload, $device->fcm_token, $device);
// sleep(0.5);
// }
}
private function sendNotification($payload, $fcm_token, $device){
// if (!$fcm_token)
// return;
// try{
// $payload["token"] = $fcm_token;
// $messaging = app('firebase.messaging');
// $message = CloudMessage::fromArray($payload);
// $result = $messaging->send($message);
// }catch(\Kreait\Firebase\Exception\Messaging\NotFound $e){
// if (get_class($device) == UserDevice::class){
// $device->fcm_token = null;
// $device->save();
// }else if (get_class($device) == User::class){
// $device->fcm_token = null;
// $device->save();
// }
// }catch(\Kreait\Firebase\Exception\Messaging\InvalidMessage $e){
// \Log::info([$fcm_token, $e->getMessage()]);
// }catch(\Exception $e){
// report($e);
// }
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace App\Notifications\Member\Transaction;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use App\Models\Transaction;
use App\Notifications\FcmChannel;
class NewOrder extends Notification implements ShouldQueue
{
use Queueable;
protected $transaction;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(Transaction $transaction)
{
$this->transaction = $transaction;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['database',FcmChannel::class];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
$transaction = $this->transaction;
return [
"title" => "Ada Pesanan baru masuk!",
"body" => "Mohon segera proses pesanan nomor $transaction->number",
"type" => "Info",
"data" => $this->transaction,
"model" => get_class($this->transaction)
];
}
public function toFcm($notifiable)
{
$payload = $this->toArray($notifiable);
return [
"notification" => [
"title" => $payload["title"],
"body" => $payload["body"],
]
];
}
}

Some files were not shown because too many files have changed in this diff Show More