Compare commits
174 Commits
fix/sync-t
...
e577da737b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e577da737b | ||
|
|
05ca927c38 | ||
|
|
fc4b419878 | ||
|
|
53d12d6798 | ||
|
|
809eb85255 | ||
|
|
8a513460bb | ||
|
|
fc74875cce | ||
|
|
beb7d935c9 | ||
|
|
5c4cebd2b3 | ||
|
|
cbe3d00c96 | ||
|
|
65d9247b46 | ||
|
|
63310f2748 | ||
|
|
c6257b79bf | ||
|
|
38493063c4 | ||
|
|
954b2d8716 | ||
|
|
41cfce589b | ||
|
|
8de1b51fea | ||
|
|
fef6ae7522 | ||
|
|
6f1cb4195a | ||
|
|
6a060f5dac | ||
|
|
844fbdfa89 | ||
|
|
e18c0cb3b6 | ||
|
|
0a9d9071e4 | ||
|
|
4b28bebcc2 | ||
|
|
1bcd2023da | ||
|
|
1b084ed485 | ||
|
|
71ca8dc553 | ||
|
|
3cddd271c8 | ||
|
|
e9a70a827c | ||
|
|
2cbc4172da | ||
|
|
d7e9f44b20 | ||
|
|
7c7aa0e2a5 | ||
|
|
0111ab14e1 | ||
|
|
68e9d5eebf | ||
|
|
209ef07f9c | ||
|
|
6896fd62a3 | ||
|
|
b73183becf | ||
|
|
9f9c3758ed | ||
|
|
48a340d684 | ||
|
|
3ff3dc8f17 | ||
|
|
7936eb1dbf | ||
|
|
ec047821a1 | ||
|
|
e0ed007a39 | ||
|
|
bb63ea8084 | ||
|
|
7a19d9f39d | ||
|
|
93af7ab2a1 | ||
|
|
6158903260 | ||
|
|
09e7d41ddc | ||
|
|
2f4ef6cb56 | ||
|
|
4f94e9d8f7 | ||
|
|
fa6a0079dc | ||
|
|
f7497cbec8 | ||
|
|
b5f7bf39b2 | ||
|
|
ef3c9d6fc3 | ||
|
|
1288ab509d | ||
|
|
588e3ad5e2 | ||
|
|
3902a486f7 | ||
|
|
dd1cd72450 | ||
|
|
af05a39a82 | ||
|
|
0abf278aa3 | ||
|
|
c2cb1b99f2 | ||
|
|
7135876ebc | ||
|
|
456eec83dc | ||
|
|
6a22b55a1c | ||
|
|
5aab6fa3d1 | ||
|
|
a1e302a56d | ||
|
|
a7f578ca3d | ||
|
|
c33193d5f0 | ||
|
|
2c7c99bcf1 | ||
|
|
a01b6f5611 | ||
|
|
2f43ebe97e | ||
|
|
e5baf5318f | ||
|
|
b895f61701 | ||
|
|
5dd92aa323 | ||
|
|
7eb5a850c2 | ||
|
|
200b398868 | ||
|
|
ccff82bd22 | ||
|
|
285e89d5d0 | ||
|
|
4c3443c2d6 | ||
|
|
df70a47bd1 | ||
|
|
e71dd7d213 | ||
|
|
f2eb998ac5 | ||
|
|
fc54e20fa4 | ||
|
|
6946fa7074 | ||
|
|
236b6f9bfc | ||
|
|
285ff46c2b | ||
|
|
a8b02afad9 | ||
|
|
a0666e78d2 | ||
|
|
799e409ce2 | ||
|
|
780ba60224 | ||
|
|
baed8cc487 | ||
|
|
e17f5beaf0 | ||
|
|
766e1a430c | ||
|
|
6677c320fc | ||
|
|
9437eb949f | ||
|
|
6f77120c33 | ||
|
|
f8d0573e5c | ||
|
|
ca74d0143f | ||
|
|
34e082c31b | ||
|
|
c4d865bf2b | ||
|
|
a103b38265 | ||
|
|
9aa3d32b6e | ||
|
|
99e2c214b6 | ||
|
|
501a76bc81 | ||
|
|
460267992e | ||
|
|
becc368069 | ||
|
|
2618ac06d0 | ||
|
|
d95676d477 | ||
|
|
7737fee30f | ||
|
|
48293386c7 | ||
|
|
84870b95b1 | ||
|
|
6294d2f950 | ||
|
|
7b70591be5 | ||
|
|
3b67bfd1fb | ||
|
|
45e22096ed | ||
|
|
c7a8d6d249 | ||
|
|
091b1f305e | ||
|
|
e7950e22f2 | ||
|
|
b68641db03 | ||
|
|
fefb85ac7a | ||
|
|
d28a08a24c | ||
|
|
654d2efe19 | ||
|
|
0a080763cd | ||
|
|
f3ef21d1be | ||
|
|
d7bff86741 | ||
|
|
f36f250700 | ||
|
|
2c5da87856 | ||
|
|
a195559c4b | ||
|
|
088f173fec | ||
|
|
eadfddb3a4 | ||
|
|
47a9fb1dfb | ||
|
|
1713e32b67 | ||
|
|
cf998455e0 | ||
|
|
5e1c9f3a2e | ||
|
|
e940b8d6c7 | ||
|
|
5e139bc29c | ||
|
|
f9e1aa1604 | ||
|
|
2e385f80cd | ||
|
|
e2c26e0eff | ||
|
|
ca5b8ad403 | ||
|
|
e97b7eb70d | ||
|
|
0258ca9f04 | ||
|
|
0116147e06 | ||
|
|
7787db02a3 | ||
|
|
e47ab36d5e | ||
|
|
e5db2294b4 | ||
|
|
4db457d7bd | ||
|
|
7a82ad5302 | ||
|
|
b0d4d4c23b | ||
|
|
68ffc1c090 | ||
|
|
a1f4bd7f81 | ||
|
|
238aaba96c | ||
|
|
c7152d9dbe | ||
|
|
2a4b96d0b2 | ||
|
|
dd940ebdb6 | ||
|
|
dce5409248 | ||
|
|
b8f7d7f655 | ||
|
|
65600f1b4f | ||
|
|
b0f15a9221 | ||
|
|
bf55eb228e | ||
|
|
755720bac9 | ||
|
|
098b4c605b | ||
|
|
4632e102eb | ||
|
|
0431945a42 | ||
|
|
fbfa2a37bb | ||
|
|
c529a5d511 | ||
|
|
ff244039ff | ||
|
|
55902042f4 | ||
|
|
c67aa979c2 | ||
|
|
fbaa33ae13 | ||
|
|
9516b6f575 | ||
|
|
ffc08f26cc | ||
|
|
dceb46ab86 | ||
|
|
e0c35b8897 |
0
.editorconfig
Executable file → Normal file
0
.editorconfig
Executable file → Normal file
4
.env.example
Executable file → Normal file
4
.env.example
Executable file → Normal file
@@ -2,7 +2,7 @@ APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_TIMEZONE=UTC
|
||||
APP_TIMEZONE=Asia/Jakarta
|
||||
APP_URL=http://localhost
|
||||
API_URL=http://localhost:8000
|
||||
|
||||
@@ -65,6 +65,8 @@ AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
VITE_APP_HOST=localhost
|
||||
|
||||
API_KEY_GOOGLE="xxxxx"
|
||||
SPREAD_SHEET_ID="xxxxx"
|
||||
OPENAI_API_KEY="xxxxx"
|
||||
0
.gitattributes
vendored
Executable file → Normal file
0
.gitattributes
vendored
Executable file → Normal file
3
.gitignore
vendored
Executable file → Normal file
3
.gitignore
vendored
Executable file → Normal file
@@ -21,3 +21,6 @@ yarn-error.log
|
||||
/.nova
|
||||
/.vscode
|
||||
/.zed
|
||||
/.composer
|
||||
/.config
|
||||
/.npm
|
||||
115
Dockerfile
Normal file
115
Dockerfile
Normal file
@@ -0,0 +1,115 @@
|
||||
FROM node:18 AS node-base
|
||||
|
||||
# Development stage
|
||||
FROM node-base AS development
|
||||
WORKDIR /var/www
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
EXPOSE 5173
|
||||
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
||||
|
||||
# Local development stage for PHP
|
||||
FROM php:8.2-fpm AS local
|
||||
WORKDIR /var/www
|
||||
|
||||
# Install PHP extensions
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git curl zip unzip libpng-dev libonig-dev libxml2-dev libzip-dev \
|
||||
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
|
||||
|
||||
# Override PHP memory limit
|
||||
COPY docker/php/memory-limit.ini /usr/local/etc/php/conf.d/memory-limit.ini
|
||||
|
||||
# Install Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
|
||||
# Install Composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Create www-data user with same UID/GID as host user (1000:1000 is common for first user)
|
||||
RUN usermod -u 1000 www-data && groupmod -g 1000 www-data
|
||||
|
||||
# Copy application files
|
||||
COPY . .
|
||||
|
||||
# Install dependencies
|
||||
RUN composer install
|
||||
|
||||
# Create storage directories and set proper permissions
|
||||
RUN mkdir -p storage/framework/{sessions,views,cache} \
|
||||
&& mkdir -p storage/logs \
|
||||
&& mkdir -p bootstrap/cache \
|
||||
&& chown -R www-data:www-data /var/www \
|
||||
&& chmod -R 775 /var/www/storage \
|
||||
&& chmod -R 775 /var/www/bootstrap/cache
|
||||
|
||||
# Create entrypoint script to fix permissions on startup
|
||||
RUN echo '#!/bin/bash\n\
|
||||
chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache\n\
|
||||
chmod -R 775 /var/www/storage /var/www/bootstrap/cache\n\
|
||||
exec "$@"' > /entrypoint.sh && chmod +x /entrypoint.sh
|
||||
|
||||
USER www-data
|
||||
|
||||
EXPOSE 9000
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["php-fpm"]
|
||||
|
||||
# Production stage
|
||||
FROM php:8.2-fpm AS production
|
||||
WORKDIR /var/www
|
||||
|
||||
# Install PHP extensions
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git curl zip unzip libpng-dev libonig-dev libxml2-dev libzip-dev \
|
||||
supervisor \
|
||||
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
|
||||
|
||||
# Override PHP memory limit
|
||||
COPY docker/php/memory-limit.ini /usr/local/etc/php/conf.d/memory-limit.ini
|
||||
|
||||
# Install Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
|
||||
# Install Composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Copy application files
|
||||
COPY . .
|
||||
|
||||
# Install dependencies
|
||||
RUN composer install --no-dev --optimize-autoloader
|
||||
|
||||
# Install and build frontend assets
|
||||
RUN npm install \
|
||||
&& npm run build \
|
||||
&& ls -la public/build \
|
||||
&& mkdir -p public/assets \
|
||||
&& cp -r public/build/* public/assets/ \
|
||||
&& ls -la public/assets \
|
||||
&& rm -rf node_modules \
|
||||
&& rm -rf public/build
|
||||
|
||||
# Laravel caches
|
||||
RUN php artisan config:clear \
|
||||
&& php artisan route:clear \
|
||||
&& php artisan view:clear \
|
||||
&& php artisan optimize
|
||||
|
||||
RUN php artisan storage:link
|
||||
|
||||
# Create supervisor directories
|
||||
RUN mkdir -p /var/log/supervisor /var/run/supervisor
|
||||
|
||||
# Copy supervisor configuration
|
||||
COPY docker/supervisor/supervisord.conf /etc/supervisor/supervisord.conf
|
||||
COPY docker/supervisor/laravel-production.conf /etc/supervisor/conf.d/laravel-production.conf
|
||||
|
||||
# Permissions
|
||||
RUN chown -R www-data:www-data /var/www && chmod -R 755 /var/www/storage /var/www/public
|
||||
|
||||
EXPOSE 9000
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
|
||||
178
README.md
Executable file → Normal file
178
README.md
Executable file → Normal file
@@ -1,37 +1,167 @@
|
||||
# Usage icon
|
||||
# Sibedas PBG Web
|
||||
|
||||
search or pick icon in <a href="https://icon-sets.iconify.design/mingcute/?keyword=mingcute">here</a>
|
||||
Aplikasi web untuk manajemen data PBG (Pendidikan Berkelanjutan Guru) dengan fitur integrasi Google Sheets.
|
||||
|
||||
# Set up queue for running automatically
|
||||
## 🚀 Quick Start
|
||||
|
||||
- Install Supervisor
|
||||
### Prerequisites
|
||||
|
||||
```
|
||||
sudo apt update && sudo apt install supervisor -y
|
||||
- Docker & Docker Compose
|
||||
- Domain name (untuk production)
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd sibedas-pbg-web
|
||||
./scripts/setup-local.sh
|
||||
# Access: http://localhost:8000
|
||||
```
|
||||
|
||||
- Create Supervisor Config
|
||||
### Production Deployment
|
||||
|
||||
```
|
||||
sudo nano /etc/supervisor/conf.d/laravel-worker.conf
|
||||
```bash
|
||||
# 1. Setup environment
|
||||
cp env.production.example .env
|
||||
nano .env
|
||||
|
||||
[program:laravel-worker]
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
command=php /path-to-your-project/artisan queue:work --tries=3 --timeout=600
|
||||
autostart=true
|
||||
autorestart=true
|
||||
numprocs=1
|
||||
user=www-data
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/supervisor/laravel-worker.log
|
||||
# 2. Deploy with SSL (Recommended)
|
||||
./scripts/setup-reverse-proxy.sh setup
|
||||
|
||||
# 3. Check status
|
||||
./scripts/setup-reverse-proxy.sh status
|
||||
# Access: https://yourdomain.com
|
||||
```
|
||||
|
||||
- Reload Supervisor
|
||||
## 🏗️ Architecture
|
||||
|
||||
### Local Development
|
||||
|
||||
```
|
||||
sudo supervisorctl reread
|
||||
sudo supervisorctl update
|
||||
sudo supervisorctl start laravel-worker
|
||||
sudo supervisorctl restart laravel-worker
|
||||
sudo supervisorctl status
|
||||
Browser → Port 8000 → Nginx → PHP-FPM → MariaDB
|
||||
```
|
||||
|
||||
### Production dengan Reverse Proxy
|
||||
|
||||
```
|
||||
Internet → Reverse Proxy (80/443) → Internal Nginx → PHP-FPM → MariaDB
|
||||
```
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# Domain & SSL
|
||||
DOMAIN=sibedas.yourdomain.com
|
||||
EMAIL=admin@yourdomain.com
|
||||
SSL_TYPE=self-signed # atau letsencrypt
|
||||
|
||||
# Database
|
||||
DB_PASSWORD=your_secure_password
|
||||
MYSQL_ROOT_PASSWORD=your_root_password
|
||||
|
||||
# Laravel
|
||||
APP_KEY=base64:your_app_key_here
|
||||
APP_URL=https://sibedas.yourdomain.com
|
||||
```
|
||||
|
||||
## 🚀 Production Deployment Steps
|
||||
|
||||
### 1. Server Preparation
|
||||
|
||||
```bash
|
||||
# Install Docker & Docker Compose
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh get-docker.sh
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
# Install Docker Compose
|
||||
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
```
|
||||
|
||||
### 2. Clone & Setup
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd sibedas-pbg-web
|
||||
chmod +x scripts/*.sh
|
||||
cp env.production.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
### 3. Deploy
|
||||
|
||||
```bash
|
||||
# Full deployment with SSL
|
||||
./scripts/setup-reverse-proxy.sh setup
|
||||
|
||||
# Or step by step
|
||||
./scripts/deploy-production.sh deploy
|
||||
./scripts/setup-ssl.sh letsencrypt
|
||||
```
|
||||
|
||||
### 4. Verify
|
||||
|
||||
```bash
|
||||
docker-compose ps
|
||||
./scripts/setup-reverse-proxy.sh status
|
||||
curl -f http://localhost/health-check
|
||||
```
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
./scripts/setup-reverse-proxy.sh status
|
||||
|
||||
# View logs
|
||||
docker-compose logs [service]
|
||||
|
||||
# Check SSL certificate
|
||||
./scripts/setup-ssl.sh check
|
||||
```
|
||||
|
||||
## 🛠️ Common Commands
|
||||
|
||||
```bash
|
||||
# Start services
|
||||
docker-compose up -d
|
||||
|
||||
# Stop services
|
||||
docker-compose down
|
||||
|
||||
# Restart services
|
||||
docker-compose restart
|
||||
|
||||
# Execute Laravel commands
|
||||
docker-compose exec app php artisan [command]
|
||||
|
||||
# Backup database
|
||||
docker exec sibedas_db mysqldump -u root -p sibedas > backup.sql
|
||||
```
|
||||
|
||||
## 📁 Scripts
|
||||
|
||||
### Essential Scripts
|
||||
|
||||
- `scripts/setup-reverse-proxy.sh` - Setup lengkap reverse proxy dan SSL
|
||||
- `scripts/deploy-production.sh` - Deployment production
|
||||
- `scripts/setup-ssl.sh` - Setup SSL certificates
|
||||
|
||||
### Optional Scripts
|
||||
|
||||
- `scripts/setup-local.sh` - Setup local development
|
||||
- `scripts/import-sibedas-database.sh` - Manual database import
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
Untuk dokumentasi lengkap, lihat [docs/README.md](docs/README.md)
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
1. Check logs: `docker-compose logs [service]`
|
||||
2. Check status: `./scripts/setup-reverse-proxy.sh status`
|
||||
3. Restart services: `docker-compose restart`
|
||||
4. Review documentation di folder `docs/`
|
||||
|
||||
482
app/Console/Commands/AssignSpatialPlanningsToCalculation.php
Normal file
482
app/Console/Commands/AssignSpatialPlanningsToCalculation.php
Normal file
@@ -0,0 +1,482 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\SpatialPlanning;
|
||||
use App\Models\RetributionCalculation;
|
||||
use App\Models\BuildingType;
|
||||
use App\Services\RetributionCalculatorService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AssignSpatialPlanningsToCalculation extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'spatial-planning:assign-calculations
|
||||
{--force : Force assign even if already has calculation}
|
||||
{--recalculate : Recalculate existing calculations with new values}
|
||||
{--chunk=100 : Process in chunks}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Assign retribution calculations to spatial plannings (recalculate mode recalculates with current values)';
|
||||
|
||||
protected $calculatorService;
|
||||
|
||||
public function __construct(RetributionCalculatorService $calculatorService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->calculatorService = $calculatorService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🏗️ Starting spatial planning calculation assignment...');
|
||||
|
||||
// Get processing options
|
||||
$force = $this->option('force');
|
||||
$recalculate = $this->option('recalculate');
|
||||
$chunkSize = (int) $this->option('chunk');
|
||||
|
||||
// Get spatial plannings query
|
||||
$query = SpatialPlanning::query();
|
||||
|
||||
if ($recalculate) {
|
||||
// Recalculate mode: only process those WITH active calculations
|
||||
$query->whereHas('retributionCalculations', function ($q) {
|
||||
$q->where('is_active', true);
|
||||
});
|
||||
$this->info('🔄 Recalculate mode: Processing spatial plannings with existing calculations');
|
||||
$this->warn('⚠️ NOTE: Recalculate mode will recalculate all existing calculations with current values');
|
||||
} elseif (!$force) {
|
||||
// Normal mode: only process those without active calculations
|
||||
$query->whereDoesntHave('retributionCalculations', function ($q) {
|
||||
$q->where('is_active', true);
|
||||
});
|
||||
$this->info('➕ Normal mode: Processing spatial plannings without calculations');
|
||||
} else {
|
||||
// Force mode: process all
|
||||
$this->info('🔥 Force mode: Processing ALL spatial plannings');
|
||||
}
|
||||
|
||||
$totalRecords = $query->count();
|
||||
|
||||
if ($totalRecords === 0) {
|
||||
$this->warn('No spatial plannings found to process.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->info("Found {$totalRecords} spatial planning(s) to process");
|
||||
|
||||
if (!$this->confirm('Do you want to continue?')) {
|
||||
$this->info('Operation cancelled.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Process in chunks
|
||||
$processed = 0;
|
||||
$errors = 0;
|
||||
$reused = 0;
|
||||
$created = 0;
|
||||
$buildingTypeStats = [];
|
||||
|
||||
$progressBar = $this->output->createProgressBar($totalRecords);
|
||||
$progressBar->start();
|
||||
|
||||
$recalculated = 0;
|
||||
|
||||
$query->chunk($chunkSize, function ($spatialPlannings) use (&$processed, &$errors, &$reused, &$created, &$recalculated, &$buildingTypeStats, $progressBar, $recalculate) {
|
||||
foreach ($spatialPlannings as $spatialPlanning) {
|
||||
try {
|
||||
$result = $this->assignCalculationToSpatialPlanning($spatialPlanning, $recalculate);
|
||||
|
||||
if ($result['reused']) {
|
||||
$reused++;
|
||||
} elseif (isset($result['recalculated']) && $result['recalculated']) {
|
||||
$recalculated++;
|
||||
} else {
|
||||
$created++;
|
||||
}
|
||||
|
||||
// Track building type statistics
|
||||
$buildingTypeName = $result['building_type_name'] ?? 'Unknown';
|
||||
if (!isset($buildingTypeStats[$buildingTypeName])) {
|
||||
$buildingTypeStats[$buildingTypeName] = 0;
|
||||
}
|
||||
$buildingTypeStats[$buildingTypeName]++;
|
||||
|
||||
$processed++;
|
||||
} catch (\Exception $e) {
|
||||
$errors++;
|
||||
$this->error("Error processing ID {$spatialPlanning->id}: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$progressBar->advance();
|
||||
}
|
||||
});
|
||||
|
||||
$progressBar->finish();
|
||||
|
||||
// Show summary
|
||||
$this->newLine(2);
|
||||
$this->info('✅ Assignment completed!');
|
||||
|
||||
if ($recalculate) {
|
||||
$this->table(
|
||||
['Metric', 'Count'],
|
||||
[
|
||||
['Total Processed', $processed],
|
||||
['Recalculated (Changed)', $recalculated],
|
||||
['Unchanged', $reused],
|
||||
['Errors', $errors],
|
||||
]
|
||||
);
|
||||
$this->info('📊 Recalculate mode recalculated all existing calculations with current values');
|
||||
} else {
|
||||
$this->table(
|
||||
['Metric', 'Count'],
|
||||
[
|
||||
['Total Processed', $processed],
|
||||
['Calculations Created', $created],
|
||||
['Calculations Reused', $reused],
|
||||
['Errors', $errors],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Show building type statistics
|
||||
if (!empty($buildingTypeStats)) {
|
||||
$this->newLine();
|
||||
$this->info('📊 Building Type Distribution:');
|
||||
$statsRows = [];
|
||||
arsort($buildingTypeStats); // Sort by count descending
|
||||
foreach ($buildingTypeStats as $typeName => $count) {
|
||||
$percentage = round(($count / $processed) * 100, 1);
|
||||
$statsRows[] = [$typeName, $count, $percentage . '%'];
|
||||
}
|
||||
$this->table(['Building Type', 'Count', 'Percentage'], $statsRows);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign calculation to a spatial planning
|
||||
*/
|
||||
private function assignCalculationToSpatialPlanning(SpatialPlanning $spatialPlanning, bool $recalculate = false): array
|
||||
{
|
||||
// 1. Detect building type
|
||||
$buildingType = $this->detectBuildingType($spatialPlanning->building_function);
|
||||
|
||||
// 2. Get calculation parameters (round to 2 decimal places)
|
||||
$floorNumber = $spatialPlanning->number_of_floors ?: 1;
|
||||
$buildingArea = round($spatialPlanning->getCalculationArea(), 2);
|
||||
|
||||
if ($buildingArea <= 0) {
|
||||
throw new \Exception("Invalid building area: {$buildingArea}");
|
||||
}
|
||||
|
||||
$reused = false;
|
||||
$isRecalculated = false;
|
||||
|
||||
if ($recalculate) {
|
||||
// Recalculate mode: Always create new calculation
|
||||
$calculationResult = $this->performCalculation($spatialPlanning, $buildingType, true);
|
||||
|
||||
// Check if spatial planning has existing active calculation
|
||||
$currentActiveCalculation = $spatialPlanning->activeRetributionCalculation;
|
||||
|
||||
if ($currentActiveCalculation) {
|
||||
$oldAmount = $currentActiveCalculation->retributionCalculation->retribution_amount;
|
||||
$oldArea = $currentActiveCalculation->retributionCalculation->building_area;
|
||||
$newAmount = $calculationResult['amount'];
|
||||
|
||||
// Check if there's a significant difference (more than 1 rupiah)
|
||||
if (abs($oldAmount - $newAmount) > 1) {
|
||||
// Create new calculation
|
||||
$calculation = RetributionCalculation::create([
|
||||
'building_type_id' => $buildingType->id,
|
||||
'floor_number' => $floorNumber,
|
||||
'building_area' => $buildingArea,
|
||||
'retribution_amount' => $calculationResult['amount'],
|
||||
'calculation_detail' => $calculationResult['detail'],
|
||||
]);
|
||||
|
||||
// Assign new calculation
|
||||
$spatialPlanning->assignRetributionCalculation(
|
||||
$calculation,
|
||||
"Recalculated: Original area {$oldArea}m² → New area {$buildingArea}m², Amount {$oldAmount}→{$newAmount}"
|
||||
);
|
||||
|
||||
$isRecalculated = true;
|
||||
} else {
|
||||
// No significant difference, keep existing
|
||||
$calculation = $currentActiveCalculation->retributionCalculation;
|
||||
$reused = true;
|
||||
}
|
||||
} else {
|
||||
// No existing calculation, create new
|
||||
$calculation = RetributionCalculation::create([
|
||||
'building_type_id' => $buildingType->id,
|
||||
'floor_number' => $floorNumber,
|
||||
'building_area' => $buildingArea,
|
||||
'retribution_amount' => $calculationResult['amount'],
|
||||
'calculation_detail' => $calculationResult['detail'],
|
||||
]);
|
||||
|
||||
$spatialPlanning->assignRetributionCalculation(
|
||||
$calculation,
|
||||
'Recalculated (new calculation with current values)'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Normal mode: Check if calculation already exists with same parameters
|
||||
$existingCalculation = RetributionCalculation::where([
|
||||
'building_type_id' => $buildingType->id,
|
||||
'floor_number' => $floorNumber,
|
||||
])
|
||||
->whereBetween('building_area', [
|
||||
$buildingArea * 0.99, // 1% tolerance
|
||||
$buildingArea * 1.01
|
||||
])
|
||||
->first();
|
||||
|
||||
if ($existingCalculation) {
|
||||
// Reuse existing calculation
|
||||
$calculation = $existingCalculation;
|
||||
$reused = true;
|
||||
} else {
|
||||
// Create new calculation
|
||||
$calculationResult = $this->performCalculation($spatialPlanning, $buildingType, false);
|
||||
|
||||
$calculation = RetributionCalculation::create([
|
||||
'building_type_id' => $buildingType->id,
|
||||
'floor_number' => $floorNumber,
|
||||
'building_area' => $buildingArea,
|
||||
'retribution_amount' => $calculationResult['amount'],
|
||||
'calculation_detail' => $calculationResult['detail'],
|
||||
]);
|
||||
}
|
||||
|
||||
// Assign to spatial planning
|
||||
$spatialPlanning->assignRetributionCalculation(
|
||||
$calculation,
|
||||
$reused ? 'Auto-assigned (reused calculation)' : 'Auto-assigned (new calculation)'
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'calculation' => $calculation,
|
||||
'reused' => $reused,
|
||||
'recalculated' => $isRecalculated,
|
||||
'building_type_name' => $buildingType->name,
|
||||
'building_type_code' => $buildingType->code,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect building type based on building function using database
|
||||
*/
|
||||
private function detectBuildingType(string $buildingFunction = null): BuildingType
|
||||
{
|
||||
$function = strtolower($buildingFunction ?? '');
|
||||
|
||||
// Mapping building functions to building type codes from database
|
||||
$mappings = [
|
||||
// Religious
|
||||
'masjid' => 'KEAGAMAAN',
|
||||
'gereja' => 'KEAGAMAAN',
|
||||
'vihara' => 'KEAGAMAAN',
|
||||
'pura' => 'KEAGAMAAN',
|
||||
'keagamaan' => 'KEAGAMAAN',
|
||||
'religious' => 'KEAGAMAAN',
|
||||
|
||||
// Residential/Housing
|
||||
'rumah' => 'HUN_SEDH', // Default to simple housing
|
||||
'perumahan' => 'HUN_SEDH',
|
||||
'hunian' => 'HUN_SEDH',
|
||||
'residential' => 'HUN_SEDH',
|
||||
'tinggal' => 'HUN_SEDH',
|
||||
'mbr' => 'MBR', // Specifically for MBR
|
||||
'masyarakat berpenghasilan rendah' => 'MBR',
|
||||
|
||||
// Commercial/Business - default to UMKM
|
||||
'toko' => 'UMKM',
|
||||
'warung' => 'UMKM',
|
||||
'perdagangan' => 'UMKM',
|
||||
'dagang' => 'UMKM',
|
||||
'usaha' => 'UMKM',
|
||||
'komersial' => 'UMKM',
|
||||
'commercial' => 'UMKM',
|
||||
'pasar' => 'UMKM',
|
||||
'kios' => 'UMKM',
|
||||
|
||||
// Large commercial
|
||||
'mall' => 'USH_BESAR',
|
||||
'plaza' => 'USH_BESAR',
|
||||
'supermarket' => 'USH_BESAR',
|
||||
'department' => 'USH_BESAR',
|
||||
'hotel' => 'USH_BESAR',
|
||||
'resort' => 'USH_BESAR',
|
||||
|
||||
// Office
|
||||
'kantor' => 'UMKM', // Can be UMKM or USH_BESAR depending on size
|
||||
'perkantoran' => 'UMKM',
|
||||
'office' => 'UMKM',
|
||||
|
||||
// Industry (usually big business)
|
||||
'industri' => 'USH_BESAR',
|
||||
'pabrik' => 'USH_BESAR',
|
||||
'gudang' => 'USH_BESAR',
|
||||
'warehouse' => 'USH_BESAR',
|
||||
'manufacturing' => 'USH_BESAR',
|
||||
|
||||
// Social/Cultural
|
||||
'sekolah' => 'SOSBUDAYA',
|
||||
'pendidikan' => 'SOSBUDAYA',
|
||||
'universitas' => 'SOSBUDAYA',
|
||||
'kampus' => 'SOSBUDAYA',
|
||||
'rumah sakit' => 'SOSBUDAYA',
|
||||
'klinik' => 'SOSBUDAYA',
|
||||
'kesehatan' => 'SOSBUDAYA',
|
||||
'puskesmas' => 'SOSBUDAYA',
|
||||
'museum' => 'SOSBUDAYA',
|
||||
'perpustakaan' => 'SOSBUDAYA',
|
||||
'gedung olahraga' => 'SOSBUDAYA',
|
||||
|
||||
// Mixed use
|
||||
'campuran' => 'CAMP_KECIL', // Default to small mixed
|
||||
'mixed' => 'CAMP_KECIL',
|
||||
];
|
||||
|
||||
// Try to match building function
|
||||
$detectedCode = null;
|
||||
foreach ($mappings as $keyword => $code) {
|
||||
if (str_contains($function, $keyword)) {
|
||||
$detectedCode = $code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find building type in database by code
|
||||
if ($detectedCode) {
|
||||
$buildingType = BuildingType::where('code', $detectedCode)
|
||||
->whereHas('indices') // Only types with indices
|
||||
->first();
|
||||
|
||||
if ($buildingType) {
|
||||
return $buildingType;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to "UMKM" type if not detected (most common business type)
|
||||
$defaultType = BuildingType::where('code', 'UMKM')
|
||||
->whereHas('indices')
|
||||
->first();
|
||||
|
||||
if ($defaultType) {
|
||||
return $defaultType;
|
||||
}
|
||||
|
||||
// Fallback to any available type with indices
|
||||
$fallbackType = BuildingType::whereHas('indices')
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
|
||||
if (!$fallbackType) {
|
||||
throw new \Exception('No building types with indices found in database. Please run: php artisan db:seed --class=RetributionDataSeeder');
|
||||
}
|
||||
|
||||
return $fallbackType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform calculation using RetributionCalculatorService
|
||||
*/
|
||||
private function performCalculation(SpatialPlanning $spatialPlanning, BuildingType $buildingType, bool $recalculate = false): array
|
||||
{
|
||||
// Round area to 2 decimal places to match database storage format
|
||||
$buildingArea = round($spatialPlanning->getCalculationArea(), 2);
|
||||
|
||||
// For recalculate mode, use the current area without any adjustment
|
||||
if ($recalculate) {
|
||||
$this->info("Recalculate mode: Using current area {$buildingArea}m²");
|
||||
}
|
||||
|
||||
$floorNumber = $spatialPlanning->number_of_floors ?: 1;
|
||||
|
||||
try {
|
||||
// Use the same calculation service as TestRetributionCalculation
|
||||
$result = $this->calculatorService->calculate(
|
||||
$buildingType->id,
|
||||
$floorNumber,
|
||||
$buildingArea,
|
||||
false // Don't save to database, we'll handle that separately
|
||||
);
|
||||
|
||||
return [
|
||||
'amount' => $result['total_retribution'],
|
||||
'detail' => [
|
||||
'building_type_id' => $buildingType->id,
|
||||
'building_type_name' => $buildingType->name,
|
||||
'building_type_code' => $buildingType->code,
|
||||
'coefficient' => $result['indices']['coefficient'],
|
||||
'ip_permanent' => $result['indices']['ip_permanent'],
|
||||
'ip_complexity' => $result['indices']['ip_complexity'],
|
||||
'locality_index' => $result['indices']['locality_index'],
|
||||
'height_index' => $result['input_parameters']['height_index'],
|
||||
'infrastructure_factor' => $result['indices']['infrastructure_factor'],
|
||||
'building_area' => $buildingArea,
|
||||
'floor_number' => $floorNumber,
|
||||
'building_function' => $spatialPlanning->building_function,
|
||||
'calculation_steps' => $result['calculation_detail'],
|
||||
'base_value' => $result['input_parameters']['base_value'],
|
||||
'is_free' => $buildingType->is_free,
|
||||
'calculation_date' => now()->toDateTimeString(),
|
||||
'total' => $result['total_retribution'],
|
||||
'is_recalculated' => $recalculate,
|
||||
]
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Fallback to basic calculation if service fails
|
||||
$this->warn("Calculation service failed for {$spatialPlanning->name}: {$e->getMessage()}. Using fallback calculation.");
|
||||
|
||||
// Basic fallback calculation
|
||||
$totalAmount = $buildingType->is_free ? 0 : ($buildingArea * 50000);
|
||||
|
||||
// For recalculate mode in fallback, use current amount without adjustment
|
||||
if ($recalculate) {
|
||||
$this->warn("Fallback recalculate: Using current amount Rp{$totalAmount}");
|
||||
}
|
||||
|
||||
return [
|
||||
'amount' => $totalAmount,
|
||||
'detail' => [
|
||||
'building_type_id' => $buildingType->id,
|
||||
'building_type_name' => $buildingType->name,
|
||||
'building_type_code' => $buildingType->code,
|
||||
'building_area' => $buildingArea,
|
||||
'floor_number' => $floorNumber,
|
||||
'building_function' => $spatialPlanning->building_function,
|
||||
'calculation_method' => 'fallback',
|
||||
'error_message' => $e->getMessage(),
|
||||
'is_free' => $buildingType->is_free,
|
||||
'calculation_date' => now()->toDateTimeString(),
|
||||
'total' => $totalAmount,
|
||||
'is_recalculated' => $recalculate,
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\SyncronizeSIMBG;
|
||||
use App\Services\ServiceSIMBG;
|
||||
use Illuminate\Console\Command;
|
||||
use \Illuminate\Support\Facades\Log;
|
||||
|
||||
class ExecuteScraping extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:execute-scraping';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Execure scraping service daily every 12 pm';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
|
||||
private $service_simbg;
|
||||
|
||||
public function __construct(){
|
||||
parent::__construct();
|
||||
}
|
||||
public function handle()
|
||||
{
|
||||
SyncronizeSIMBG::dispatch();
|
||||
Log::info("running scheduler daily scraping");
|
||||
}
|
||||
}
|
||||
790
app/Console/Commands/InjectSpatialPlanningsData.php
Normal file
790
app/Console/Commands/InjectSpatialPlanningsData.php
Normal file
@@ -0,0 +1,790 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\SpatialPlanning;
|
||||
use Illuminate\Console\Command;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Exception;
|
||||
|
||||
class InjectSpatialPlanningsData extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'spatial-planning:inject
|
||||
{--file=storage/app/public/templates/2025.xlsx : Path to Excel file}
|
||||
{--sheet=0 : Sheet index to read from}
|
||||
{--dry-run : Run without actually inserting data}
|
||||
{--debug : Show Excel content for debugging}
|
||||
{--truncate : Clear existing data before import}
|
||||
{--no-truncate : Skip truncation (keep existing data)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Inject spatial planning data from Excel file with BCR area calculation';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$filePath = $this->option('file');
|
||||
$sheetIndex = (int) $this->option('sheet');
|
||||
$isDryRun = $this->option('dry-run');
|
||||
$isDebug = $this->option('debug');
|
||||
$shouldTruncate = $this->option('truncate');
|
||||
$noTruncate = $this->option('no-truncate');
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
$this->error("File not found: {$filePath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->info("Reading Excel file: {$filePath}");
|
||||
$this->info("Sheet index: {$sheetIndex}");
|
||||
|
||||
if ($isDryRun) {
|
||||
$this->warn("DRY RUN MODE - No data will be inserted");
|
||||
}
|
||||
|
||||
// Check existing data
|
||||
$existingCount = DB::table('spatial_plannings')->count();
|
||||
if ($existingCount > 0) {
|
||||
$this->info("Found {$existingCount} existing spatial planning records");
|
||||
} else {
|
||||
$this->info('No existing spatial planning data found');
|
||||
}
|
||||
|
||||
// Handle truncation logic
|
||||
$willTruncate = false;
|
||||
|
||||
if ($shouldTruncate) {
|
||||
$willTruncate = true;
|
||||
$this->info('Truncation requested via --truncate option');
|
||||
} elseif ($noTruncate) {
|
||||
$willTruncate = false;
|
||||
$this->info('Truncation skipped via --no-truncate option');
|
||||
} else {
|
||||
// Default behavior: ask user if not in dry run mode
|
||||
if (!$isDryRun) {
|
||||
$willTruncate = $this->confirm('Do you want to clear existing spatial planning data before import?');
|
||||
} else {
|
||||
$willTruncate = false;
|
||||
$this->info('DRY RUN MODE - Truncation will be skipped');
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm truncation if not in dry run mode and truncation is requested
|
||||
if ($willTruncate && !$isDryRun) {
|
||||
if (!$this->confirm('This will delete all existing spatial planning data and related retribution calculations. Continue?')) {
|
||||
$this->info('Operation cancelled.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate all related data properly
|
||||
if ($willTruncate && !$isDryRun) {
|
||||
$this->info('Truncating spatial planning data and related retribution calculations...');
|
||||
|
||||
try {
|
||||
// Disable foreign key checks for safe truncation
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
|
||||
|
||||
// 1. Delete calculable retributions for spatial plannings (polymorphic relationship)
|
||||
$deletedCalculableRetributions = DB::table('calculable_retributions')
|
||||
->where('calculable_type', 'App\\Models\\SpatialPlanning')
|
||||
->count();
|
||||
|
||||
if ($deletedCalculableRetributions > 0) {
|
||||
DB::table('calculable_retributions')
|
||||
->where('calculable_type', 'App\\Models\\SpatialPlanning')
|
||||
->delete();
|
||||
$this->info("Deleted {$deletedCalculableRetributions} calculable retributions for spatial plannings.");
|
||||
}
|
||||
|
||||
// 2. Truncate spatial plannings table
|
||||
DB::table('spatial_plannings')->truncate();
|
||||
$this->info('Spatial plannings table truncated successfully.');
|
||||
|
||||
// Re-enable foreign key checks
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
|
||||
$this->info('All spatial planning data and related retribution calculations cleared successfully.');
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Make sure to re-enable foreign key checks even on error
|
||||
try {
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
} catch (Exception $fkError) {
|
||||
$this->error('Failed to re-enable foreign key checks: ' . $fkError->getMessage());
|
||||
}
|
||||
|
||||
$this->error('Failed to truncate spatial planning data: ' . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
} elseif ($willTruncate && $isDryRun) {
|
||||
$this->info('DRY RUN MODE - Would truncate spatial planning data and related retribution calculations');
|
||||
} else {
|
||||
$this->info('Keeping existing data (no truncation)');
|
||||
}
|
||||
|
||||
$spreadsheet = IOFactory::load($filePath);
|
||||
$worksheet = $spreadsheet->getSheet($sheetIndex);
|
||||
$rows = $worksheet->toArray(null, true, true, true);
|
||||
|
||||
if ($isDebug) {
|
||||
$this->info("=== EXCEL CONTENT DEBUG ===");
|
||||
foreach (array_slice($rows, 0, 20) as $index => $row) {
|
||||
if (!empty(array_filter($row))) {
|
||||
$this->line("Row $index: " . json_encode($row));
|
||||
}
|
||||
}
|
||||
$this->info("=== END DEBUG ===");
|
||||
}
|
||||
|
||||
// Find BCR percentages from last rows (columns D and E)
|
||||
$bcrPercentages = $this->findBcrPercentages($rows);
|
||||
$this->info("Found BCR Percentages: " . json_encode($bcrPercentages));
|
||||
|
||||
// Process data by sections
|
||||
$sections = $this->processSections($rows, $bcrPercentages, $isDebug);
|
||||
|
||||
$this->info("Found " . count($sections) . " sections");
|
||||
|
||||
$totalInserted = 0;
|
||||
foreach ($sections as $sectionIndex => $section) {
|
||||
$this->info("Processing Section " . ($sectionIndex + 1) . ": " . $section['applicant_name']);
|
||||
|
||||
// Gudang/pergudangan keywords successfully added to Fungsi Usaha classification
|
||||
|
||||
if (!$isDryRun) {
|
||||
$inserted = $this->insertSpatialPlanningData($section);
|
||||
$totalInserted += $inserted;
|
||||
$this->info("Inserted {$inserted} record for this section");
|
||||
} else {
|
||||
$this->info("Would insert 1 record for this section");
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isDryRun) {
|
||||
$this->info("Successfully inserted {$totalInserted} spatial planning records");
|
||||
|
||||
// Show summary of what was done
|
||||
$finalCount = DB::table('spatial_plannings')->count();
|
||||
$this->info("Final spatial planning records count: {$finalCount}");
|
||||
|
||||
if ($willTruncate) {
|
||||
$this->info("✅ Data import completed with truncation");
|
||||
} else {
|
||||
$this->info("✅ Data import completed (existing data preserved)");
|
||||
}
|
||||
} else {
|
||||
$this->info("Dry run completed. Total records that would be inserted: " . count($sections));
|
||||
if ($willTruncate) {
|
||||
$this->info("Would truncate existing data before import");
|
||||
} else {
|
||||
$this->info("Would preserve existing data during import");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
} catch (Exception $e) {
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
Log::error("InjectSpatialPlanningsData failed", ['error' => $e->getMessage()]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find BCR percentages from last rows in columns D and E
|
||||
*/
|
||||
private function findBcrPercentages(array $rows): array
|
||||
{
|
||||
$bcrPercentages = [];
|
||||
|
||||
// Look for BCR percentages in the last few rows
|
||||
$totalRows = count($rows);
|
||||
$searchRows = max(1, $totalRows - 10); // Search last 10 rows
|
||||
|
||||
for ($i = $totalRows; $i >= $searchRows; $i--) {
|
||||
if (isset($rows[$i]['D']) && isset($rows[$i]['E'])) {
|
||||
$valueD = $this->cleanNumericValue($rows[$i]['D']);
|
||||
$valueE = $this->cleanNumericValue($rows[$i]['E']);
|
||||
|
||||
// Check if these look like percentages (between 0 and 100)
|
||||
if ($valueD > 0 && $valueD <= 100 && $valueE > 0 && $valueE <= 100) {
|
||||
$bcrPercentages['D'] = $valueD;
|
||||
$bcrPercentages['E'] = $valueE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default values if not found
|
||||
if (empty($bcrPercentages)) {
|
||||
$bcrPercentages = ['D' => 60, 'E' => 40]; // Default BCR percentages
|
||||
}
|
||||
|
||||
return $bcrPercentages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process data by sections (each applicant)
|
||||
*/
|
||||
private function processSections(array $rows, array $bcrPercentages, bool $isDebug): array
|
||||
{
|
||||
$sections = [];
|
||||
$currentSection = null;
|
||||
$currentSectionNumber = null;
|
||||
$sectionData = [];
|
||||
|
||||
foreach ($rows as $rowIndex => $row) {
|
||||
// Skip empty rows
|
||||
if (empty(array_filter($row))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($isDebug) {
|
||||
$this->line("Checking row $rowIndex: " . substr(json_encode($row), 0, 100) . "...");
|
||||
}
|
||||
|
||||
// Check if this is a new section (applicant)
|
||||
if ($this->isNewSection($row)) {
|
||||
if ($isDebug) {
|
||||
$this->info("Found new section at row $rowIndex");
|
||||
}
|
||||
|
||||
// Save previous section if exists
|
||||
if ($currentSection && !empty($sectionData)) {
|
||||
$sections[] = [
|
||||
'applicant_name' => $currentSection,
|
||||
'section_number' => $currentSectionNumber,
|
||||
'data' => $sectionData
|
||||
];
|
||||
if ($isDebug) {
|
||||
$this->info("Saved section: $currentSection with " . count($sectionData) . " data rows");
|
||||
}
|
||||
}
|
||||
|
||||
// Start new section
|
||||
$currentSectionNumber = trim($row['A'] ?? ''); // Store section number
|
||||
$currentSection = $this->extractApplicantName($row);
|
||||
$sectionData = [];
|
||||
|
||||
// Also process the header row itself for F, G, H data
|
||||
$headerRow = $this->processDataRow($row, $bcrPercentages);
|
||||
if ($headerRow) {
|
||||
$sectionData[] = $headerRow;
|
||||
}
|
||||
|
||||
if ($isDebug) {
|
||||
$this->info("Starting new section: $currentSection");
|
||||
$this->line(" Header F: " . ($row['F'] ?? 'null'));
|
||||
$this->line(" Header G: " . ($row['G'] ?? 'null'));
|
||||
$this->line(" Header H: " . ($row['H'] ?? 'null'));
|
||||
}
|
||||
} elseif ($currentSection && $this->isDataRow($row)) {
|
||||
if ($isDebug) {
|
||||
$this->line("Found data row for section: $currentSection");
|
||||
$this->line(" Column D: " . ($row['D'] ?? 'null'));
|
||||
$this->line(" Column E: " . ($row['E'] ?? 'null'));
|
||||
$this->line(" Column F: " . ($row['F'] ?? 'null'));
|
||||
$this->line(" Column G: " . ($row['G'] ?? 'null'));
|
||||
$this->line(" Column H: " . ($row['H'] ?? 'null'));
|
||||
}
|
||||
|
||||
// Add data to current section
|
||||
$processedRow = $this->processDataRow($row, $bcrPercentages);
|
||||
if ($processedRow) {
|
||||
$sectionData[] = $processedRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add last section
|
||||
if ($currentSection && !empty($sectionData)) {
|
||||
$sections[] = [
|
||||
'applicant_name' => $currentSection,
|
||||
'section_number' => $currentSectionNumber,
|
||||
'data' => $sectionData
|
||||
];
|
||||
}
|
||||
|
||||
return $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if row indicates a new section/applicant
|
||||
*/
|
||||
private function isNewSection(array $row): bool
|
||||
{
|
||||
// Look for patterns that indicate a new applicant
|
||||
$firstCell = trim($row['A'] ?? '');
|
||||
|
||||
// Check for pattern like "55 / 1565", "56 / 1543", etc.
|
||||
return !empty($firstCell) && preg_match('/^\d+\s*\/\s*\d+$/', $firstCell);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract applicant name from section header
|
||||
*/
|
||||
private function extractApplicantName(array $row): string
|
||||
{
|
||||
// Row A contains number like "55 / 1565", Row B contains name and phone
|
||||
$numberPart = trim($row['A'] ?? '');
|
||||
$namePart = trim($row['B'] ?? '');
|
||||
|
||||
// Extract name from column B (remove phone number part)
|
||||
if (!empty($namePart)) {
|
||||
// Remove phone number pattern "No Telpon : xxxxx"
|
||||
$name = preg_replace('/\s*No Telpon\s*:\s*[\d\s\-\+\(\)]+.*$/i', '', $namePart);
|
||||
$name = trim($name);
|
||||
|
||||
return !empty($name) ? $name : $numberPart;
|
||||
}
|
||||
|
||||
return $numberPart ?: 'Unknown Applicant';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if row contains data
|
||||
*/
|
||||
private function isDataRow(array $row): bool
|
||||
{
|
||||
// Check if row has data we're interested in
|
||||
$columnD = trim($row['D'] ?? '');
|
||||
$columnE = trim($row['E'] ?? '');
|
||||
$columnF = trim($row['F'] ?? '');
|
||||
$columnG = trim($row['G'] ?? '');
|
||||
$columnH = trim($row['H'] ?? '');
|
||||
|
||||
// Look for important data patterns in column D
|
||||
$importantPatterns = [
|
||||
'A. Total luas lahan',
|
||||
'Total luas lahan',
|
||||
'Total Luas Lahan',
|
||||
'BCR Kawasan',
|
||||
'E. BCR Kawasan',
|
||||
'D. BCR Kawasan',
|
||||
'KWT',
|
||||
'Total KWT',
|
||||
'KWT Perumahan',
|
||||
'D. KWT Perumahan',
|
||||
'E. KWT Perumahan',
|
||||
'BCR',
|
||||
'Koefisien Wilayah Terbangun'
|
||||
];
|
||||
|
||||
foreach ($importantPatterns as $pattern) {
|
||||
if (stripos($columnD, $pattern) !== false && !empty($columnE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for location data
|
||||
if (stripos($columnD, 'Desa') !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if any of the important columns (F, G, H) have data
|
||||
// We want to capture ALL non-empty data in these columns within a section
|
||||
if (!empty($columnF) && trim($columnF) !== '') {
|
||||
return true;
|
||||
}
|
||||
if (!empty($columnG) && trim($columnG) !== '') {
|
||||
return true;
|
||||
}
|
||||
if (!empty($columnH) && trim($columnH) !== '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a data row and calculate area using BCR formula
|
||||
*/
|
||||
private function processDataRow(array $row, array $bcrPercentages): ?array
|
||||
{
|
||||
try {
|
||||
$columnD = trim($row['D'] ?? '');
|
||||
$columnE = trim($row['E'] ?? '');
|
||||
$columnF = trim($row['F'] ?? '');
|
||||
$columnG = trim($row['G'] ?? '');
|
||||
$columnH = trim($row['H'] ?? '');
|
||||
|
||||
$landArea = 0;
|
||||
$bcrPercentage = $bcrPercentages['D'] ?? 60; // Default BCR percentage
|
||||
$location = '';
|
||||
|
||||
// Extract land area if this is a "Total luas lahan" row
|
||||
if (stripos($columnD, 'Total luas lahan') !== false ||
|
||||
stripos($columnD, 'A. Total luas lahan') !== false) {
|
||||
$landArea = $this->cleanNumericValue($columnE);
|
||||
}
|
||||
|
||||
// Extract BCR percentage if this is a BCR row - comprehensive detection
|
||||
if (stripos($columnD, 'BCR Kawasan') !== false ||
|
||||
stripos($columnD, 'E. BCR Kawasan') !== false ||
|
||||
stripos($columnD, 'D. BCR Kawasan') !== false ||
|
||||
stripos($columnD, 'KWT Perumahan') !== false ||
|
||||
stripos($columnD, 'D. KWT Perumahan') !== false ||
|
||||
stripos($columnD, 'E. KWT Perumahan') !== false ||
|
||||
stripos($columnD, 'KWT') !== false ||
|
||||
(stripos($columnD, 'BCR') !== false && stripos($columnE, '%') !== false) ||
|
||||
stripos($columnD, 'Koefisien Wilayah Terbangun') !== false) {
|
||||
$bcrValue = $this->cleanNumericValue($columnE);
|
||||
if ($bcrValue > 0 && $bcrValue <= 100) {
|
||||
$bcrPercentage = $bcrValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Get location from village/subdistrict info (previous rows in the section)
|
||||
if (stripos($columnD, 'Desa') !== false) {
|
||||
$location = $columnD;
|
||||
}
|
||||
|
||||
// Calculate area: total luas lahan dikali persentase BCR
|
||||
$calculatedArea = $landArea > 0 && $bcrPercentage > 0 ?
|
||||
round($landArea * ($bcrPercentage / 100), 2) : 0;
|
||||
|
||||
return [
|
||||
'data_type' => $columnD,
|
||||
'value' => $columnE,
|
||||
'land_area' => $landArea,
|
||||
'bcr_percentage' => $bcrPercentage,
|
||||
'calculated_area' => $calculatedArea,
|
||||
'location' => $location,
|
||||
'no_tapak' => !empty($columnF) ? $columnF : null,
|
||||
'no_skkl' => !empty($columnG) ? $columnG : null,
|
||||
'no_ukl' => !empty($columnH) ? $columnH : null,
|
||||
'raw_data' => $row
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
Log::warning("Error processing row", ['row' => $row, 'error' => $e->getMessage()]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert spatial planning data
|
||||
*/
|
||||
private function insertSpatialPlanningData(array $section): int
|
||||
{
|
||||
try {
|
||||
// Process section data to extract key values
|
||||
$sectionData = $this->consolidateSectionData($section);
|
||||
|
||||
if (empty($sectionData) || !$sectionData['has_valid_data']) {
|
||||
$this->warn("No valid data found for section: " . $section['applicant_name']);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SpatialPlanning::create([
|
||||
'name' => $section['applicant_name'],
|
||||
'number' => $section['section_number'], // Kolom A - section number
|
||||
'location' => $sectionData['location'], // Column C from header row
|
||||
'land_area' => $sectionData['land_area'],
|
||||
'area' => $sectionData['calculated_area'],
|
||||
'building_function' => $sectionData['building_function'], // Determined from activities
|
||||
'sub_building_function' => $sectionData['sub_building_function'], // UMKM or Usaha Besar
|
||||
'activities' => $sectionData['activities'], // Activities from column D of first row
|
||||
'site_bcr' => $sectionData['bcr_percentage'],
|
||||
'no_tapak' => $sectionData['no_tapak'],
|
||||
'no_skkl' => $sectionData['no_skkl'],
|
||||
'no_ukl' => $sectionData['no_ukl'],
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
]);
|
||||
|
||||
return 1;
|
||||
} catch (Exception $e) {
|
||||
Log::error("Error inserting spatial planning data", [
|
||||
'section' => $section['applicant_name'],
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
$this->warn("Failed to insert record for: " . $section['applicant_name']);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidate section data into a single record
|
||||
*/
|
||||
private function consolidateSectionData(array $section): array
|
||||
{
|
||||
$landArea = 0;
|
||||
$bcrPercentage = 60; // Default from Excel file
|
||||
$location = '';
|
||||
$activities = ''; // Activities from column D of first row
|
||||
$villages = [];
|
||||
$noTapakValues = [];
|
||||
$noSKKLValues = [];
|
||||
$noUKLValues = [];
|
||||
|
||||
// Get activities from first row (header row) column D
|
||||
if (!empty($section['data']) && !empty($section['data'][0]['data_type'])) {
|
||||
$activities = $section['data'][0]['data_type']; // Column D data
|
||||
}
|
||||
|
||||
// Get location from first row (header row) column C (alamat)
|
||||
// We need to get this from raw data since processDataRow doesn't capture column C
|
||||
if (!empty($section['data']) && !empty($section['data'][0]['raw_data']['C'])) {
|
||||
$location = trim($section['data'][0]['raw_data']['C']);
|
||||
}
|
||||
|
||||
foreach ($section['data'] as $dataRow) {
|
||||
// Extract land area
|
||||
if ($dataRow['land_area'] > 0) {
|
||||
$landArea = $dataRow['land_area'];
|
||||
}
|
||||
|
||||
// Extract BCR percentage - prioritize specific BCR from this section
|
||||
// Always use section-specific BCR if found, regardless of value
|
||||
if ($dataRow['bcr_percentage'] > 0 && $dataRow['bcr_percentage'] <= 100) {
|
||||
$bcrPercentage = $dataRow['bcr_percentage'];
|
||||
}
|
||||
|
||||
// Extract additional location info from village/subdistrict data if main location is empty
|
||||
if (empty($location) && !empty($dataRow['location'])) {
|
||||
$villages[] = trim(str_replace('Desa ', '', $dataRow['location']));
|
||||
}
|
||||
|
||||
// Collect no_tapak values
|
||||
if (!empty($dataRow['no_tapak']) && !in_array($dataRow['no_tapak'], $noTapakValues)) {
|
||||
$noTapakValues[] = $dataRow['no_tapak'];
|
||||
}
|
||||
|
||||
// Collect no_skkl values
|
||||
if (!empty($dataRow['no_skkl']) && !in_array($dataRow['no_skkl'], $noSKKLValues)) {
|
||||
$noSKKLValues[] = $dataRow['no_skkl'];
|
||||
}
|
||||
|
||||
// Collect no_ukl values
|
||||
if (!empty($dataRow['no_ukl']) && !in_array($dataRow['no_ukl'], $noUKLValues)) {
|
||||
$noUKLValues[] = $dataRow['no_ukl'];
|
||||
}
|
||||
}
|
||||
|
||||
// Use first village as fallback location if main location is empty
|
||||
if (empty($location)) {
|
||||
$location = !empty($villages) ? $villages[0] : 'Unknown Location';
|
||||
}
|
||||
|
||||
// Merge multiple values with | separator
|
||||
$noTapak = !empty($noTapakValues) ? implode('|', $noTapakValues) : null;
|
||||
$noSKKL = !empty($noSKKLValues) ? implode('|', $noSKKLValues) : null;
|
||||
$noUKL = !empty($noUKLValues) ? implode('|', $noUKLValues) : null;
|
||||
|
||||
// Calculate area using BCR formula: land_area * (bcr_percentage / 100)
|
||||
$calculatedArea = $landArea > 0 && $bcrPercentage > 0 ?
|
||||
round($landArea * ($bcrPercentage / 100), 2) : 0;
|
||||
|
||||
// Determine building_function and sub_building_function based on activities and applicant name
|
||||
$buildingFunction = 'Mixed Development'; // Default
|
||||
$subBuildingFunction = null;
|
||||
|
||||
// Get applicant name for PT validation
|
||||
$applicantName = $section['applicant_name'] ?? '';
|
||||
$isCompany = (strpos($applicantName, 'PT ') === 0 || strpos($applicantName, 'PT.') === 0);
|
||||
|
||||
// Activity-based classification (priority over PT validation for specific activities)
|
||||
if (!empty($activities)) {
|
||||
$activitiesLower = strtolower($activities);
|
||||
|
||||
// 1. FUNGSI KEAGAMAAN
|
||||
if (strpos($activitiesLower, 'masjid') !== false ||
|
||||
strpos($activitiesLower, 'gereja') !== false ||
|
||||
strpos($activitiesLower, 'pura') !== false ||
|
||||
strpos($activitiesLower, 'vihara') !== false ||
|
||||
strpos($activitiesLower, 'klenteng') !== false ||
|
||||
strpos($activitiesLower, 'tempat ibadah') !== false ||
|
||||
strpos($activitiesLower, 'keagamaan') !== false ||
|
||||
strpos($activitiesLower, 'mushola') !== false) {
|
||||
|
||||
$buildingFunction = 'Fungsi Keagamaan';
|
||||
$subBuildingFunction = 'Fungsi Keagamaan';
|
||||
}
|
||||
|
||||
// 2. FUNGSI HUNIAN (PERUMAHAN) - PRIORITY HIGHER THAN PT VALIDATION
|
||||
elseif (strpos($activitiesLower, 'perumahan') !== false ||
|
||||
strpos($activitiesLower, 'perumhan') !== false ||
|
||||
strpos($activitiesLower, 'perum') !== false ||
|
||||
strpos($activitiesLower, 'rumah') !== false ||
|
||||
strpos($activitiesLower, 'hunian') !== false ||
|
||||
strpos($activitiesLower, 'residence') !== false ||
|
||||
strpos($activitiesLower, 'residential') !== false ||
|
||||
strpos($activitiesLower, 'housing') !== false ||
|
||||
strpos($activitiesLower, 'town') !== false) {
|
||||
|
||||
$buildingFunction = 'Fungsi Hunian';
|
||||
|
||||
// Determine housing type based on area and keywords
|
||||
if (strpos($activitiesLower, 'mbr') !== false ||
|
||||
strpos($activitiesLower, 'masyarakat berpenghasilan rendah') !== false ||
|
||||
strpos($activitiesLower, 'sederhana') !== false ||
|
||||
($landArea > 0 && $landArea < 2000)) { // Small area indicates MBR
|
||||
|
||||
$subBuildingFunction = 'Rumah Tinggal Deret (MBR) dan Rumah Tinggal Tunggal (MBR)';
|
||||
}
|
||||
elseif ($landArea > 0 && $landArea < 100) {
|
||||
$subBuildingFunction = 'Sederhana <100';
|
||||
}
|
||||
elseif ($landArea > 0 && $landArea > 100) {
|
||||
$subBuildingFunction = 'Tidak Sederhana >100';
|
||||
}
|
||||
else {
|
||||
$subBuildingFunction = 'Tidak Sederhana >100'; // Default for housing
|
||||
}
|
||||
}
|
||||
|
||||
// 3. FUNGSI SOSIAL BUDAYA
|
||||
elseif (strpos($activitiesLower, 'sekolah') !== false ||
|
||||
strpos($activitiesLower, 'rumah sakit') !== false ||
|
||||
strpos($activitiesLower, 'puskesmas') !== false ||
|
||||
strpos($activitiesLower, 'klinik') !== false ||
|
||||
strpos($activitiesLower, 'universitas') !== false ||
|
||||
strpos($activitiesLower, 'kampus') !== false ||
|
||||
strpos($activitiesLower, 'pendidikan') !== false ||
|
||||
strpos($activitiesLower, 'kesehatan') !== false ||
|
||||
strpos($activitiesLower, 'sosial') !== false ||
|
||||
strpos($activitiesLower, 'budaya') !== false ||
|
||||
strpos($activitiesLower, 'museum') !== false ||
|
||||
strpos($activitiesLower, 'tower') !== false ||
|
||||
strpos($activitiesLower, 'perpustakaan') !== false) {
|
||||
|
||||
$buildingFunction = 'Fungsi Sosial Budaya';
|
||||
$subBuildingFunction = 'Fungsi Sosial Budaya';
|
||||
}
|
||||
|
||||
// 4. FUNGSI USAHA
|
||||
elseif (strpos($activitiesLower, 'perdagangan') !== false ||
|
||||
strpos($activitiesLower, 'dagang') !== false ||
|
||||
strpos($activitiesLower, 'toko') !== false ||
|
||||
strpos($activitiesLower, 'usaha') !== false ||
|
||||
strpos($activitiesLower, 'komersial') !== false ||
|
||||
strpos($activitiesLower, 'pabrik') !== false ||
|
||||
strpos($activitiesLower, 'industri') !== false ||
|
||||
strpos($activitiesLower, 'manufaktur') !== false ||
|
||||
strpos($activitiesLower, 'bisnis') !== false ||
|
||||
strpos($activitiesLower, 'resto') !== false ||
|
||||
strpos($activitiesLower, 'villa') !== false ||
|
||||
strpos($activitiesLower, 'vila') !== false ||
|
||||
strpos($activitiesLower, 'gudang') !== false ||
|
||||
strpos($activitiesLower, 'pergudangan') !== false ||
|
||||
strpos($activitiesLower, 'kolam renang') !== false ||
|
||||
strpos($activitiesLower, 'minimarket') !== false ||
|
||||
strpos($activitiesLower, 'supermarket') !== false ||
|
||||
strpos($activitiesLower, 'perdaganagan') !== false ||
|
||||
strpos($activitiesLower, 'waterpark') !== false ||
|
||||
strpos($activitiesLower, 'pasar') !== false ||
|
||||
strpos($activitiesLower, 'kantor') !== false) {
|
||||
|
||||
$buildingFunction = 'Fungsi Usaha';
|
||||
|
||||
// Determine business size based on land area for non-PT businesses
|
||||
if ($landArea > 0 && $landArea > 500) { // > 500 m² considered large business
|
||||
$subBuildingFunction = 'Usaha Besar (Non-Mikro)';
|
||||
} else {
|
||||
$subBuildingFunction = 'UMKM'; // For small individual businesses
|
||||
}
|
||||
}
|
||||
|
||||
// 5. FUNGSI CAMPURAN
|
||||
elseif (strpos($activitiesLower, 'campuran') !== false ||
|
||||
strpos($activitiesLower, 'mixed') !== false ||
|
||||
strpos($activitiesLower, 'mix') !== false ||
|
||||
strpos($activitiesLower, 'multi') !== false) {
|
||||
|
||||
$buildingFunction = 'Fungsi Campuran (lebih dari 1)';
|
||||
|
||||
// Determine mixed use size
|
||||
if ($landArea > 0 && $landArea > 3000) { // > 3000 m² considered large mixed use
|
||||
$subBuildingFunction = 'Campuran Besar';
|
||||
} else {
|
||||
$subBuildingFunction = 'Campuran Kecil';
|
||||
}
|
||||
}
|
||||
// If no specific activity detected, fall back to PT validation
|
||||
else {
|
||||
// PT Company validation - PT/PT. automatically classified as Fungsi Usaha
|
||||
if ($isCompany) {
|
||||
$buildingFunction = 'Fungsi Usaha';
|
||||
|
||||
// For PT companies: area-based classification
|
||||
if ($landArea > 0 && $landArea < 500) { // < 500 m² for PT = Non-Mikro (since PT is already established business)
|
||||
$subBuildingFunction = 'Usaha Besar (Non-Mikro)';
|
||||
} elseif ($landArea >= 500) { // >= 500 m² for PT = Large Business
|
||||
$subBuildingFunction = 'Usaha Besar (Non-Mikro)';
|
||||
} else {
|
||||
$subBuildingFunction = 'Usaha Besar (Non-Mikro)'; // Default for PT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no activities, fall back to PT validation
|
||||
else {
|
||||
// PT Company validation - PT/PT. automatically classified as Fungsi Usaha
|
||||
if ($isCompany) {
|
||||
$buildingFunction = 'Fungsi Usaha';
|
||||
|
||||
// For PT companies: area-based classification
|
||||
if ($landArea > 0 && $landArea < 500) { // < 500 m² for PT = Non-Mikro (since PT is already established business)
|
||||
$subBuildingFunction = 'Usaha Besar (Non-Mikro)';
|
||||
} elseif ($landArea >= 500) { // >= 500 m² for PT = Large Business
|
||||
$subBuildingFunction = 'Usaha Besar (Non-Mikro)';
|
||||
} else {
|
||||
$subBuildingFunction = 'Usaha Besar (Non-Mikro)'; // Default for PT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'land_area' => $landArea,
|
||||
'bcr_percentage' => $bcrPercentage,
|
||||
'calculated_area' => $calculatedArea,
|
||||
'location' => $location,
|
||||
'activities' => $activities, // Activities from column D of first row
|
||||
'building_function' => $buildingFunction,
|
||||
'sub_building_function' => $subBuildingFunction,
|
||||
'no_tapak' => $noTapak,
|
||||
'no_skkl' => $noSKKL,
|
||||
'no_ukl' => $noUKL,
|
||||
'has_valid_data' => $landArea > 0 // Flag untuk validasi
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean and convert string to numeric value
|
||||
*/
|
||||
private function cleanNumericValue($value): float
|
||||
{
|
||||
if (is_numeric($value)) {
|
||||
return (float) $value;
|
||||
}
|
||||
|
||||
// Remove non-numeric characters except decimal points and commas
|
||||
$cleaned = preg_replace('/[^0-9.,]/', '', $value);
|
||||
|
||||
// Handle different decimal separators
|
||||
if (strpos($cleaned, ',') !== false && strpos($cleaned, '.') !== false) {
|
||||
// Both comma and dot present, assume comma is thousands separator
|
||||
$cleaned = str_replace(',', '', $cleaned);
|
||||
} elseif (strpos($cleaned, ',') !== false) {
|
||||
// Only comma present, assume it's decimal separator
|
||||
$cleaned = str_replace(',', '.', $cleaned);
|
||||
}
|
||||
|
||||
return is_numeric($cleaned) ? (float) $cleaned : 0;
|
||||
}
|
||||
}
|
||||
33
app/Console/Commands/ScrapingLeaderData.php
Normal file
33
app/Console/Commands/ScrapingLeaderData.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\ServiceGoogleSheet;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ScrapingLeaderData extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:scraping-leader-data';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Scraping leader data from google spreadsheet and save to database';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$service_google_sheet = app(ServiceGoogleSheet::class);
|
||||
$service_google_sheet->sync_leader_data();
|
||||
$this->info('Leader data synced successfully');
|
||||
}
|
||||
}
|
||||
80
app/Console/Commands/StartScrapingData.php
Normal file
80
app/Console/Commands/StartScrapingData.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\ScrapingDataJob;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class StartScrapingData extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:start-scraping-data
|
||||
{--confirm : Skip confirmation prompt}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Start the optimized scraping data job (Google Sheet -> PBG Task -> Details)';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🚀 Starting Optimized Scraping Data Job');
|
||||
$this->info('=====================================');
|
||||
|
||||
if (!$this->option('confirm')) {
|
||||
$this->warn('⚠️ This will start a comprehensive data scraping process:');
|
||||
$this->line(' 1. Google Sheet data scraping');
|
||||
$this->line(' 2. PBG Task parent data scraping');
|
||||
$this->line(' 3. Detailed task information scraping');
|
||||
$this->line(' 4. BigData resume generation');
|
||||
$this->newLine();
|
||||
|
||||
if (!$this->confirm('Do you want to continue?')) {
|
||||
$this->info('Operation cancelled.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Dispatch the optimized job
|
||||
$job = new ScrapingDataJob();
|
||||
dispatch($job);
|
||||
|
||||
Log::info('ScrapingDataJob dispatched via command', [
|
||||
'command' => $this->signature,
|
||||
'user' => $this->option('confirm') ? 'auto' : 'manual'
|
||||
]);
|
||||
|
||||
$this->info('✅ Scraping Data Job has been dispatched to the scraping queue!');
|
||||
$this->newLine();
|
||||
$this->info('📊 Monitor the job with:');
|
||||
$this->line(' php artisan queue:monitor scraping');
|
||||
$this->newLine();
|
||||
$this->info('📜 View detailed logs with:');
|
||||
$this->line(' tail -f /var/log/supervisor/sibedas-queue-scraping.log | grep "SCRAPING DATA JOB"');
|
||||
$this->newLine();
|
||||
$this->info('🔍 Check ImportDatasource status:');
|
||||
$this->line(' docker-compose -f docker-compose.local.yml exec app php artisan tinker --execute="App\\Models\\ImportDatasource::latest()->first();"');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('❌ Failed to dispatch ScrapingDataJob: ' . $e->getMessage());
|
||||
Log::error('Failed to dispatch ScrapingDataJob via command', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
62
app/Console/Commands/SyncDashboardPbg.php
Normal file
62
app/Console/Commands/SyncDashboardPbg.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\ServiceGoogleSheet;
|
||||
use App\Models\BigdataResume;
|
||||
use App\Models\ImportDatasource;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SyncDashboardPbg extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:sync-dashboard-pbg';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$import_datasource = ImportDatasource::create([
|
||||
'message' => 'Initiating sync dashboard pbg...',
|
||||
'response_body' => null,
|
||||
'status' => 'processing',
|
||||
'start_time' => now(),
|
||||
'failed_uuid' => null
|
||||
]);
|
||||
|
||||
try {
|
||||
BigdataResume::generateResumeData($import_datasource->id, date('Y'), "simbg");
|
||||
|
||||
$import_datasource->update([
|
||||
'status' => 'success',
|
||||
'message' => 'Sync dashboard pbg completed successfully.',
|
||||
'finish_time' => now()
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Sync dashboard pbg failed: ' . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
|
||||
|
||||
// Update status to failed
|
||||
if (isset($import_datasource)) {
|
||||
$import_datasource->update([
|
||||
'status' => 'failed',
|
||||
'message' => 'Sync dashboard pbg failed.',
|
||||
'finish_time' => now()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
98
app/Console/Commands/SyncGoogleSheetData.php
Normal file
98
app/Console/Commands/SyncGoogleSheetData.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\ServiceGoogleSheet;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Exception;
|
||||
|
||||
class SyncGoogleSheetData extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'sync:google-sheet {--type=all : Specify sync type (all, google-sheet, big-data, leader)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Sync data from Google Sheets to database';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$type = $this->option('type');
|
||||
|
||||
$this->info('Starting Google Sheet data synchronization...');
|
||||
$this->info("Sync type: {$type}");
|
||||
|
||||
try {
|
||||
$service = new ServiceGoogleSheet();
|
||||
|
||||
switch ($type) {
|
||||
case 'google-sheet':
|
||||
$this->info('Syncing Google Sheet data...');
|
||||
$service->sync_google_sheet_data();
|
||||
$this->info('✅ Google Sheet data synchronized successfully!');
|
||||
break;
|
||||
|
||||
case 'big-data':
|
||||
$this->info('Syncing Big Data...');
|
||||
$service->sync_big_data();
|
||||
$this->info('✅ Big Data synchronized successfully!');
|
||||
break;
|
||||
|
||||
case 'leader':
|
||||
$this->info('Syncing Leader data...');
|
||||
$result = $service->sync_leader_data();
|
||||
$this->info('✅ Leader data synchronized successfully!');
|
||||
$this->table(['Section', 'Total', 'Nominal'], collect($result)->map(function($item, $key) {
|
||||
// Convert nominal to numeric before formatting
|
||||
$nominal = $item['nominal'] ?? 0;
|
||||
if (is_string($nominal)) {
|
||||
// Remove dots and convert to float
|
||||
$nominal = (float) str_replace('.', '', $nominal);
|
||||
}
|
||||
|
||||
return [
|
||||
$key,
|
||||
$item['total'] ?? 'N/A',
|
||||
number_format((float) $nominal, 0, ',', '.')
|
||||
];
|
||||
})->toArray());
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
default:
|
||||
$this->info('Syncing all data (Google Sheet + Big Data)...');
|
||||
$service->run_service();
|
||||
$this->info('✅ All data synchronized successfully!');
|
||||
break;
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->info('🚀 Synchronization completed successfully!');
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->error('❌ Synchronization failed!');
|
||||
$this->error("Error: {$e->getMessage()}");
|
||||
|
||||
Log::error('Google Sheet sync command failed', [
|
||||
'error' => $e->getMessage(),
|
||||
'type' => $type,
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
65
app/Console/Commands/SyncPbgTaskPayments.php
Normal file
65
app/Console/Commands/SyncPbgTaskPayments.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\ServiceGoogleSheet;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SyncPbgTaskPayments extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'sync:pbg-payments';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Sync PBG task payments from Google Sheets Sheet Data';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🚀 Starting PBG Task Payments sync...');
|
||||
$this->newLine();
|
||||
|
||||
try {
|
||||
$service = new ServiceGoogleSheet();
|
||||
|
||||
// Show progress bar
|
||||
$this->info('📊 Fetching data from Google Sheets...');
|
||||
$result = $service->sync_pbg_task_payments();
|
||||
|
||||
// Display results
|
||||
$this->newLine();
|
||||
$this->info('✅ Sync completed successfully!');
|
||||
$this->newLine();
|
||||
|
||||
$this->table(
|
||||
['Metric', 'Value'],
|
||||
[
|
||||
['Inserted rows', $result['inserted'] ?? 0],
|
||||
['Success', ($result['success'] ?? false) ? 'Yes' : 'No'],
|
||||
]
|
||||
);
|
||||
|
||||
$this->newLine();
|
||||
$this->info('📝 Check Laravel logs for detailed information.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->newLine();
|
||||
$this->error('❌ Sync failed!');
|
||||
$this->error('Error: ' . $e->getMessage());
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
265
app/Console/Commands/TestRetributionCalculation.php
Normal file
265
app/Console/Commands/TestRetributionCalculation.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\RetributionCalculatorService;
|
||||
use App\Models\BuildingType;
|
||||
|
||||
class TestRetributionCalculation extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'retribution:test
|
||||
{--area= : Luas bangunan dalam m2}
|
||||
{--floor= : Jumlah lantai (1-6)}
|
||||
{--type= : ID atau kode building type}
|
||||
{--all : Test semua building types}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Test perhitungan retribusi PBG dengan input luas bangunan dan tinggi lantai';
|
||||
|
||||
protected $calculatorService;
|
||||
|
||||
public function __construct(RetributionCalculatorService $calculatorService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->calculatorService = $calculatorService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🏢 SISTEM TEST PERHITUNGAN RETRIBUSI PBG');
|
||||
$this->info('=' . str_repeat('=', 50));
|
||||
|
||||
// Test all building types if --all flag is used
|
||||
if ($this->option('all')) {
|
||||
return $this->testAllBuildingTypes();
|
||||
}
|
||||
|
||||
// Get input parameters
|
||||
$area = $this->getArea();
|
||||
$floor = $this->getFloor();
|
||||
$buildingTypeId = $this->getBuildingType();
|
||||
|
||||
if (!$area || !$floor || !$buildingTypeId) {
|
||||
$this->error('❌ Parameter tidak lengkap!');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Perform calculation
|
||||
$this->performCalculation($buildingTypeId, $floor, $area);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function getArea()
|
||||
{
|
||||
$area = $this->option('area');
|
||||
|
||||
if (!$area) {
|
||||
$area = $this->ask('📐 Masukkan luas bangunan (m²)');
|
||||
}
|
||||
|
||||
if (!is_numeric($area) || $area <= 0) {
|
||||
$this->error('❌ Luas bangunan harus berupa angka positif!');
|
||||
return null;
|
||||
}
|
||||
|
||||
return (float) $area;
|
||||
}
|
||||
|
||||
protected function getFloor()
|
||||
{
|
||||
$floor = $this->option('floor');
|
||||
|
||||
if (!$floor) {
|
||||
$floor = $this->ask('🏗️ Masukkan jumlah lantai (1-6)');
|
||||
}
|
||||
|
||||
if (!is_numeric($floor) || $floor < 1 || $floor > 6) {
|
||||
$this->error('❌ Jumlah lantai harus antara 1-6!');
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $floor;
|
||||
}
|
||||
|
||||
protected function getBuildingType()
|
||||
{
|
||||
$type = $this->option('type');
|
||||
|
||||
if (!$type) {
|
||||
$this->showBuildingTypes();
|
||||
$type = $this->ask('🏢 Masukkan ID atau kode building type');
|
||||
}
|
||||
|
||||
// Try to find by ID first, then by code
|
||||
$buildingType = null;
|
||||
|
||||
if (is_numeric($type)) {
|
||||
$buildingType = BuildingType::find($type);
|
||||
} else {
|
||||
$buildingType = BuildingType::where('code', strtoupper($type))->first();
|
||||
}
|
||||
|
||||
if (!$buildingType) {
|
||||
$this->error('❌ Building type tidak ditemukan!');
|
||||
return null;
|
||||
}
|
||||
|
||||
return $buildingType->id;
|
||||
}
|
||||
|
||||
protected function showBuildingTypes()
|
||||
{
|
||||
$this->info('📋 DAFTAR BUILDING TYPES:');
|
||||
$this->line('');
|
||||
|
||||
$buildingTypes = BuildingType::with('indices')
|
||||
->whereHas('indices') // Only types that have indices
|
||||
->get();
|
||||
|
||||
$headers = ['ID', 'Kode', 'Nama', 'Coefficient', 'Free'];
|
||||
$rows = [];
|
||||
|
||||
foreach ($buildingTypes as $type) {
|
||||
$rows[] = [
|
||||
$type->id,
|
||||
$type->code,
|
||||
$type->name,
|
||||
$type->indices ? number_format($type->indices->coefficient, 4) : 'N/A',
|
||||
$type->is_free ? '✅' : '❌'
|
||||
];
|
||||
}
|
||||
|
||||
$this->table($headers, $rows);
|
||||
$this->line('');
|
||||
}
|
||||
|
||||
protected function performCalculation($buildingTypeId, $floor, $area)
|
||||
{
|
||||
try {
|
||||
// Round area to 2 decimal places to match database storage format
|
||||
$roundedArea = round($area, 2);
|
||||
$result = $this->calculatorService->calculate($buildingTypeId, $floor, $roundedArea, false);
|
||||
|
||||
$this->displayResults($result, $roundedArea, $floor);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('❌ Error: ' . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected function displayResults($result, $area, $floor)
|
||||
{
|
||||
$this->info('');
|
||||
$this->info('📊 HASIL PERHITUNGAN RETRIBUSI');
|
||||
$this->info('=' . str_repeat('=', 40));
|
||||
|
||||
// Building info
|
||||
$this->line('🏢 <fg=cyan>Building Type:</> ' . $result['building_type']['name']);
|
||||
$this->line('📐 <fg=cyan>Luas Bangunan:</> ' . number_format($area, 0) . ' m²');
|
||||
$this->line('🏗️ <fg=cyan>Jumlah Lantai:</> ' . $floor);
|
||||
|
||||
if (isset($result['building_type']['is_free']) && $result['building_type']['is_free']) {
|
||||
$this->line('');
|
||||
$this->info('🎉 GRATIS - Building type ini tidak dikenakan retribusi');
|
||||
$this->line('💰 <fg=green>Total Retribusi: Rp 0</fg=green>');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
|
||||
// Parameters
|
||||
$this->info('📋 PARAMETER PERHITUNGAN:');
|
||||
$indices = $result['indices'];
|
||||
$this->line('• Coefficient: ' . number_format($indices['coefficient'], 4));
|
||||
$this->line('• IP Permanent: ' . number_format($indices['ip_permanent'], 4));
|
||||
$this->line('• IP Complexity: ' . number_format($indices['ip_complexity'], 4));
|
||||
$this->line('• Locality Index: ' . number_format($indices['locality_index'], 4));
|
||||
$this->line('• Height Index: ' . number_format($result['input_parameters']['height_index'], 4));
|
||||
|
||||
$this->line('');
|
||||
|
||||
// Calculation steps
|
||||
$this->info('🔢 LANGKAH PERHITUNGAN:');
|
||||
$detail = $result['calculation_detail'];
|
||||
$this->line('1. H5 Raw: ' . number_format($detail['h5_raw'], 6));
|
||||
$this->line('2. H5 Rounded: ' . number_format($detail['h5'], 4));
|
||||
$this->line('3. Main Calculation: Rp ' . number_format($detail['main'], 2));
|
||||
$this->line('4. Infrastructure (50%): Rp ' . number_format($detail['infrastructure'], 2));
|
||||
|
||||
$this->line('');
|
||||
|
||||
// Final result
|
||||
$this->info('💰 <fg=green>TOTAL RETRIBUSI: ' . $result['formatted_amount'] . '</fg=green>');
|
||||
$this->line('📈 <fg=yellow>Per m²: Rp ' . number_format($result['total_retribution'] / $area, 2) . '</fg=yellow>');
|
||||
}
|
||||
|
||||
protected function testAllBuildingTypes()
|
||||
{
|
||||
$area = round($this->option('area') ?: 100, 2);
|
||||
$floor = $this->option('floor') ?: 2;
|
||||
|
||||
$this->info("🧪 TESTING SEMUA BUILDING TYPES");
|
||||
$this->info("📐 Luas: {$area} m² | 🏗️ Lantai: {$floor}");
|
||||
$this->info('=' . str_repeat('=', 60));
|
||||
|
||||
$buildingTypes = BuildingType::with('indices')
|
||||
->whereHas('indices') // Only types that have indices
|
||||
->orderBy('level')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
$headers = ['Kode', 'Nama', 'Coefficient', 'Total Retribusi', 'Per m²'];
|
||||
$rows = [];
|
||||
|
||||
foreach ($buildingTypes as $type) {
|
||||
try {
|
||||
$result = $this->calculatorService->calculate($type->id, $floor, $area, false);
|
||||
|
||||
if ($type->is_free) {
|
||||
$rows[] = [
|
||||
$type->code,
|
||||
$type->name,
|
||||
'FREE',
|
||||
'Rp 0',
|
||||
'Rp 0'
|
||||
];
|
||||
} else {
|
||||
$rows[] = [
|
||||
$type->code,
|
||||
$type->name,
|
||||
number_format($result['indices']['coefficient'], 4),
|
||||
'Rp ' . number_format($result['total_retribution'], 0),
|
||||
'Rp ' . number_format($result['total_retribution'] / $area, 0)
|
||||
];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$rows[] = [
|
||||
$type->code,
|
||||
$type->name,
|
||||
'ERROR',
|
||||
$e->getMessage(),
|
||||
'-'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->table($headers, $rows);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
42
app/Console/Commands/TruncatePBGTable.php
Normal file
42
app/Console/Commands/TruncatePBGTable.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\PbgTask;
|
||||
use App\Models\PbgTaskDetail;
|
||||
use App\Models\PbgTaskIndexIntegrations;
|
||||
use App\Models\PbgTaskPrasarana;
|
||||
use App\Models\PbgTaskRetributions;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TruncatePBGTable extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:truncate-pbg-table';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
|
||||
PbgTask::truncate();
|
||||
PbgTaskRetributions::truncate();
|
||||
PbgTaskDetail::truncate();
|
||||
PbgTaskIndexIntegrations::truncate();
|
||||
PbgTaskPrasarana::truncate();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
|
||||
}
|
||||
}
|
||||
266
app/Console/Commands/TruncateSpatialPlanningData.php
Normal file
266
app/Console/Commands/TruncateSpatialPlanningData.php
Normal file
@@ -0,0 +1,266 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Exception;
|
||||
|
||||
class TruncateSpatialPlanningData extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'spatial-planning:truncate
|
||||
{--force : Force truncate without confirmation}
|
||||
{--backup : Create backup before truncate}
|
||||
{--dry-run : Show what would be truncated without actually doing it}
|
||||
{--only-active : Only truncate active calculations}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Truncate spatial planning data with related calculable retributions and retribution calculations';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$force = $this->option('force');
|
||||
$backup = $this->option('backup');
|
||||
$dryRun = $this->option('dry-run');
|
||||
$onlyActive = $this->option('only-active');
|
||||
|
||||
if ($dryRun) {
|
||||
$this->warn('DRY RUN MODE - No data will be truncated');
|
||||
}
|
||||
|
||||
// Check existing data
|
||||
$this->showDataStatistics();
|
||||
|
||||
// Confirm truncation if not in force mode
|
||||
if (!$force && !$dryRun) {
|
||||
if (!$this->confirm('This will permanently delete all spatial planning data and related calculations. Continue?')) {
|
||||
$this->info('Operation cancelled.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Create backup if requested
|
||||
if ($backup && !$dryRun) {
|
||||
$this->createBackup();
|
||||
}
|
||||
|
||||
// Perform truncation
|
||||
if ($dryRun) {
|
||||
$this->performDryRun();
|
||||
} else {
|
||||
$this->performTruncation($onlyActive);
|
||||
}
|
||||
|
||||
// Show final statistics
|
||||
if (!$dryRun) {
|
||||
$this->showDataStatistics('AFTER');
|
||||
}
|
||||
|
||||
return 0;
|
||||
} catch (Exception $e) {
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
Log::error("TruncateSpatialPlanningData failed", ['error' => $e->getMessage()]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show data statistics
|
||||
*/
|
||||
private function showDataStatistics(string $prefix = 'BEFORE'): void
|
||||
{
|
||||
$this->info("=== {$prefix} TRUNCATION ===");
|
||||
|
||||
$spatialCount = DB::table('spatial_plannings')->count();
|
||||
$calculableCount = DB::table('calculable_retributions')->count();
|
||||
$activeCalculableCount = DB::table('calculable_retributions')->where('is_active', true)->count();
|
||||
$calculationCount = DB::table('retribution_calculations')->count();
|
||||
|
||||
$this->table(
|
||||
['Table', 'Total Records', 'Active Records'],
|
||||
[
|
||||
['spatial_plannings', $spatialCount, '-'],
|
||||
['calculable_retributions', $calculableCount, $activeCalculableCount],
|
||||
['retribution_calculations', $calculationCount, '-'],
|
||||
]
|
||||
);
|
||||
|
||||
// Show breakdown by building function
|
||||
$buildingFunctionStats = DB::table('spatial_plannings')
|
||||
->select('building_function', DB::raw('count(*) as total'))
|
||||
->groupBy('building_function')
|
||||
->get();
|
||||
|
||||
if ($buildingFunctionStats->isNotEmpty()) {
|
||||
$this->info('Building Function Breakdown:');
|
||||
foreach ($buildingFunctionStats as $stat) {
|
||||
$this->line(" - {$stat->building_function}: {$stat->total} records");
|
||||
}
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create backup of data
|
||||
*/
|
||||
private function createBackup(): void
|
||||
{
|
||||
$this->info('Creating backup...');
|
||||
|
||||
$timestamp = date('Y-m-d_H-i-s');
|
||||
$backupPath = storage_path("backups/spatial_planning_backup_{$timestamp}");
|
||||
|
||||
// Create backup directory
|
||||
if (!file_exists($backupPath)) {
|
||||
mkdir($backupPath, 0755, true);
|
||||
}
|
||||
|
||||
// Backup spatial plannings
|
||||
$spatialData = DB::table('spatial_plannings')->get();
|
||||
file_put_contents(
|
||||
"{$backupPath}/spatial_plannings.json",
|
||||
$spatialData->toJson(JSON_PRETTY_PRINT)
|
||||
);
|
||||
|
||||
// Backup calculable retributions
|
||||
$calculableData = DB::table('calculable_retributions')->get();
|
||||
file_put_contents(
|
||||
"{$backupPath}/calculable_retributions.json",
|
||||
$calculableData->toJson(JSON_PRETTY_PRINT)
|
||||
);
|
||||
|
||||
// Backup retribution calculations
|
||||
$calculationData = DB::table('retribution_calculations')->get();
|
||||
file_put_contents(
|
||||
"{$backupPath}/retribution_calculations.json",
|
||||
$calculationData->toJson(JSON_PRETTY_PRINT)
|
||||
);
|
||||
|
||||
$this->info("Backup created at: {$backupPath}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform dry run
|
||||
*/
|
||||
private function performDryRun(): void
|
||||
{
|
||||
$this->info('DRY RUN - Would truncate the following:');
|
||||
|
||||
$spatialCount = DB::table('spatial_plannings')->count();
|
||||
$calculableCount = DB::table('calculable_retributions')->count();
|
||||
$calculationCount = DB::table('retribution_calculations')->count();
|
||||
|
||||
$this->line(" - spatial_plannings: {$spatialCount} records");
|
||||
$this->line(" - calculable_retributions: {$calculableCount} records");
|
||||
$this->line(" - retribution_calculations: {$calculationCount} records");
|
||||
|
||||
$this->warn('No actual data was truncated (dry run mode)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform actual truncation
|
||||
*/
|
||||
private function performTruncation(bool $onlyActive = false): void
|
||||
{
|
||||
$this->info('Starting truncation...');
|
||||
|
||||
try {
|
||||
// Disable foreign key checks for safe truncation
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
|
||||
|
||||
if ($onlyActive) {
|
||||
// Only truncate active calculations
|
||||
$this->truncateActiveOnly();
|
||||
} else {
|
||||
// Truncate all data
|
||||
$this->truncateAllData();
|
||||
}
|
||||
|
||||
// Re-enable foreign key checks
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
|
||||
$this->info('✅ Truncation completed successfully!');
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Make sure to re-enable foreign key checks even on error
|
||||
try {
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
} catch (Exception $fkError) {
|
||||
$this->error('Failed to re-enable foreign key checks: ' . $fkError->getMessage());
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate only active calculations
|
||||
*/
|
||||
private function truncateActiveOnly(): void
|
||||
{
|
||||
$this->info('Truncating only active calculations...');
|
||||
|
||||
// Delete active calculable retributions
|
||||
$deletedActive = DB::table('calculable_retributions')
|
||||
->where('is_active', true)
|
||||
->delete();
|
||||
$this->info("Deleted {$deletedActive} active calculable retributions");
|
||||
|
||||
// Delete orphaned retribution calculations
|
||||
$deletedOrphaned = DB::table('retribution_calculations')
|
||||
->whereNotExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('calculable_retributions')
|
||||
->whereColumn('calculable_retributions.retribution_calculation_id', 'retribution_calculations.id');
|
||||
})
|
||||
->delete();
|
||||
$this->info("Deleted {$deletedOrphaned} orphaned retribution calculations");
|
||||
|
||||
// Keep spatial plannings but remove their calculation relationships
|
||||
$this->info('Spatial plannings data preserved (only calculations removed)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate all data
|
||||
*/
|
||||
private function truncateAllData(): void
|
||||
{
|
||||
$this->info('Truncating all data...');
|
||||
|
||||
// Get counts before truncation
|
||||
$spatialCount = DB::table('spatial_plannings')->count();
|
||||
$calculableCount = DB::table('calculable_retributions')->count();
|
||||
$calculationCount = DB::table('retribution_calculations')->count();
|
||||
|
||||
// Truncate tables in correct order
|
||||
DB::table('calculable_retributions')->truncate();
|
||||
$this->info("Truncated calculable_retributions table ({$calculableCount} records)");
|
||||
|
||||
DB::table('retribution_calculations')->truncate();
|
||||
$this->info("Truncated retribution_calculations table ({$calculationCount} records)");
|
||||
|
||||
DB::table('spatial_plannings')->truncate();
|
||||
$this->info("Truncated spatial_plannings table ({$spatialCount} records)");
|
||||
|
||||
// Reset auto increment
|
||||
DB::statement('ALTER TABLE calculable_retributions AUTO_INCREMENT = 1');
|
||||
DB::statement('ALTER TABLE retribution_calculations AUTO_INCREMENT = 1');
|
||||
DB::statement('ALTER TABLE spatial_plannings AUTO_INCREMENT = 1');
|
||||
$this->info('Reset auto increment counters');
|
||||
}
|
||||
}
|
||||
25
app/Enums/PbgTaskApplicationTypes.php
Normal file
25
app/Enums/PbgTaskApplicationTypes.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum PbgTaskApplicationTypes: string
|
||||
{
|
||||
case PERSETUJUAN_BG = '1';
|
||||
case PERUBAHAN_BG = '2';
|
||||
case SLF_BB = '4';
|
||||
case SLF = '5';
|
||||
public static function labels(): array
|
||||
{
|
||||
return [
|
||||
null => "Pilih Application Type",
|
||||
self::PERSETUJUAN_BG->value => 'Persetujuan Bangunan Gedung',
|
||||
self::PERUBAHAN_BG->value => 'Perubahan Bangunan Gedung',
|
||||
self::SLF_BB->value => 'Sertifikat Laik Fungsi - Bangunan Baru',
|
||||
self::SLF->value => 'Sertifikat Laik Fungsi',
|
||||
];
|
||||
}
|
||||
public static function getLabel(?string $status): ?string
|
||||
{
|
||||
return self::labels()[$status] ?? null;
|
||||
}
|
||||
}
|
||||
40
app/Enums/PbgTaskFilterData.php
Normal file
40
app/Enums/PbgTaskFilterData.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum PbgTaskFilterData : string
|
||||
{
|
||||
case non_business = 'non-business';
|
||||
case business = 'business';
|
||||
case verified = 'verified';
|
||||
case non_verified = 'non-verified';
|
||||
case all = 'all';
|
||||
case potention = 'potention';
|
||||
case issuance_realization_pbg = 'issuance-realization-pbg';
|
||||
case process_in_technical_office = 'process-in-technical-office';
|
||||
case waiting_click_dpmptsp = 'waiting-click-dpmptsp';
|
||||
case non_business_rab = 'non-business-rab';
|
||||
case non_business_krk = 'non-business-krk';
|
||||
case business_rab = 'business-rab';
|
||||
case business_krk = 'business-krk';
|
||||
case business_dlh = 'business-dlh';
|
||||
|
||||
public static function getAllOptions() : array {
|
||||
return [
|
||||
self::all->value => 'Semua Berkas',
|
||||
self::business->value => 'Usaha',
|
||||
self::non_business->value => 'Bukan Usaha',
|
||||
self::verified->value => 'Terverifikasi',
|
||||
self::non_verified->value => 'Belum Terverifikasi',
|
||||
self::potention->value => 'Potensi',
|
||||
self::issuance_realization_pbg->value => 'Realisasi PBG',
|
||||
self::process_in_technical_office->value => 'Proses Di Dinas Teknis',
|
||||
self::waiting_click_dpmptsp->value => 'Menunggu Klik DPMPTSP',
|
||||
self::non_business_rab->value => 'Non Usaha - RAB',
|
||||
self::non_business_krk->value => 'Non Usaha - KRK',
|
||||
self::business_rab->value => 'Usaha - RAB',
|
||||
self::business_krk->value => 'Usaha - KRK',
|
||||
self::business_dlh->value => 'Usaha - DLH',
|
||||
];
|
||||
}
|
||||
}
|
||||
145
app/Enums/PbgTaskStatus.php
Normal file
145
app/Enums/PbgTaskStatus.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum PbgTaskStatus: int
|
||||
{
|
||||
case VERIFIKASI_KELENGKAPAN = 1;
|
||||
case PERBAIKAN_DOKUMEN = 2;
|
||||
case PERMOHONAN_DIBATALKAN = 3;
|
||||
case MENUNGGU_PENUGASAN_TPT_TPA = 4;
|
||||
case MENUNGGU_JADWAL_KONSULTASI = 5;
|
||||
case PELAKSANAAN_KONSULTASI = 6;
|
||||
case PERBAIKAN_DOKUMEN_KONSULTASI = 8;
|
||||
case PERMOHONAN_DITOLAK = 9;
|
||||
case PERHITUNGAN_RETRIBUSI = 10;
|
||||
case MENUNGGU_PEMBAYARAN_RETRIBUSI = 14;
|
||||
case VERIFIKASI_PEMBAYARAN_RETRIBUSI = 15;
|
||||
case RETRIBUSI_TIDAK_SESUAI = 16;
|
||||
case VERIFIKASI_SK_PBG = 18;
|
||||
case PENERBITAN_SK_PBG = 19;
|
||||
case SK_PBG_TERBIT = 20;
|
||||
case PENERBITAN_SPPST = 24;
|
||||
case PROSES_PENERBITAN_SKRD = 25;
|
||||
case MENUNGGU_PENUGASAN_TPT = 26;
|
||||
case VERIFIKASI_DATA_TPT = 27;
|
||||
case SERTIFIKAT_SLF_TERBIT = 28;
|
||||
|
||||
public static function getStatuses(): array
|
||||
{
|
||||
return [
|
||||
null => "Pilih Status",
|
||||
self::VERIFIKASI_KELENGKAPAN->value => "Verifikasi Kelengkapan Dokumen",
|
||||
self::PERBAIKAN_DOKUMEN->value => "Perbaikan Dokumen",
|
||||
self::PERMOHONAN_DIBATALKAN->value => "Permohonan Dibatalkan",
|
||||
self::MENUNGGU_PENUGASAN_TPT_TPA->value => "Menunggu Penugasan TPT/TPA",
|
||||
self::MENUNGGU_JADWAL_KONSULTASI->value => "Menunggu Jadwal Konsultasi",
|
||||
self::PELAKSANAAN_KONSULTASI->value => "Pelaksanaan Konsultasi",
|
||||
self::PERBAIKAN_DOKUMEN_KONSULTASI->value => "Perbaikan Dokumen Konsultasi",
|
||||
self::PERMOHONAN_DITOLAK->value => "Permohonan Ditolak",
|
||||
self::PERHITUNGAN_RETRIBUSI->value => "Perhitungan Retribusi",
|
||||
self::MENUNGGU_PEMBAYARAN_RETRIBUSI->value => "Menunggu Pembayaran Retribusi",
|
||||
self::VERIFIKASI_PEMBAYARAN_RETRIBUSI->value => "Verifikasi Pembayaran Retribusi",
|
||||
self::RETRIBUSI_TIDAK_SESUAI->value => "Retribusi Tidak Sesuai",
|
||||
self::VERIFIKASI_SK_PBG->value => "Verifikasi SK PBG",
|
||||
self::PENERBITAN_SK_PBG->value => "Penerbitan SK PBG",
|
||||
self::SK_PBG_TERBIT->value => "SK PBG Terbit",
|
||||
self::PENERBITAN_SPPST->value => "Penerbitan SPPST",
|
||||
self::PROSES_PENERBITAN_SKRD->value => "Proses Penerbitan SKRD",
|
||||
self::MENUNGGU_PENUGASAN_TPT->value => "Menunggu Penugasan TPT",
|
||||
self::VERIFIKASI_DATA_TPT->value => "Verifikasi Data TPT",
|
||||
self::SERTIFIKAT_SLF_TERBIT->value => "Sertifikat SLF Terbit",
|
||||
];
|
||||
}
|
||||
|
||||
public static function getLabel(?int $status): ?string
|
||||
{
|
||||
return self::getStatuses()[$status] ?? null;
|
||||
}
|
||||
|
||||
public static function getWaitingClickDpmptsp(): array
|
||||
{
|
||||
return [
|
||||
self::MENUNGGU_PEMBAYARAN_RETRIBUSI->value,
|
||||
self::PROSES_PENERBITAN_SKRD->value,
|
||||
self::VERIFIKASI_PEMBAYARAN_RETRIBUSI->value
|
||||
];
|
||||
}
|
||||
|
||||
public static function getIssuanceRealizationPbg(): array
|
||||
{
|
||||
return [
|
||||
self::PENERBITAN_SK_PBG->value,
|
||||
self::SK_PBG_TERBIT->value,
|
||||
self::VERIFIKASI_SK_PBG->value
|
||||
];
|
||||
}
|
||||
|
||||
public static function getProcessInTechnicalOffice(): array
|
||||
{
|
||||
return [
|
||||
self::PENERBITAN_SPPST->value,
|
||||
self::PERHITUNGAN_RETRIBUSI->value,
|
||||
self::RETRIBUSI_TIDAK_SESUAI->value,
|
||||
self::MENUNGGU_JADWAL_KONSULTASI->value,
|
||||
self::MENUNGGU_PENUGASAN_TPT_TPA->value,
|
||||
self::PELAKSANAAN_KONSULTASI->value
|
||||
];
|
||||
}
|
||||
|
||||
public static function getVerified(): array
|
||||
{
|
||||
return [
|
||||
self::MENUNGGU_PEMBAYARAN_RETRIBUSI->value,
|
||||
self::PROSES_PENERBITAN_SKRD->value,
|
||||
self::VERIFIKASI_PEMBAYARAN_RETRIBUSI->value,
|
||||
self::PENERBITAN_SK_PBG->value,
|
||||
self::SK_PBG_TERBIT->value,
|
||||
self::VERIFIKASI_SK_PBG->value,
|
||||
self::PENERBITAN_SPPST->value,
|
||||
self::PERHITUNGAN_RETRIBUSI->value,
|
||||
self::RETRIBUSI_TIDAK_SESUAI->value,
|
||||
self::MENUNGGU_JADWAL_KONSULTASI->value,
|
||||
self::MENUNGGU_PENUGASAN_TPT_TPA->value,
|
||||
self::PELAKSANAAN_KONSULTASI->value
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNonVerified(): array
|
||||
{
|
||||
return [
|
||||
self::VERIFIKASI_KELENGKAPAN->value,
|
||||
self::PERBAIKAN_DOKUMEN->value,
|
||||
self::PERBAIKAN_DOKUMEN_KONSULTASI->value,
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPotention(): array
|
||||
{
|
||||
return [
|
||||
self::MENUNGGU_PEMBAYARAN_RETRIBUSI->value,
|
||||
self::PROSES_PENERBITAN_SKRD->value,
|
||||
self::VERIFIKASI_PEMBAYARAN_RETRIBUSI->value,
|
||||
self::PENERBITAN_SK_PBG->value,
|
||||
self::SK_PBG_TERBIT->value,
|
||||
self::VERIFIKASI_SK_PBG->value,
|
||||
self::PENERBITAN_SPPST->value,
|
||||
self::PERHITUNGAN_RETRIBUSI->value,
|
||||
self::RETRIBUSI_TIDAK_SESUAI->value,
|
||||
self::MENUNGGU_JADWAL_KONSULTASI->value,
|
||||
self::MENUNGGU_PENUGASAN_TPT_TPA->value,
|
||||
self::PELAKSANAAN_KONSULTASI->value,
|
||||
self::VERIFIKASI_KELENGKAPAN->value,
|
||||
self::PERBAIKAN_DOKUMEN->value,
|
||||
self::PERBAIKAN_DOKUMEN_KONSULTASI->value,
|
||||
];
|
||||
}
|
||||
|
||||
public static function getRejected(): array
|
||||
{
|
||||
return [
|
||||
self::PERMOHONAN_DITOLAK->value,
|
||||
self::PERMOHONAN_DIBATALKAN->value
|
||||
];
|
||||
}
|
||||
}
|
||||
31
app/Exports/DistrictPaymentRecapExport.php
Normal file
31
app/Exports/DistrictPaymentRecapExport.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\PbgTaskGoogleSheet;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
|
||||
class DistrictPaymentRecapExport implements FromCollection, WithHeadings
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return PbgTaskGoogleSheet::select(
|
||||
'kecamatan',
|
||||
DB::raw('SUM(nilai_retribusi_keseluruhan_simbg) as total')
|
||||
)
|
||||
->groupBy('kecamatan')->get();
|
||||
}
|
||||
|
||||
public function headings(): array{
|
||||
return [
|
||||
'Kecamatan',
|
||||
'Total'
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
118
app/Exports/PbgTaskExport.php
Normal file
118
app/Exports/PbgTaskExport.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use App\Models\PbgTask;
|
||||
use App\Enums\PbgTaskFilterData;
|
||||
|
||||
class PbgTaskExport implements FromCollection, WithHeadings
|
||||
{
|
||||
protected $category;
|
||||
protected $year;
|
||||
|
||||
public function __construct(string $category, int $year)
|
||||
{
|
||||
$this->category = $category;
|
||||
$this->year = $year;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
$query = PbgTask::query()
|
||||
->whereYear('task_created_at', $this->year);
|
||||
|
||||
// Menggunakan switch case karena lebih readable dan maintainable
|
||||
// untuk multiple conditions yang berbeda
|
||||
switch ($this->category) {
|
||||
case PbgTaskFilterData::all->value:
|
||||
// Tidak ada filter tambahan, ambil semua data
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::business->value:
|
||||
$query->where('application_type', 'business');
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::non_business->value:
|
||||
$query->where('application_type', 'non-business');
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::verified->value:
|
||||
$query->where('is_valid', true);
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::non_verified->value:
|
||||
$query->where('is_valid', false);
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::potention->value:
|
||||
$query->where('status', 'potention');
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::issuance_realization_pbg->value:
|
||||
$query->where('status', 'issuance-realization-pbg');
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::process_in_technical_office->value:
|
||||
$query->where('status', 'process-in-technical-office');
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::waiting_click_dpmptsp->value:
|
||||
$query->where('status', 'waiting-click-dpmptsp');
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::non_business_rab->value:
|
||||
$query->where('application_type', 'non-business')
|
||||
->where('consultation_type', 'rab');
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::non_business_krk->value:
|
||||
$query->where('application_type', 'non-business')
|
||||
->where('consultation_type', 'krk');
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::business_rab->value:
|
||||
$query->where('application_type', 'business')
|
||||
->where('consultation_type', 'rab');
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::business_krk->value:
|
||||
$query->where('application_type', 'business')
|
||||
->where('consultation_type', 'krk');
|
||||
break;
|
||||
|
||||
case PbgTaskFilterData::business_dlh->value:
|
||||
$query->where('application_type', 'business')
|
||||
->where('consultation_type', 'dlh');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Jika category tidak dikenali, return empty collection
|
||||
return collect();
|
||||
}
|
||||
|
||||
return $query->select([
|
||||
'registration_number',
|
||||
'document_number',
|
||||
'owner_name',
|
||||
'address',
|
||||
'name as building_name',
|
||||
'function_type'
|
||||
])->get();
|
||||
}
|
||||
|
||||
public function headings(): array{
|
||||
return [
|
||||
'Nomor Registrasi',
|
||||
'Nomor Dokumen',
|
||||
'Nama Pemilik',
|
||||
'Alamat Pemilik',
|
||||
'Nama Bangunan',
|
||||
'Fungsi Bangunan',
|
||||
];
|
||||
}
|
||||
}
|
||||
90
app/Exports/ReportDirectorExport.php
Normal file
90
app/Exports/ReportDirectorExport.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\BigdataResume;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
|
||||
class ReportDirectorExport implements FromCollection, WithHeadings, WithMapping
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return BigdataResume::select(
|
||||
'potention_count',
|
||||
'potention_sum',
|
||||
'non_verified_count',
|
||||
'non_verified_sum',
|
||||
'verified_count',
|
||||
'verified_sum',
|
||||
'business_count',
|
||||
'business_sum',
|
||||
'non_business_count',
|
||||
'non_business_sum',
|
||||
'spatial_count',
|
||||
'spatial_sum',
|
||||
'waiting_click_dpmptsp_count',
|
||||
'waiting_click_dpmptsp_sum',
|
||||
'issuance_realization_pbg_count',
|
||||
'issuance_realization_pbg_sum',
|
||||
'process_in_technical_office_count',
|
||||
'process_in_technical_office_sum',
|
||||
'year',
|
||||
'created_at'
|
||||
)->orderBy('id', 'desc')->get();
|
||||
}
|
||||
public function headings(): array{
|
||||
return [
|
||||
"Jumlah Potensi" ,
|
||||
"Total Potensi" ,
|
||||
"Jumlah Berkas Belum Terverifikasi" ,
|
||||
"Total Berkas Belum Terverifikasi" ,
|
||||
"Jumlah Berkas Terverifikasi" ,
|
||||
"Total Berkas Terverifikasi" ,
|
||||
"Jumlah Usaha" ,
|
||||
"Total Usaha" ,
|
||||
"Jumlah Non Usaha" ,
|
||||
"Total Non Usaha" ,
|
||||
"Jumlah Tata Ruang" ,
|
||||
"Total Tata Ruang" ,
|
||||
"Jumlah Menunggu Klik DPMPTSP" ,
|
||||
"Total Menunggu Klik DPMPTSP" ,
|
||||
"Jumlah Realisasi Terbit PBG" ,
|
||||
"Total Realisasi Terbit PBG" ,
|
||||
"Jumlah Proses Dinas Teknis" ,
|
||||
"Total Proses Dinas Teknis",
|
||||
"Tahun",
|
||||
"Created"
|
||||
];
|
||||
}
|
||||
|
||||
public function map($row): array
|
||||
{
|
||||
return [
|
||||
$row->potention_count,
|
||||
$row->potention_sum,
|
||||
$row->non_verified_count,
|
||||
$row->non_verified_sum,
|
||||
$row->verified_count,
|
||||
$row->verified_sum,
|
||||
$row->business_count,
|
||||
$row->business_sum,
|
||||
$row->non_business_count,
|
||||
$row->non_business_sum,
|
||||
$row->spatial_count,
|
||||
$row->spatial_sum,
|
||||
$row->waiting_click_dpmptsp_count,
|
||||
$row->waiting_click_dpmptsp_sum,
|
||||
$row->issuance_realization_pbg_count,
|
||||
$row->issuance_realization_pbg_sum,
|
||||
$row->process_in_technical_office_count,
|
||||
$row->process_in_technical_office_sum,
|
||||
$row->year,
|
||||
$row->created_at ? $row->created_at->format('Y-m-d H:i:s') : null, // Format created_at as Y-m-d
|
||||
];
|
||||
}
|
||||
}
|
||||
71
app/Exports/ReportPaymentRecapExport.php
Normal file
71
app/Exports/ReportPaymentRecapExport.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\BigdataResume;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
|
||||
class ReportPaymentRecapExport implements FromCollection, WithHeadings
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $startDate;
|
||||
protected $endDate;
|
||||
public function __construct($startDate, $endDate){
|
||||
$this->startDate = $startDate;
|
||||
$this->endDate = $endDate;
|
||||
}
|
||||
public function collection()
|
||||
{
|
||||
$query = BigdataResume::query()->orderBy('id', 'desc');
|
||||
|
||||
if ($this->startDate && $this->endDate) {
|
||||
$query->whereBetween('created_at', [$this->startDate, $this->endDate]);
|
||||
}
|
||||
|
||||
$items = $query->get();
|
||||
|
||||
$categoryMap = [
|
||||
'potention_sum' => 'Potensi',
|
||||
'non_verified_sum' => 'Belum Terverifikasi',
|
||||
'verified_sum' => 'Terverifikasi',
|
||||
'business_sum' => 'Usaha',
|
||||
'non_business_sum' => 'Non Usaha',
|
||||
'spatial_sum' => 'Tata Ruang',
|
||||
'waiting_click_dpmptsp_sum' => 'Menunggu Klik DPMPTSP',
|
||||
'issuance_realization_pbg_sum' => 'Realisasi Terbit PBG',
|
||||
'process_in_technical_office_sum' => 'Proses Di Dinas Teknis',
|
||||
];
|
||||
|
||||
// Restructure response
|
||||
$data = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$createdAt = $item->created_at;
|
||||
$id = $item->id;
|
||||
|
||||
foreach ($item->toArray() as $key => $value) {
|
||||
// Only include columns with "sum" in their names
|
||||
if (strpos($key, 'sum') !== false) {
|
||||
$data[] = [
|
||||
'category' => $categoryMap[$key] ?? $key, // Map category
|
||||
'nominal' => number_format($value, 0, ',', '.'), // Format number
|
||||
'created_at' => $createdAt->format('Y-m-d H:i:s'), // Format date
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return collect($data);
|
||||
}
|
||||
|
||||
public function headings(): array{
|
||||
return [
|
||||
'Kategori',
|
||||
'Nominal',
|
||||
'Created'
|
||||
];
|
||||
}
|
||||
}
|
||||
32
app/Exports/ReportPbgPtspExport.php
Normal file
32
app/Exports/ReportPbgPtspExport.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\PbgTask;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
|
||||
class ReportPbgPtspExport implements FromCollection, WithHeadings
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return PbgTask::select(
|
||||
'status_name',
|
||||
DB::raw('COUNT(*) as total')
|
||||
)
|
||||
->groupBy('status', 'status_name')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'Status Name',
|
||||
'Total'
|
||||
];
|
||||
}
|
||||
}
|
||||
25
app/Exports/ReportTourismExport.php
Normal file
25
app/Exports/ReportTourismExport.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\TourismBasedKBLI;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
|
||||
class ReportTourismExport implements FromCollection, WithHeadings
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return TourismBasedKBLI::select('kbli_title', 'total_records')->get();
|
||||
}
|
||||
|
||||
public function headings(): array{
|
||||
return [
|
||||
'Jenis Bisnis Pariwisata',
|
||||
'Jumlah Total'
|
||||
];
|
||||
}
|
||||
}
|
||||
59
app/Exports/TaxSubdistrictSheetExport.php
Normal file
59
app/Exports/TaxSubdistrictSheetExport.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\Tax;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
|
||||
class TaxSubdistrictSheetExport implements FromCollection, WithTitle, WithHeadings
|
||||
{
|
||||
protected $subdistrict;
|
||||
|
||||
public function __construct(string $subdistrict)
|
||||
{
|
||||
$this->subdistrict = $subdistrict;
|
||||
}
|
||||
|
||||
public function collection()
|
||||
{
|
||||
return Tax::where('subdistrict', $this->subdistrict)
|
||||
->select(
|
||||
'tax_code',
|
||||
'tax_no',
|
||||
'npwpd',
|
||||
'wp_name',
|
||||
'business_name',
|
||||
'address',
|
||||
'start_validity',
|
||||
'end_validity',
|
||||
'tax_value',
|
||||
'subdistrict',
|
||||
'village'
|
||||
)->get();
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'Kode',
|
||||
'No',
|
||||
'NPWPD',
|
||||
'Nama WP',
|
||||
'Nama Usaha',
|
||||
'Alamat Usaha',
|
||||
'Tanggal Mulai Berlaku',
|
||||
'Tanggal Berakhir Berlaku',
|
||||
'Nilai Pajak',
|
||||
'Kecamatan',
|
||||
'Desa'
|
||||
];
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return mb_substr($this->subdistrict, 0, 31);
|
||||
}
|
||||
}
|
||||
|
||||
23
app/Exports/TaxationsExport.php
Normal file
23
app/Exports/TaxationsExport.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\Tax;
|
||||
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
|
||||
|
||||
class TaxationsExport implements WithMultipleSheets
|
||||
{
|
||||
public function sheets(): array
|
||||
{
|
||||
$sheets = [];
|
||||
|
||||
// Ambil semua subdistrict unik
|
||||
$subdistricts = Tax::select('subdistrict')->distinct()->pluck('subdistrict');
|
||||
|
||||
foreach ($subdistricts as $subdistrict) {
|
||||
$sheets[] = new TaxSubdistrictSheetExport($subdistrict);
|
||||
}
|
||||
|
||||
return $sheets;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,19 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Exports\ReportDirectorExport;
|
||||
use App\Exports\ReportPaymentRecapExport;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\BigdataResumeResource;
|
||||
use App\Models\BigdataResume;
|
||||
use App\Models\DataSetting;
|
||||
use App\Models\SpatialPlanning;
|
||||
use App\Models\PbgTaskPayment;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
|
||||
class BigDataResumeController extends Controller
|
||||
{
|
||||
@@ -18,14 +25,16 @@ class BigDataResumeController extends Controller
|
||||
{
|
||||
try{
|
||||
$filterDate = $request->get("filterByDate");
|
||||
$type = trim($request->get("type"));
|
||||
|
||||
if (!$filterDate || $filterDate === "latest") {
|
||||
$big_data_resume = BigdataResume::where('year', now()->year)->latest()->first();
|
||||
$big_data_resume = BigdataResume::where('resume_type', $type)->latest()->first();
|
||||
if (!$big_data_resume) {
|
||||
return $this->response_empty_resume();
|
||||
}
|
||||
} else {
|
||||
$big_data_resume = BigdataResume::whereDate('created_at', $filterDate)
|
||||
->where('resume_type', $type)
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
@@ -35,23 +44,28 @@ class BigDataResumeController extends Controller
|
||||
}
|
||||
|
||||
$data_settings = DataSetting::all();
|
||||
if($data_settings->isEmpty()){
|
||||
return response()->json(['message' => 'No data setting found']);
|
||||
$target_pad = 0;
|
||||
if($data_settings->where('key', 'TARGET_PAD')->first()){
|
||||
$target_pad = floatval($data_settings->where('key', 'TARGET_PAD')->first()->value ?? 0);
|
||||
}
|
||||
|
||||
function cleanNumber($value) {
|
||||
return floatval(str_replace('.', '', $value));
|
||||
}
|
||||
$realisasi_terbit_pbg_sum = $big_data_resume->issuance_realization_pbg_sum;
|
||||
$realisasi_terbit_pbg_count = $big_data_resume->issuance_realization_pbg_count;
|
||||
$menunggu_klik_dpmptsp_sum = $big_data_resume->waiting_click_dpmptsp_sum;
|
||||
$menunggu_klik_dpmptsp_count = $big_data_resume->waiting_click_dpmptsp_count;
|
||||
$proses_dinas_teknis_sum = $big_data_resume->process_in_technical_office_sum;
|
||||
$proses_dinas_teknis_count = $big_data_resume->process_in_technical_office_count;
|
||||
|
||||
$target_pad = floatval(optional($data_settings->where('key', 'TARGET_PAD')->first())->value);
|
||||
$realisasi_terbit_pbg_sum = cleanNumber(optional($data_settings->where('key', 'REALISASI_TERBIT_PBG_SUM')->first())->value);
|
||||
$realisasi_terbit_pbg_count = cleanNumber(optional($data_settings->where('key', 'REALISASI_TERBIT_PBG_COUNT')->first())->value);
|
||||
$menunggu_klik_dpmptsp_sum = cleanNumber(optional($data_settings->where('key', 'MENUNGGU_KLIK_DPMPTSP_SUM')->first())->value);
|
||||
$menunggu_klik_dpmptsp_count = cleanNumber(optional($data_settings->where('key', 'MENUNGGU_KLIK_DPMPTSP_COUNT')->first())->value);
|
||||
$proses_dinas_teknis_sum = cleanNumber(optional($data_settings->where('key', 'PROSES_DINAS_TEKNIS_SUM')->first())->value);
|
||||
$proses_dinas_teknis_count = cleanNumber(optional($data_settings->where('key', 'PROSES_DINAS_TEKNIS_COUNT')->first())->value);
|
||||
// Get real-time spatial planning data using new calculation formula
|
||||
$spatialData = $this->getSpatialPlanningData();
|
||||
$tata_ruang = $spatialData['sum'];
|
||||
$tata_ruang_count = $spatialData['count'];
|
||||
|
||||
// Get real-time PBG Task Payments data
|
||||
$pbgPaymentsData = $this->getPbgTaskPaymentsData();
|
||||
$pbg_task_payments_sum = $pbgPaymentsData['sum'];
|
||||
$pbg_task_payments_count = $pbgPaymentsData['count'];
|
||||
|
||||
$tata_ruang = $big_data_resume->spatial_sum;
|
||||
$kekurangan_potensi = $target_pad - $big_data_resume->potention_sum;
|
||||
|
||||
// percentage kekurangan potensi
|
||||
@@ -62,46 +76,62 @@ class BigDataResumeController extends Controller
|
||||
$total_potensi_percentage = $big_data_resume->potention_sum > 0 && $target_pad > 0
|
||||
? round(($big_data_resume->potention_sum / $target_pad) * 100, 2) : 0;
|
||||
|
||||
// percentage verified document
|
||||
$verified_percentage = $big_data_resume->verified_sum > 0 && $big_data_resume->potention_sum > 0
|
||||
// percentage verified document (verified_sum / potention_sum) - by value/amount
|
||||
$verified_percentage = $big_data_resume->potention_sum > 0 && $big_data_resume->verified_sum >= 0
|
||||
? round(($big_data_resume->verified_sum / $big_data_resume->potention_sum) * 100, 2) : 0;
|
||||
|
||||
// percentage non-verified document
|
||||
$non_verified_percentage = $big_data_resume->non_verified_sum > 0 && $big_data_resume->potention_sum > 0
|
||||
// percentage non-verified document (non_verified_sum / potention_sum) - by value/amount
|
||||
$non_verified_percentage = $big_data_resume->potention_sum > 0 && $big_data_resume->non_verified_sum >= 0
|
||||
? round(($big_data_resume->non_verified_sum / $big_data_resume->potention_sum) * 100, 2) : 0;
|
||||
|
||||
// percentage business document
|
||||
$business_percentage = $big_data_resume->business_sum > 0 && $big_data_resume->non_verified_sum > 0
|
||||
// Alternative: percentage by count (if needed)
|
||||
// $verified_count_percentage = $big_data_resume->potention_count > 0
|
||||
// ? round(($big_data_resume->verified_count / $big_data_resume->potention_count) * 100, 2) : 0;
|
||||
// $non_verified_count_percentage = $big_data_resume->potention_count > 0
|
||||
// ? round(($big_data_resume->non_verified_count / $big_data_resume->potention_count) * 100, 2) : 0;
|
||||
|
||||
// percentage business document (business / non_verified)
|
||||
$business_percentage = $big_data_resume->non_verified_sum > 0 && $big_data_resume->business_sum >= 0
|
||||
? round(($big_data_resume->business_sum / $big_data_resume->non_verified_sum) * 100, 2) : 0;
|
||||
|
||||
// percentage non-business document
|
||||
$non_business_percentage = $big_data_resume->non_business_sum > 0 && $big_data_resume->potention_sum > 0
|
||||
? round(($big_data_resume->non_business_sum / $big_data_resume->potention_sum) * 100, 2) : 0;
|
||||
// percentage non-business document (non_business / non_verified)
|
||||
$non_business_percentage = $big_data_resume->non_verified_sum > 0 && $big_data_resume->non_business_sum >= 0
|
||||
? round(($big_data_resume->non_business_sum / $big_data_resume->non_verified_sum) * 100, 2) : 0;
|
||||
|
||||
// percentage tata ruang
|
||||
$tata_ruang_percentage = $tata_ruang > 0 && $big_data_resume->potention_sum > 0
|
||||
// percentage tata ruang (spatial / potention)
|
||||
$tata_ruang_percentage = $big_data_resume->potention_sum > 0 && $tata_ruang >= 0
|
||||
? round(($tata_ruang / $big_data_resume->potention_sum) * 100, 2) : 0;
|
||||
|
||||
// percentage realisasi terbit pbg
|
||||
$realisasi_terbit_percentage = $big_data_resume->verified_sum > 0 && $realisasi_terbit_pbg_sum > 0
|
||||
// percentage realisasi terbit pbg (issuance / verified)
|
||||
$realisasi_terbit_percentage = $big_data_resume->verified_sum > 0 && $realisasi_terbit_pbg_sum >= 0
|
||||
? round(($realisasi_terbit_pbg_sum / $big_data_resume->verified_sum) * 100, 2) : 0;
|
||||
|
||||
// percentage menunggu klik dpmptsp
|
||||
$menunggu_klik_dpmptsp_percentage = $big_data_resume->verified_sum > 0 && $menunggu_klik_dpmptsp_sum > 0
|
||||
// percentage menunggu klik dpmptsp (waiting / verified)
|
||||
$menunggu_klik_dpmptsp_percentage = $big_data_resume->verified_sum > 0 && $menunggu_klik_dpmptsp_sum >= 0
|
||||
? round(($menunggu_klik_dpmptsp_sum / $big_data_resume->verified_sum) * 100, 2) : 0;
|
||||
|
||||
// percentage proses_dinas_teknis
|
||||
$proses_dinas_teknis_percentage = $big_data_resume->verified_sum > 0 && $proses_dinas_teknis_sum > 0
|
||||
// percentage proses_dinas_teknis (process / verified)
|
||||
$proses_dinas_teknis_percentage = $big_data_resume->verified_sum > 0 && $proses_dinas_teknis_sum >= 0
|
||||
? round(($proses_dinas_teknis_sum / $big_data_resume->verified_sum) * 100, 2) : 0;
|
||||
|
||||
// percentage pbg_task_payments (payments / verified)
|
||||
$pbg_task_payments_percentage = $realisasi_terbit_pbg_sum > 0 && $pbg_task_payments_sum >= 0
|
||||
? round(($pbg_task_payments_sum / $realisasi_terbit_pbg_sum) * 100, 2) : 0;
|
||||
|
||||
$business_rab_count = $big_data_resume->business_rab_count;
|
||||
$business_krk_count = $big_data_resume->business_krk_count;
|
||||
$non_business_rab_count = $big_data_resume->non_business_rab_count;
|
||||
$non_business_krk_count = $big_data_resume->non_business_krk_count;
|
||||
$business_dlh_count = $big_data_resume->business_dlh_count;
|
||||
|
||||
$result = [
|
||||
'target_pad' => [
|
||||
'sum' => $target_pad,
|
||||
'percentage' => 100,
|
||||
],
|
||||
'tata_ruang' => [
|
||||
'sum' => $big_data_resume->spatial_sum,
|
||||
'count' => $big_data_resume->spatial_count,
|
||||
'sum' => $tata_ruang,
|
||||
'count' => $tata_ruang_count,
|
||||
'percentage' => $tata_ruang_percentage,
|
||||
],
|
||||
'kekurangan_potensi' => [
|
||||
@@ -147,6 +177,16 @@ class BigDataResumeController extends Controller
|
||||
'sum' => $proses_dinas_teknis_sum,
|
||||
'count' => $proses_dinas_teknis_count,
|
||||
'percentage' => $proses_dinas_teknis_percentage
|
||||
],
|
||||
'business_rab_count' => $business_rab_count,
|
||||
'business_krk_count' => $business_krk_count,
|
||||
'non_business_rab_count' => $non_business_rab_count,
|
||||
'non_business_krk_count' => $non_business_krk_count,
|
||||
'business_dlh_count' => $business_dlh_count,
|
||||
'pbg_task_payments' => [
|
||||
'sum' => (float) $pbg_task_payments_sum,
|
||||
'count' => $pbg_task_payments_count,
|
||||
'percentage' => $pbg_task_payments_percentage
|
||||
]
|
||||
];
|
||||
return response()->json($result);
|
||||
@@ -162,7 +202,7 @@ class BigDataResumeController extends Controller
|
||||
$query = BigdataResume::query()->orderBy('id', 'desc');
|
||||
|
||||
if($request->filled('search')){
|
||||
$query->where('name', 'LIKE', '%'.$request->input('search').'%');
|
||||
$query->where('year', 'LIKE', '%'.$request->input('search').'%');
|
||||
}
|
||||
|
||||
$query = $query->paginate(config('app.paginate_per_page', 50));
|
||||
@@ -173,10 +213,158 @@ class BigDataResumeController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
public function payment_recaps(Request $request)
|
||||
{
|
||||
try {
|
||||
$query = BigdataResume::query()->orderBy('id', 'desc');
|
||||
|
||||
if ($request->filled('start_date') && $request->filled('end_date')) {
|
||||
$startDate = Carbon::parse($request->input('start_date'))->startOfDay();
|
||||
$endDate = Carbon::parse($request->input('end_date'))->endOfDay();
|
||||
|
||||
$query->whereBetween('created_at', [$startDate, $endDate]);
|
||||
}
|
||||
|
||||
$data = $query->paginate(50);
|
||||
|
||||
// Restructure response
|
||||
$transformedData = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
$createdAt = $item->created_at;
|
||||
$id = $item->id;
|
||||
|
||||
foreach ($item->toArray() as $key => $value) {
|
||||
// Only include columns with "sum" in their names
|
||||
if (strpos($key, 'sum') !== false) {
|
||||
$transformedData[] = [
|
||||
'id' => $id,
|
||||
'category' => $key,
|
||||
'nominal' => $value,
|
||||
'created_at' => $createdAt,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => $transformedData, // Flat array
|
||||
'pagination' => [
|
||||
'total' => count($transformedData),
|
||||
'per_page' => $data->perPage(),
|
||||
'current_page' => $data->currentPage(),
|
||||
'last_page' => $data->lastPage(),
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
return response()->json(['message' => 'Error when fetching data'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function export_excel_payment_recaps(Request $request)
|
||||
{
|
||||
$startDate = null;
|
||||
$endDate = null;
|
||||
|
||||
if ($request->filled('start_date') && $request->filled('end_date')) {
|
||||
$startDate = Carbon::parse($request->input('start_date'))->startOfDay();
|
||||
$endDate = Carbon::parse($request->input('end_date'))->endOfDay();
|
||||
}
|
||||
|
||||
return Excel::download(new ReportPaymentRecapExport($startDate, $endDate), 'laporan-rekap-pembayaran.xlsx');
|
||||
}
|
||||
|
||||
public function export_pdf_payment_recaps(Request $request){
|
||||
$query = BigdataResume::query()->orderBy('id', 'desc');
|
||||
|
||||
if ($request->filled('start_date') && $request->filled('end_date')) {
|
||||
$startDate = Carbon::parse($request->input('start_date'))->startOfDay();
|
||||
$endDate = Carbon::parse($request->input('end_date'))->endOfDay();
|
||||
|
||||
$query->whereBetween('created_at', [$startDate, $endDate]);
|
||||
}
|
||||
|
||||
$items = $query->get();
|
||||
|
||||
// Define category mapping
|
||||
$categoryMap = [
|
||||
'potention_sum' => 'Potensi',
|
||||
'non_verified_sum' => 'Belum Terverifikasi',
|
||||
'verified_sum' => 'Terverifikasi',
|
||||
'business_sum' => 'Usaha',
|
||||
'non_business_sum' => 'Non Usaha',
|
||||
'spatial_sum' => 'Tata Ruang',
|
||||
'waiting_click_dpmptsp_sum' => 'Menunggu Klik DPMPTSP',
|
||||
'issuance_realization_pbg_sum' => 'Realisasi Terbit PBG',
|
||||
'process_in_technical_office_sum' => 'Proses Di Dinas Teknis',
|
||||
];
|
||||
|
||||
// Restructure response
|
||||
$data = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$createdAt = $item->created_at;
|
||||
$id = $item->id;
|
||||
|
||||
foreach ($item->toArray() as $key => $value) {
|
||||
// Only include columns with "sum" in their names
|
||||
if (strpos($key, 'sum') !== false) {
|
||||
$data[] = [
|
||||
'id' => $id,
|
||||
'category' => $categoryMap[$key] ?? $key, // Map category
|
||||
'nominal' => $value, // Format number
|
||||
'created_at' => $createdAt->format('Y-m-d H:i:s'), // Format date
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pdf = Pdf::loadView('exports.payment_recaps_report', compact('data'));
|
||||
return $pdf->download('laporan-rekap-pembayaran.pdf');
|
||||
}
|
||||
|
||||
|
||||
public function export_excel_report_director(){
|
||||
return Excel::download(new ReportDirectorExport, 'laporan-pimpinan.xlsx');
|
||||
}
|
||||
|
||||
public function export_pdf_report_director(){
|
||||
$data = BigdataResume::select(
|
||||
'potention_count',
|
||||
'potention_sum',
|
||||
'non_verified_count',
|
||||
'non_verified_sum',
|
||||
'verified_count',
|
||||
'verified_sum',
|
||||
'business_count',
|
||||
'business_sum',
|
||||
'non_business_count',
|
||||
'non_business_sum',
|
||||
'spatial_count',
|
||||
'spatial_sum',
|
||||
'waiting_click_dpmptsp_count',
|
||||
'waiting_click_dpmptsp_sum',
|
||||
'issuance_realization_pbg_count',
|
||||
'issuance_realization_pbg_sum',
|
||||
'process_in_technical_office_count',
|
||||
'process_in_technical_office_sum',
|
||||
'year',
|
||||
'created_at'
|
||||
)->orderBy('id', 'desc')->get();
|
||||
$pdf = Pdf::loadView('exports.director_report', compact('data'))->setPaper('a4', 'landscape');
|
||||
return $pdf->download('laporan-pimpinan.pdf');
|
||||
}
|
||||
private function response_empty_resume(){
|
||||
$data_settings = DataSetting::all();
|
||||
$target_pad = 0;
|
||||
if($data_settings->where('key', 'TARGET_PAD')->first()){
|
||||
$target_pad = floatval($data_settings->where('key', 'TARGET_PAD')->first()->value ?? 0);
|
||||
}
|
||||
|
||||
$result = [
|
||||
'target_pad' => [
|
||||
'sum' => 0,
|
||||
'sum' => $target_pad,
|
||||
'percentage' => 100,
|
||||
],
|
||||
'tata_ruang' => [
|
||||
@@ -226,9 +414,97 @@ class BigDataResumeController extends Controller
|
||||
'sum' => 0,
|
||||
'count' => 0,
|
||||
'percentage' => 0
|
||||
],
|
||||
'pbg_task_payments' => [
|
||||
'sum' => 0,
|
||||
'count' => 0,
|
||||
'percentage' => 0
|
||||
]
|
||||
];
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get spatial planning data using new calculation formula
|
||||
*/
|
||||
private function getSpatialPlanningData(): array
|
||||
{
|
||||
try {
|
||||
// Get spatial plannings that are not yet issued (is_terbit = false) and have valid data
|
||||
$spatialPlannings = SpatialPlanning::where('land_area', '>', 0)
|
||||
->where('site_bcr', '>', 0)
|
||||
->where('is_terbit', false)
|
||||
->get();
|
||||
|
||||
$totalSum = 0;
|
||||
$businessCount = 0;
|
||||
$nonBusinessCount = 0;
|
||||
|
||||
foreach ($spatialPlannings as $spatialPlanning) {
|
||||
// Use new calculation formula: LUAS LAHAN × BCR × HARGA SATUAN
|
||||
$calculatedAmount = $spatialPlanning->calculated_retribution;
|
||||
$totalSum += $calculatedAmount;
|
||||
|
||||
// Count business types
|
||||
if ($spatialPlanning->is_business_type) {
|
||||
$businessCount++;
|
||||
} else {
|
||||
$nonBusinessCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Log::info("Real-time Spatial Planning Data (is_terbit = false only)", [
|
||||
'total_records' => $spatialPlannings->count(),
|
||||
'business_count' => $businessCount,
|
||||
'non_business_count' => $nonBusinessCount,
|
||||
'total_sum' => $totalSum,
|
||||
'filtered_by' => 'is_terbit = false'
|
||||
]);
|
||||
|
||||
return [
|
||||
'count' => $spatialPlannings->count(),
|
||||
'sum' => (float) $totalSum,
|
||||
'business_count' => $businessCount,
|
||||
'non_business_count' => $nonBusinessCount,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error getting spatial planning data", ['error' => $e->getMessage()]);
|
||||
return [
|
||||
'count' => 0,
|
||||
'sum' => 0.0,
|
||||
'business_count' => 0,
|
||||
'non_business_count' => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PBG Task Payments data from database
|
||||
*/
|
||||
private function getPbgTaskPaymentsData(): array
|
||||
{
|
||||
try {
|
||||
// Get sum and count from PbgTaskPayment model
|
||||
$stats = PbgTaskPayment::whereNotNull('payment_date_raw')
|
||||
->whereNotNull('retribution_total_pad')
|
||||
->whereYear('payment_date_raw', date('Y'))
|
||||
->selectRaw('SUM(retribution_total_pad) as total_sum, COUNT(*) as total_count')
|
||||
->first();
|
||||
|
||||
$totalSum = $stats->total_sum ?? 0;
|
||||
$totalCount = $stats->total_count ?? 0;
|
||||
|
||||
return [
|
||||
'sum' => (float) $totalSum,
|
||||
'count' => (int) $totalCount,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error getting PBG task payments data", ['error' => $e->getMessage()]);
|
||||
return [
|
||||
'sum' => 0.0,
|
||||
'count' => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class ChatbotController extends Controller
|
||||
};
|
||||
|
||||
$chatHistory = $request->input('chatHistory');
|
||||
// Log::info('Chat history sebelum disimpan:', ['history' => $chatHistory]);
|
||||
Log::info('Chat history sebelum disimpan:', ['history' => $chatHistory]);
|
||||
|
||||
if ($main_content === "UNKNOWN") {
|
||||
return response()->json(['response' => 'Invalid tab_active value.'], 400);
|
||||
@@ -44,14 +44,15 @@ class ChatbotController extends Controller
|
||||
|
||||
$queryResponse = $this->openAIService->generateQueryBasedMainContent($request->input('prompt'), $main_content, $chatHistory);
|
||||
|
||||
$firstValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
|
||||
$secondValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
|
||||
if (str_contains($queryResponse, 'tidak relevan') || str_contains($queryResponse, 'tidak valid') || str_starts_with($queryResponse, 'Prompt')) {
|
||||
return response()->json(['response' => $queryResponse], 400);
|
||||
}
|
||||
|
||||
$formattedResultQuery = "[]";
|
||||
$queryResponse = str_replace(['```sql', '```'], '', $queryResponse);
|
||||
$resultQuery = DB::select($queryResponse);
|
||||
$formattedResultQuery = json_encode($resultQuery, JSON_PRETTY_PRINT);
|
||||
// info($formattedResultQuery);
|
||||
info($formattedResultQuery);
|
||||
|
||||
$nlpResult = $this->openAIService->generateNLPFromQuery($request->input('prompt'), $formattedResultQuery);
|
||||
$finalGeneratedText =$this->openAIService->generateFinalText($nlpResult);
|
||||
@@ -92,9 +93,6 @@ class ChatbotController extends Controller
|
||||
$queryResponse = $this->openAIService->createMainQuery($classifyResponse, $request->input('prompt'), $chatHistory);
|
||||
info($queryResponse);
|
||||
|
||||
$firstValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
|
||||
$secondValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
|
||||
|
||||
$formattedResultQuery = "[]";
|
||||
|
||||
$queryResponse = str_replace(['```sql', '```'], '', $queryResponse);
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Http\Resources\CustomersResource;
|
||||
use App\Imports\CustomersImport;
|
||||
use App\Models\Customer;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
|
||||
class CustomersController extends Controller
|
||||
@@ -120,7 +121,7 @@ class CustomersController extends Controller
|
||||
'message' => 'File uploaded successfully',
|
||||
]);
|
||||
}catch(\Exception $e){
|
||||
\Log::info($e->getMessage());
|
||||
Log::info($e->getMessage());
|
||||
return response()->json([
|
||||
'error' => 'Failed to upload file',
|
||||
'message' => $e->getMessage()
|
||||
|
||||
@@ -34,7 +34,7 @@ class GlobalSettingsController extends Controller
|
||||
try {
|
||||
$data = GlobalSetting::create($request->validated());
|
||||
return new GlobalSettingResource($data);
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
return $this->resError($e->getMessage(), null, $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,33 +3,15 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\GoogleSheetService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GoogleSheetController extends Controller
|
||||
{
|
||||
protected $googleSheetService;
|
||||
public function __construct(GoogleSheetService $googleSheetService){
|
||||
$this->googleSheetService = $googleSheetService;
|
||||
}
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
$dataCollection = $this->googleSheetService->getSheetDataCollection();
|
||||
$result = [
|
||||
"last_row" => $this->googleSheetService->getLastRowByColumn("C"),
|
||||
"last_column" => $this->googleSheetService->getLastColumn(),
|
||||
"header" => $this->googleSheetService->getHeader(),
|
||||
"data_collection" => $dataCollection
|
||||
];
|
||||
return response()->json($result);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
|
||||
91
app/Http/Controllers/Api/GrowthReportAPIController.php
Normal file
91
app/Http/Controllers/Api/GrowthReportAPIController.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\BigdataResume;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class GrowthReportAPIController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
// Get current date
|
||||
$today = Carbon::today();
|
||||
|
||||
// Define default range: 1 month back from today
|
||||
$defaultStart = $today->copy()->subMonth();
|
||||
$defaultEnd = $today;
|
||||
|
||||
// Use request values if provided, else use defaults
|
||||
// $startDate = $request->input('start_date', $defaultStart->toDateString());
|
||||
// $endDate = $request->input('end_date', $defaultEnd->toDateString());
|
||||
|
||||
// Optional year filter (used if specified)
|
||||
$year = $request->input('year', now()->year);
|
||||
|
||||
// $query = BigdataResume::selectRaw("
|
||||
// DATE(created_at) as date,
|
||||
// SUM(potention_sum) as potention_sum,
|
||||
// SUM(verified_sum) as verified_sum,
|
||||
// SUM(non_verified_sum) as non_verified_sum
|
||||
// ")
|
||||
// ->whereBetween('created_at', [$startDate, $endDate]);
|
||||
$query = BigdataResume::selectRaw("
|
||||
DATE(created_at) as date,
|
||||
SUM(potention_sum) as potention_sum,
|
||||
SUM(verified_sum) as verified_sum,
|
||||
SUM(non_verified_sum) as non_verified_sum
|
||||
");
|
||||
|
||||
$query->whereNotNull('year')
|
||||
->where('year', '!=', 'all');
|
||||
|
||||
$data = $query->groupBy(DB::raw('DATE(created_at)'))
|
||||
->orderBy(DB::raw('DATE(created_at)'))
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
$item->date = Carbon::parse($item->date)->format('d M Y');
|
||||
return $item;
|
||||
});
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ use App\Models\Customer;
|
||||
use App\Models\SpatialPlanning;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\TourismBasedKBLI;
|
||||
use App\Models\Tax;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class LackOfPotentialController extends Controller
|
||||
{
|
||||
@@ -17,13 +19,28 @@ class LackOfPotentialController extends Controller
|
||||
$total_reklame = Advertisement::count();
|
||||
$total_pdam = Customer::count();
|
||||
$total_tata_ruang = SpatialPlanning::count();
|
||||
$total_tata_ruang_usaha = SpatialPlanning::where('building_function','like', '%usaha%')->count();
|
||||
$total_tata_ruang_non_usaha = SpatialPlanning::where('building_function','not like', '%usaha%')->count();
|
||||
$data_report_tourism = TourismBasedKBLI::all();
|
||||
$data_pajak_reklame = Tax::where('tax_code','Reklame')->distinct('business_name')->count();
|
||||
$data_pajak_restoran = Tax::where('tax_code','Restoran')->distinct('business_name')->count();
|
||||
$data_pajak_hiburan = Tax::where('tax_code','Hiburan')->distinct('business_name')->count();
|
||||
$data_pajak_hotel = Tax::where('tax_code','Hotel')->distinct('business_name')->count();
|
||||
$data_pajak_parkir = Tax::where('tax_code','Parkir')->distinct('business_name')->count();
|
||||
|
||||
return response()->json([
|
||||
'total_reklame' => $total_reklame,
|
||||
'total_pdam' => $total_pdam,
|
||||
'total_tata_ruang' => $total_tata_ruang,
|
||||
'total_tata_ruang_usaha' => $total_tata_ruang_usaha,
|
||||
'total_tata_ruang_non_usaha' => $total_tata_ruang_non_usaha,
|
||||
'data_report' => $data_report_tourism,
|
||||
'data_pajak_reklame' => $data_pajak_reklame,
|
||||
'data_pajak_restoran' => $data_pajak_restoran,
|
||||
'data_pajak_hiburan' => $data_pajak_hiburan,
|
||||
'data_pajak_hotel' => $data_pajak_hotel,
|
||||
'data_pajak_parkir' => $data_pajak_parkir,
|
||||
'tata_ruang' => $this->getSpatialPlanningData()
|
||||
], 200);
|
||||
}catch(\Exception $e){
|
||||
return response()->json([
|
||||
@@ -31,4 +48,63 @@ class LackOfPotentialController extends Controller
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function getSpatialPlanningData(): array
|
||||
{
|
||||
try {
|
||||
// Get spatial plannings that are not yet issued (is_terbit = false) and have valid data
|
||||
$spatialPlannings = SpatialPlanning::where('land_area', '>', 0)
|
||||
->where('site_bcr', '>', 0)
|
||||
->where('is_terbit', false)
|
||||
->get();
|
||||
|
||||
$totalSum = 0;
|
||||
$businessCount = 0;
|
||||
$nonBusinessCount = 0;
|
||||
$businessSum = 0;
|
||||
$nonBusinessSum = 0;
|
||||
|
||||
foreach ($spatialPlannings as $spatialPlanning) {
|
||||
// Use new calculation formula: LUAS LAHAN × BCR × HARGA SATUAN
|
||||
$calculatedAmount = $spatialPlanning->calculated_retribution;
|
||||
$totalSum += $calculatedAmount;
|
||||
|
||||
// Count business types
|
||||
if ($spatialPlanning->is_business_type) {
|
||||
$businessCount++;
|
||||
$businessSum += $calculatedAmount;
|
||||
} else {
|
||||
$nonBusinessCount++;
|
||||
$nonBusinessSum += $calculatedAmount;
|
||||
}
|
||||
}
|
||||
|
||||
Log::info("Real-time Spatial Planning Data (is_terbit = false only)", [
|
||||
'total_records' => $spatialPlannings->count(),
|
||||
'business_count' => $businessCount,
|
||||
'non_business_count' => $nonBusinessCount,
|
||||
'total_sum' => $totalSum,
|
||||
'filtered_by' => 'is_terbit = false'
|
||||
]);
|
||||
|
||||
return [
|
||||
'count' => $spatialPlannings->count(),
|
||||
'sum' => (float) $totalSum,
|
||||
'business_count' => $businessCount,
|
||||
'non_business_count' => $nonBusinessCount,
|
||||
'business_sum' => (float) $businessSum,
|
||||
'non_business_sum' => (float) $nonBusinessSum,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error getting spatial planning data", ['error' => $e->getMessage()]);
|
||||
return [
|
||||
'count' => 0,
|
||||
'sum' => 0.0,
|
||||
'business_count' => 0,
|
||||
'non_business_count' => 0,
|
||||
'business_sum' => 0.0,
|
||||
'non_business_sum' => 0.0,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ class MenusController extends Controller
|
||||
$query = $query->where("name", "like", "%".$request->get("search")."%");
|
||||
}
|
||||
|
||||
return response()->json($query->paginate(config('app.paginate_per_page', 50)));
|
||||
// return response()->json($query->paginate(config('app.paginate_per_page', 50)));
|
||||
return MenuResource::collection($query->paginate(config('app.paginate_per_page',50)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
109
app/Http/Controllers/Api/PbgTaskAttachmentsController.php
Normal file
109
app/Http/Controllers/Api/PbgTaskAttachmentsController.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PbgTaskAttachment;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class PbgTaskAttachmentsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request, $pbg_task_id)
|
||||
{
|
||||
try{
|
||||
$request->validate([
|
||||
'file' => 'required|file|mimes:jpg,png,pdf|max:5120',
|
||||
'pbg_type' => 'string'
|
||||
]);
|
||||
|
||||
$attachment = PbgTaskAttachment::create([
|
||||
'pbg_task_id' => $pbg_task_id,
|
||||
'file_name' => $request->file('file')->getClientOriginalName(),
|
||||
'file_path' => '', // empty path initially
|
||||
'pbg_type' => $request->pbg_type == 'bukti_bayar' ? 'bukti_bayar' : 'berita_acara'
|
||||
]);
|
||||
|
||||
$file = $request->file('file');
|
||||
$path = $file->store("uploads/pbg-tasks/{$pbg_task_id}/{$attachment->id}", "public");
|
||||
|
||||
$attachment->update([
|
||||
'file_path' => $path,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'File uploaded successfully.',
|
||||
'attachment' => [
|
||||
'id' => $attachment->id,
|
||||
'file_name' => $attachment->file_name,
|
||||
'file_url' => Storage::url($attachment->file_path),
|
||||
'pbg_type' => $attachment->pbg_type
|
||||
]
|
||||
]);
|
||||
}catch(\Exception $e){
|
||||
\Log::error($e->getMessage());
|
||||
return response()->json([
|
||||
"success" => false,
|
||||
"message" => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function download(string $id)
|
||||
{
|
||||
try {
|
||||
$data = PbgTaskAttachment::findOrFail($id);
|
||||
$filePath = $data->file_path; // already relative to 'public' disk
|
||||
|
||||
if (!Storage::disk('public')->exists($filePath)) {
|
||||
return response()->json([
|
||||
"success" => false,
|
||||
"message" => "File not found on server"
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return Storage::disk('public')->download($filePath, $data->file_name);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
"success" => false,
|
||||
"message" => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Enums\ImportDatasourceStatus;
|
||||
use App\Enums\PbgTaskApplicationTypes;
|
||||
use App\Enums\PbgTaskStatus;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\PbgTaskMultiStepRequest;
|
||||
use App\Http\Resources\PbgTaskResource;
|
||||
@@ -14,6 +16,7 @@ use App\Services\GoogleSheetService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
|
||||
class PbgTaskController extends Controller
|
||||
{
|
||||
@@ -116,9 +119,72 @@ class PbgTaskController extends Controller
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
public function update(Request $request, string $task_uuid)
|
||||
{
|
||||
//
|
||||
try{
|
||||
$pbg_task = PbgTask::where('uuid',$task_uuid)->first();
|
||||
|
||||
if(!$pbg_task){
|
||||
return response()->json([
|
||||
"success"=> false,
|
||||
"message"=> "Data PBG Task tidak ditemukan",
|
||||
], 404);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'nullable|string|max:255',
|
||||
'owner_name' => 'nullable|string|max:255',
|
||||
'application_type' => ['nullable', new Enum(PbgTaskApplicationTypes::class)],
|
||||
'condition' => 'nullable|string|max:255',
|
||||
'registration_number' => 'nullable|string|max:255',
|
||||
'document_number' => 'nullable|string|max:255',
|
||||
'status' => ['nullable', new Enum(PbgTaskStatus::class)],
|
||||
'address' => 'nullable|string|max:255',
|
||||
'slf_status_name' => 'nullable|string|max:255',
|
||||
'function_type' => 'nullable|string|max:255',
|
||||
'consultation_type' => 'nullable|string|max:255',
|
||||
'due_date' => 'nullable|date',
|
||||
'is_valid' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
$statusLabel = $validated['status'] !== null ? PbgTaskStatus::getLabel($validated['status']) : null;
|
||||
$applicationLabel = $validated['application_type'] !== null ? PbgTaskApplicationTypes::getLabel($validated['application_type']) : null;
|
||||
|
||||
// Prepare update data - only include fields that are actually provided
|
||||
$updateData = [];
|
||||
|
||||
foreach ($validated as $key => $value) {
|
||||
if ($value !== null || $request->has($key)) {
|
||||
$updateData[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle special cases for labels
|
||||
if (isset($updateData['status'])) {
|
||||
$updateData['status_name'] = $statusLabel;
|
||||
}
|
||||
|
||||
if (isset($updateData['application_type'])) {
|
||||
$updateData['application_type_name'] = $applicationLabel;
|
||||
}
|
||||
|
||||
// Handle is_valid specifically
|
||||
if ($request->has('is_valid')) {
|
||||
$updateData['is_valid'] = $validated['is_valid'];
|
||||
}
|
||||
|
||||
$pbg_task->update($updateData);
|
||||
return response()->json([
|
||||
"success"=> true,
|
||||
"message"=> "Data berhasil diubah",
|
||||
"data"=> $pbg_task
|
||||
]);
|
||||
}catch(\Exception $e){
|
||||
return response()->json([
|
||||
"success"=> false,
|
||||
"message"=> $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,255 +201,4 @@ class PbgTaskController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function syncPbgFromGoogleSheet(){
|
||||
$import_datasource = ImportDatasource::create([
|
||||
"message" => "initialization",
|
||||
"response_body" => null,
|
||||
"status" => ImportDatasourceStatus::Processing->value,
|
||||
]);
|
||||
try{
|
||||
$totalRowCount = $this->googleSheetService->getLastRowByColumn("C");
|
||||
$sheetData = $this->googleSheetService->getSheetDataCollection($totalRowCount);
|
||||
$sheet_big_data = $this->googleSheetService->get_data_by_sheet();
|
||||
$data_setting_result = []; // Initialize result storage
|
||||
|
||||
$found_section = null; // Track which section is found
|
||||
|
||||
foreach ($sheet_big_data as $row) {
|
||||
// Check for section headers
|
||||
if (in_array("•PROSES PENERBITAN:", $row)) {
|
||||
$found_section = "MENUNGGU_KLIK_DPMPTSP";
|
||||
} elseif (in_array("•BERKAS AKTUAL TERVERIFIKASI DINAS TEKNIS 2024:", $row)) {
|
||||
$found_section = "REALISASI_TERBIT_PBG";
|
||||
} elseif (in_array("•TERPROSES DI DPUTR: belum selesai rekomtek'", $row)) {
|
||||
$found_section = "PROSES_DINAS_TEKNIS";
|
||||
}
|
||||
|
||||
// If a section is found and we reach "Grand Total", save the corresponding values
|
||||
if ($found_section && isset($row[0]) && trim($row[0]) === "Grand Total") {
|
||||
if ($found_section === "MENUNGGU_KLIK_DPMPTSP") {
|
||||
$data_setting_result["MENUNGGU_KLIK_DPMPTSP_COUNT"] = $row[2] ?? null;
|
||||
$data_setting_result["MENUNGGU_KLIK_DPMPTSP_SUM"] = $row[3] ?? null;
|
||||
} elseif ($found_section === "REALISASI_TERBIT_PBG") {
|
||||
$data_setting_result["REALISASI_TERBIT_PBG_COUNT"] = $row[2] ?? null;
|
||||
$data_setting_result["REALISASI_TERBIT_PBG_SUM"] = $row[4] ?? null;
|
||||
} elseif ($found_section === "PROSES_DINAS_TEKNIS") {
|
||||
$data_setting_result["PROSES_DINAS_TEKNIS_COUNT"] = $row[2] ?? null;
|
||||
$data_setting_result["PROSES_DINAS_TEKNIS_SUM"] = $row[3] ?? null;
|
||||
}
|
||||
|
||||
// Reset section tracking after capturing "Grand Total"
|
||||
$found_section = null;
|
||||
}
|
||||
}
|
||||
foreach ($data_setting_result as $key => $value) {
|
||||
DataSetting::updateOrInsert(
|
||||
["key" => $key], // Find by key
|
||||
["value" => $value] // Update or insert value
|
||||
);
|
||||
}
|
||||
$mapToUpsert = [];
|
||||
$count = 0;
|
||||
|
||||
foreach($sheetData as $data){
|
||||
$mapToUpsert[] =
|
||||
[
|
||||
'no_registrasi' => $data['no__registrasi'] ?? null,
|
||||
'jenis_konsultasi' => $data['jenis_konsultasi'] ?? null,
|
||||
'fungsi_bg' => $data['fungsi_bg'] ?? null,
|
||||
'tgl_permohonan' => $this->convertToDate($data['tgl_permohonan']),
|
||||
'status_verifikasi' => $data['status_verifikasi'] ?? null,
|
||||
'status_permohonan' => $this->convertToDate($data['status_permohonan']),
|
||||
'alamat_pemilik' => $data['alamat_pemilik'] ?? null,
|
||||
'no_hp' => $data['no__hp'] ?? null,
|
||||
'email' => $data['e_mail'] ?? null,
|
||||
'tanggal_catatan' => $this->convertToDate($data['tanggal_catatan']),
|
||||
'catatan_kekurangan_dokumen' => $data['catatan_kekurangan_dokumen'] ?? null,
|
||||
'gambar' => $data['gambar'] ?? null,
|
||||
'krk_kkpr' => $data['krk_kkpr'] ?? null,
|
||||
'no_krk' => $data['no__krk'] ?? null,
|
||||
'lh' => $data['lh'] ?? null,
|
||||
'ska' => $data['ska'] ?? null,
|
||||
'keterangan' => $data['keterangan'] ?? null,
|
||||
'helpdesk' => $data['helpdesk'] ?? null,
|
||||
'pj' => $data['pj'] ?? null,
|
||||
'kepemilikan' => $data['kepemilikan'] ?? null,
|
||||
'potensi_taru' => $data['potensi_taru'] ?? null,
|
||||
'validasi_dinas' => $data['validasi_dinas'] ?? null,
|
||||
'kategori_retribusi' => $data['kategori_retribusi'] ?? null,
|
||||
'no_urut_ba_tpt' => $data['no__urut_ba_tpt__2024_0001_'] ?? null,
|
||||
'tanggal_ba_tpt' => $this->convertToDate($data['tanggal_ba_tpt']),
|
||||
'no_urut_ba_tpa' => $data['no__urut_ba_tpa'] ?? null,
|
||||
'tanggal_ba_tpa' => $this->convertToDate($data['tanggal_ba_tpa']),
|
||||
'no_urut_skrd' => $data['no__urut_skrd__2024_0001_'] ?? null,
|
||||
'tanggal_skrd' => $this->convertToDate($data['tanggal_skrd']),
|
||||
'ptsp' => $data['ptsp'] ?? null,
|
||||
'selesai_terbit' => $data['selesai_terbit'] ?? null,
|
||||
'tanggal_pembayaran' => $this->convertToDate($data['tanggal_pembayaran__yyyy_mm_dd_']),
|
||||
'format_sts' => $data['format_sts'] ?? null,
|
||||
'tahun_terbit' => (int) $data['tahun_terbit'] ?? null,
|
||||
'tahun_berjalan' => (int) $data['tahun_berjalan'] ?? null,
|
||||
'kelurahan' => $data['kelurahan'] ?? null,
|
||||
'kecamatan' => $data['kecamatan'] ?? null,
|
||||
'lb' => $this->convertToDecimal($data['lb']) ?? null,
|
||||
'tb' => $this->convertToDecimal($data['tb']) ?? null,
|
||||
'jlb' => (int) $data['jlb'] ?? null,
|
||||
'unit' => (int) $data['unit'] ?? null,
|
||||
'usulan_retribusi' => (int) $data['usulan_retribusi'] ?? null,
|
||||
'nilai_retribusi_keseluruhan_simbg' => $this->convertToDecimal($data['nilai_retribusi_keseluruhan__simbg_']) ?? null,
|
||||
'nilai_retribusi_keseluruhan_pad' => $this->convertToDecimal($data['nilai_retribusi_keseluruhan__pad_']) ?? null,
|
||||
'denda' => $this->convertToDecimal($data['denda']) ?? null,
|
||||
'latitude' => $data['latitude'] ?? null,
|
||||
'longitude' => $data['longitude'] ?? null,
|
||||
'nik_nib' => $data['nik_nib'] ?? null,
|
||||
'dok_tanah' => $data['dok__tanah'] ?? null,
|
||||
'temuan' => $data['temuan'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
$batchSize = 1000;
|
||||
$chunks = array_chunk($mapToUpsert, $batchSize);
|
||||
|
||||
foreach($chunks as $chunk){
|
||||
PbgTaskGoogleSheet::upsert($chunk, ["no_registrasi"],[
|
||||
'jenis_konsultasi',
|
||||
'nama_pemilik',
|
||||
'lokasi_bg',
|
||||
'fungsi_bg',
|
||||
'nama_bangunan',
|
||||
'tgl_permohonan',
|
||||
'status_verifikasi',
|
||||
'status_permohonan',
|
||||
'alamat_pemilik',
|
||||
'no_hp',
|
||||
'email',
|
||||
'tanggal_catatan',
|
||||
'catatan_kekurangan_dokumen',
|
||||
'gambar',
|
||||
'krk_kkpr',
|
||||
'no_krk',
|
||||
'lh',
|
||||
'ska',
|
||||
'keterangan',
|
||||
'helpdesk',
|
||||
'pj',
|
||||
'kepemilikan',
|
||||
'potensi_taru',
|
||||
'validasi_dinas',
|
||||
'kategori_retribusi',
|
||||
'no_urut_ba_tpt',
|
||||
'tanggal_ba_tpt',
|
||||
'no_urut_ba_tpa',
|
||||
'tanggal_ba_tpa',
|
||||
'no_urut_skrd',
|
||||
'tanggal_skrd',
|
||||
'ptsp',
|
||||
'selesai_terbit',
|
||||
'tanggal_pembayaran',
|
||||
'format_sts',
|
||||
'tahun_terbit',
|
||||
'tahun_berjalan',
|
||||
'kelurahan',
|
||||
'kecamatan',
|
||||
'lb',
|
||||
'tb',
|
||||
'jlb',
|
||||
'unit',
|
||||
'usulan_retribusi',
|
||||
'nilai_retribusi_keseluruhan_simbg',
|
||||
'nilai_retribusi_keseluruhan_pad',
|
||||
'denda',
|
||||
'latitude',
|
||||
'longitude',
|
||||
'nik_nib',
|
||||
'dok_tanah',
|
||||
'temuan',
|
||||
]);
|
||||
}
|
||||
|
||||
$total_data = count($mapToUpsert);
|
||||
|
||||
$import_datasource->update([
|
||||
"message" => "Successfully processed: {$total_data}",
|
||||
"status" => ImportDatasourceStatus::Success->value,
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
"success" => true,
|
||||
"message" => "Data berhasil disimpan ke database"
|
||||
], 200);
|
||||
}catch(\Exception $ex){
|
||||
DB::rollBack();
|
||||
$import_datasource->update([
|
||||
"message" => "Failed to importing",
|
||||
"response_body" => $ex->getMessage(),
|
||||
"status" => ImportDatasourceStatus::Failed->value,
|
||||
]);
|
||||
return response()->json([
|
||||
"success" => false,
|
||||
"message" => "Gagal menyimpan data",
|
||||
"error" => $ex->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
protected function convertToDecimal(?string $value): ?float
|
||||
{
|
||||
if (empty($value)) {
|
||||
return null; // Return null if the input is empty
|
||||
}
|
||||
|
||||
// Remove all non-numeric characters except comma and dot
|
||||
$value = preg_replace('/[^0-9,\.]/', '', $value);
|
||||
|
||||
// If the number contains both dot (.) and comma (,)
|
||||
if (strpos($value, '.') !== false && strpos($value, ',') !== false) {
|
||||
$value = str_replace('.', '', $value); // Remove thousands separator
|
||||
$value = str_replace(',', '.', $value); // Convert decimal separator to dot
|
||||
}
|
||||
// If only a dot is present (assumed as thousands separator)
|
||||
elseif (strpos($value, '.') !== false) {
|
||||
$value = str_replace('.', '', $value); // Remove all dots (treat as thousands separators)
|
||||
}
|
||||
// If only a comma is present (assumed as decimal separator)
|
||||
elseif (strpos($value, ',') !== false) {
|
||||
$value = str_replace(',', '.', $value); // Convert comma to dot (decimal separator)
|
||||
}
|
||||
|
||||
// Ensure the value is numeric before returning
|
||||
return is_numeric($value) ? (float) number_format((float) $value, 2, '.', '') : null;
|
||||
}
|
||||
|
||||
protected function convertToInteger($value) {
|
||||
// Check if the value is an empty string, and return null if true
|
||||
if (trim($value) === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise, cast to integer
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
protected function convertToDate($dateString)
|
||||
{
|
||||
try {
|
||||
// Check if the string is empty
|
||||
if (empty($dateString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to parse the date string
|
||||
$date = Carbon::parse($dateString);
|
||||
|
||||
// Return the Carbon instance
|
||||
return $date->format('Y-m-d');
|
||||
} catch (\Exception $e) {
|
||||
// Return null if an error occurs during parsing
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
61
app/Http/Controllers/Api/PbgTaskGoogleSheetsController.php
Normal file
61
app/Http/Controllers/Api/PbgTaskGoogleSheetsController.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\PbgTaskGoogleSheetResource;
|
||||
use App\Models\PbgTaskGoogleSheet;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PbgTaskGoogleSheetsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = PbgTaskGoogleSheet::query()->orderBy('id', 'desc');
|
||||
if ($request->filled('search')) {
|
||||
$query->where('no_registrasi', 'like', "%{$request->get('search')}%");
|
||||
}
|
||||
return PbgTaskGoogleSheetResource::collection($query->paginate(config('app.paginate_per_page', 50)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
try{
|
||||
$data = PbgTaskGoogleSheet::find($id);
|
||||
$data->delete();
|
||||
return response()->json(['message' => 'Data deleted successfully'], 200);
|
||||
}catch(\Exception $e){
|
||||
return response()->json(['message' => 'Failed to delete data'], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
app/Http/Controllers/Api/ReportPbgPtspController.php
Normal file
29
app/Http/Controllers/Api/ReportPbgPtspController.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Exports\ReportPbgPtspExport;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PbgTask;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
|
||||
class ReportPbgPtspController extends Controller
|
||||
{
|
||||
public function export_excel(){
|
||||
return Excel::download(new ReportPbgPtspExport, 'laporan-ptsp.xlsx');
|
||||
}
|
||||
public function export_pdf(){
|
||||
$data = PbgTask::select(
|
||||
'status',
|
||||
'status_name', // Keeping this column
|
||||
DB::raw('COUNT(*) as total')
|
||||
)
|
||||
->groupBy('status', 'status_name')
|
||||
->get();
|
||||
$pdf = Pdf::loadView('exports.ptsp_report', compact('data'));
|
||||
return $pdf->download('laporan-ptsp.pdf');
|
||||
}
|
||||
}
|
||||
22
app/Http/Controllers/Api/ReportTourismsController.php
Normal file
22
app/Http/Controllers/Api/ReportTourismsController.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Exports\ReportTourismExport;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\TourismBasedKBLI;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Http\Request;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
|
||||
class ReportTourismsController extends Controller
|
||||
{
|
||||
public function export_excel(){
|
||||
return Excel::download(new ReportTourismExport, 'laporan-pariwisata.xlsx');
|
||||
}
|
||||
public function export_pdf(){
|
||||
$data = TourismBasedKBLI::all();
|
||||
$pdf = Pdf::loadView('exports.tourisms_report', compact('data'));
|
||||
return $pdf->download('laporan-pariwisata.pdf');
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,18 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Exports\DistrictPaymentRecapExport;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\RequestAssignmentResouce;
|
||||
use App\Models\PbgTask;
|
||||
use App\Models\PbgTaskGoogleSheet;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use App\Enums\PbgTaskStatus;
|
||||
|
||||
class RequestAssignmentController extends Controller
|
||||
{
|
||||
@@ -14,15 +22,425 @@ class RequestAssignmentController extends Controller
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = PbgTask::query()->orderBy('id', 'desc');
|
||||
if($request->has('search') && !empty($request->get("search"))){
|
||||
$query->where('name', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('registration_number', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('document_number', 'LIKE', '%'.$request->get('search').'%');
|
||||
// Build base query for counting (without relationships to avoid duplicates)
|
||||
$baseQuery = PbgTask::query();
|
||||
|
||||
// Always filter only valid data (is_valid = true)
|
||||
$baseQuery->where('is_valid', true);
|
||||
|
||||
// Apply year filter if provided (to match BigdataResume behavior)
|
||||
if ($request->has('year') && !empty($request->get('year'))) {
|
||||
$year = $request->get('year');
|
||||
$baseQuery->where('due_date', '>=', $year.'-02-01');
|
||||
Log::info('RequestAssignmentController year filter applied', ['year' => $year]);
|
||||
}
|
||||
return RequestAssignmentResouce::collection($query->paginate());
|
||||
|
||||
// Get filter value, default to 'all' if not provided or empty
|
||||
$filter = $request->has('filter') && !empty($request->get('filter'))
|
||||
? strtolower(trim($request->get('filter')))
|
||||
: 'all';
|
||||
|
||||
// Log filter for debugging
|
||||
Log::info('RequestAssignmentController filter applied', ['filter' => $filter, 'original' => $request->get('filter')]);
|
||||
|
||||
// Apply filters to base query using single consolidated method
|
||||
$this->applyFilter($baseQuery, $filter);
|
||||
|
||||
// Get accurate count from base query (without relationships)
|
||||
$accurateCount = $baseQuery->count();
|
||||
|
||||
// Clone the base query for data fetching with relationships
|
||||
$dataQuery = clone $baseQuery;
|
||||
|
||||
$dataQuery->with([
|
||||
'attachments' => function ($q) {
|
||||
$q->whereIn('pbg_type', ['berita_acara', 'bukti_bayar']);
|
||||
},
|
||||
'pbg_task_retributions',
|
||||
'pbg_task_detail',
|
||||
'pbg_status'
|
||||
])->orderBy('id', 'desc');
|
||||
|
||||
// Log final query count for debugging
|
||||
Log::info('RequestAssignmentController final result', [
|
||||
'filter' => $filter,
|
||||
'search' => $request->get('search'),
|
||||
'year' => $request->get('year'),
|
||||
'accurate_count' => $accurateCount,
|
||||
'request_url' => $request->fullUrl(),
|
||||
'all_params' => $request->all()
|
||||
]);
|
||||
|
||||
// Cross-validation with BigdataResume logic (for debugging consistency)
|
||||
if ($filter !== 'all' && $request->has('year') && !empty($request->get('year'))) {
|
||||
$this->validateConsistencyWithBigdataResume($filter, $request->get('year'), $accurateCount);
|
||||
}
|
||||
|
||||
// Apply search to data query
|
||||
if ($request->has('search') && !empty($request->get("search"))) {
|
||||
$this->applySearch($dataQuery, $request->get('search'));
|
||||
}
|
||||
|
||||
// Additional logging for potention filter
|
||||
if ($filter === 'potention') {
|
||||
$rejectedCount = PbgTask::whereIn('status', PbgTaskStatus::getRejected())->count();
|
||||
Log::info('Potention filter details', [
|
||||
'potention_count' => $accurateCount,
|
||||
'rejected_count' => $rejectedCount,
|
||||
'total_all_records' => PbgTask::count(),
|
||||
'note' => 'Potention filter excludes rejected data'
|
||||
]);
|
||||
}
|
||||
|
||||
// Also log to console for immediate debugging
|
||||
if ($filter !== 'all') {
|
||||
error_log('RequestAssignment Filter Debug: ' . $filter . ' -> Count: ' . $accurateCount);
|
||||
}
|
||||
|
||||
// Get paginated results with relationships
|
||||
$paginatedResults = $dataQuery->paginate();
|
||||
|
||||
// Append query parameters to pagination
|
||||
$paginatedResults->appends($request->query());
|
||||
|
||||
return RequestAssignmentResouce::collection($paginatedResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter logic to the query
|
||||
*/
|
||||
private function applyFilter($query, string $filter)
|
||||
{
|
||||
switch ($filter) {
|
||||
case 'all':
|
||||
// No additional filters, just return all valid records
|
||||
break;
|
||||
case 'non-business':
|
||||
// Non-business: function_type NOT LIKE usaha AND (unit IS NULL OR unit <= 1)
|
||||
$query->where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
$q2->where(function ($q3) {
|
||||
$q3->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%fungsi usaha%'])
|
||||
->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified())
|
||||
// Additional condition: unit IS NULL OR unit <= 1
|
||||
->where(function ($q3) {
|
||||
$q3->whereDoesntHave('pbg_task_detail', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
})
|
||||
->orWhereDoesntHave('pbg_task_detail');
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case 'business':
|
||||
// Business: function_type LIKE usaha OR (non-business with unit > 1)
|
||||
$query->where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
// Traditional business: function_type LIKE usaha
|
||||
$q2->where(function ($q3) {
|
||||
$q3->whereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%fungsi usaha%'])
|
||||
->orWhereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
// OR non-business with unit > 1 (becomes business)
|
||||
->orWhere(function ($q3) {
|
||||
$q3->where(function ($q4) {
|
||||
$q4->where(function ($q5) {
|
||||
$q5->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%fungsi usaha%'])
|
||||
->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereHas('pbg_task_detail', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
});
|
||||
});
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified());
|
||||
});
|
||||
break;
|
||||
|
||||
case 'verified':
|
||||
// Match BigdataResume verified logic exactly
|
||||
$query->whereIn("status", PbgTaskStatus::getVerified());
|
||||
break;
|
||||
|
||||
case 'non-verified':
|
||||
// Match BigdataResume non-verified logic exactly
|
||||
$query->whereIn("status", PbgTaskStatus::getNonVerified());
|
||||
break;
|
||||
|
||||
case 'potention':
|
||||
// Match BigdataResume potention logic exactly
|
||||
$query->whereIn("status", PbgTaskStatus::getPotention());
|
||||
break;
|
||||
|
||||
case 'issuance-realization-pbg':
|
||||
// Match BigdataResume issuance realization logic exactly
|
||||
$query->whereIn("status", PbgTaskStatus::getIssuanceRealizationPbg());
|
||||
break;
|
||||
|
||||
case 'process-in-technical-office':
|
||||
// Match BigdataResume process in technical office logic exactly
|
||||
$query->whereIn("status", PbgTaskStatus::getProcessInTechnicalOffice());
|
||||
break;
|
||||
|
||||
case 'waiting-click-dpmptsp':
|
||||
// Match BigdataResume waiting click DPMPTSP logic exactly
|
||||
$query->whereIn("status", PbgTaskStatus::getWaitingClickDpmptsp());
|
||||
break;
|
||||
|
||||
case 'non-business-rab':
|
||||
// Non-business tasks: function_type NOT LIKE usaha AND (unit IS NULL OR unit <= 1)
|
||||
$query->where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
$q2->where(function ($q3) {
|
||||
$q3->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%fungsi usaha%'])
|
||||
->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified())
|
||||
// Additional condition: unit IS NULL OR unit <= 1
|
||||
->where(function ($q3) {
|
||||
$q3->whereDoesntHave('pbg_task_detail', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
})
|
||||
->orWhereDoesntHave('pbg_task_detail');
|
||||
});
|
||||
})
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('pbg_task_detail_data_lists')
|
||||
->whereColumn('pbg_task_detail_data_lists.pbg_task_uuid', 'pbg_task.uuid')
|
||||
->where('pbg_task_detail_data_lists.data_type', 3)
|
||||
->where('pbg_task_detail_data_lists.status', '!=', 1);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'non-business-krk':
|
||||
// Non-business tasks: function_type NOT LIKE usaha AND (unit IS NULL OR unit <= 1)
|
||||
$query->where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
$q2->where(function ($q3) {
|
||||
$q3->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%fungsi usaha%'])
|
||||
->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified())
|
||||
// Additional condition: unit IS NULL OR unit <= 1
|
||||
->where(function ($q3) {
|
||||
$q3->whereDoesntHave('pbg_task_detail', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
})
|
||||
->orWhereDoesntHave('pbg_task_detail');
|
||||
});
|
||||
})
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('pbg_task_detail_data_lists')
|
||||
->whereColumn('pbg_task_detail_data_lists.pbg_task_uuid', 'pbg_task.uuid')
|
||||
->where('pbg_task_detail_data_lists.data_type', 2)
|
||||
->where('pbg_task_detail_data_lists.status', '!=', 1);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'business-rab':
|
||||
// Business tasks: function_type LIKE usaha OR (non-business with unit > 1)
|
||||
$query->where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
// Traditional business: function_type LIKE usaha
|
||||
$q2->where(function ($q3) {
|
||||
$q3->whereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%fungsi usaha%'])
|
||||
->orWhereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
// OR non-business with unit > 1 (becomes business)
|
||||
->orWhere(function ($q3) {
|
||||
$q3->where(function ($q4) {
|
||||
$q4->where(function ($q5) {
|
||||
$q5->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%fungsi usaha%'])
|
||||
->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereHas('pbg_task_detail', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
});
|
||||
});
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified());
|
||||
})
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('pbg_task_detail_data_lists')
|
||||
->whereColumn('pbg_task_detail_data_lists.pbg_task_uuid', 'pbg_task.uuid')
|
||||
->where('pbg_task_detail_data_lists.data_type', 3)
|
||||
->where('pbg_task_detail_data_lists.status', '!=', 1);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'business-krk':
|
||||
// Business tasks: function_type LIKE usaha OR (non-business with unit > 1)
|
||||
$query->where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
// Traditional business: function_type LIKE usaha
|
||||
$q2->where(function ($q3) {
|
||||
$q3->whereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%fungsi usaha%'])
|
||||
->orWhereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
// OR non-business with unit > 1 (becomes business)
|
||||
->orWhere(function ($q3) {
|
||||
$q3->where(function ($q4) {
|
||||
$q4->where(function ($q5) {
|
||||
$q5->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%fungsi usaha%'])
|
||||
->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereHas('pbg_task_detail', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
});
|
||||
});
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified());
|
||||
})
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('pbg_task_detail_data_lists')
|
||||
->whereColumn('pbg_task_detail_data_lists.pbg_task_uuid', 'pbg_task.uuid')
|
||||
->where('pbg_task_detail_data_lists.data_type', 2)
|
||||
->where('pbg_task_detail_data_lists.status', '!=', 1);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'business-dlh':
|
||||
// Business tasks: function_type LIKE usaha OR (non-business with unit > 1)
|
||||
$query->where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
// Traditional business: function_type LIKE usaha
|
||||
$q2->where(function ($q3) {
|
||||
$q3->whereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%fungsi usaha%'])
|
||||
->orWhereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
// OR non-business with unit > 1 (becomes business)
|
||||
->orWhere(function ($q3) {
|
||||
$q3->where(function ($q4) {
|
||||
$q4->where(function ($q5) {
|
||||
$q5->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%fungsi usaha%'])
|
||||
->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereHas('pbg_task_details', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
});
|
||||
});
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified());
|
||||
})
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('pbg_task_detail_data_lists')
|
||||
->whereColumn('pbg_task_detail_data_lists.pbg_task_uuid', 'pbg_task.uuid')
|
||||
->where('pbg_task_detail_data_lists.data_type', 5)
|
||||
->where('pbg_task_detail_data_lists.status', '!=', 1);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
// Log unrecognized filter for debugging
|
||||
Log::warning('Unrecognized filter value', ['filter' => $filter]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply search logic to the query
|
||||
*/
|
||||
private function applySearch($query, string $search)
|
||||
{
|
||||
// Search in pbg_task columns
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'LIKE', "%$search%")
|
||||
->orWhere('registration_number', 'LIKE', "%$search%")
|
||||
->orWhere('owner_name', 'LIKE', "%$search%")
|
||||
->orWhere('address', 'LIKE', "%$search%");
|
||||
});
|
||||
|
||||
// If search term exists, also find UUIDs from name_building search
|
||||
$namesBuildingUuids = DB::table('pbg_task_details')
|
||||
->where('name_building', 'LIKE', "%$search%")
|
||||
->pluck('pbg_task_uid')
|
||||
->toArray();
|
||||
|
||||
// If we found matching name_building records, include them in the search
|
||||
if (!empty($namesBuildingUuids)) {
|
||||
$query->orWhereIn('uuid', $namesBuildingUuids);
|
||||
}
|
||||
}
|
||||
|
||||
public function report_payment_recaps(Request $request)
|
||||
{
|
||||
try {
|
||||
// Query dengan group by kecamatan dan sum nilai_retribusi_keseluruhan_simbg
|
||||
$query = PbgTaskGoogleSheet::select(
|
||||
'kecamatan',
|
||||
DB::raw('SUM(nilai_retribusi_keseluruhan_simbg) as total')
|
||||
)
|
||||
->groupBy('kecamatan')
|
||||
->paginate(10);
|
||||
|
||||
// Return hasil dalam JSON format
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $query
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
return response()->json(['message' => 'Terjadi kesalahan: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function export_excel_district_payment_recaps(){
|
||||
return Excel::download(new DistrictPaymentRecapExport, 'laporan-rekap-data-pembayaran.xlsx');
|
||||
}
|
||||
public function export_pdf_district_payment_recaps(){
|
||||
$data = PbgTaskGoogleSheet::select(
|
||||
'kecamatan',
|
||||
DB::raw('SUM(nilai_retribusi_keseluruhan_simbg) as total')
|
||||
)
|
||||
->groupBy('kecamatan')->get();
|
||||
$pdf = Pdf::loadView('exports.district_payment_report', compact('data'));
|
||||
return $pdf->download('laporan-rekap-data-pembayaran.pdf');
|
||||
}
|
||||
public function report_pbg_ptsp()
|
||||
{
|
||||
try {
|
||||
// Query dengan group by status dan count total per status
|
||||
$query = PbgTask::select(
|
||||
'status',
|
||||
'status_name',
|
||||
DB::raw('COUNT(*) as total')
|
||||
)
|
||||
->groupBy('status', 'status_name')
|
||||
->paginate(10);
|
||||
|
||||
// Return hasil dalam JSON format
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $query
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
return response()->json(['message' => 'Terjadi kesalahan: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
@@ -54,4 +472,270 @@ class RequestAssignmentController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate consistency with BigdataResume logic for debugging
|
||||
*/
|
||||
private function validateConsistencyWithBigdataResume(?string $filter, $year, int $actualCount)
|
||||
{
|
||||
try {
|
||||
// Validate input parameters
|
||||
if (empty($filter) || empty($year)) {
|
||||
Log::info('Skipping consistency validation - empty filter or year', [
|
||||
'filter' => $filter,
|
||||
'year' => $year
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert year to integer
|
||||
$year = (int) $year;
|
||||
if ($year <= 0) {
|
||||
Log::warning('Invalid year provided for consistency validation', ['year' => $year]);
|
||||
return;
|
||||
}
|
||||
|
||||
$bigdataResumeCount = null;
|
||||
|
||||
// Calculate expected count using BigdataResume logic
|
||||
switch ($filter) {
|
||||
case 'verified':
|
||||
$bigdataResumeCount = PbgTask::whereIn('status', PbgTaskStatus::getVerified())
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'non-verified':
|
||||
$bigdataResumeCount = PbgTask::whereIn('status', PbgTaskStatus::getNonVerified())
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'business':
|
||||
$bigdataResumeCount = PbgTask::where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
// Traditional business: function_type LIKE usaha
|
||||
$q2->where(function ($q3) {
|
||||
$q3->whereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%fungsi usaha%'])
|
||||
->orWhereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
// OR non-business with unit > 1 (becomes business)
|
||||
->orWhere(function ($q3) {
|
||||
$q3->where(function ($q4) {
|
||||
$q4->where(function ($q5) {
|
||||
$q5->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%fungsi usaha%'])
|
||||
->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereHas('pbg_task_details', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
});
|
||||
});
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified());
|
||||
})
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'non-business':
|
||||
$bigdataResumeCount = PbgTask::where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
$q2->where(function ($q3) {
|
||||
$q3->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%fungsi usaha%'])
|
||||
->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified())
|
||||
// Additional condition: unit IS NULL OR unit <= 1
|
||||
->where(function ($q3) {
|
||||
$q3->whereDoesntHave('pbg_task_details', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
})
|
||||
->orWhereDoesntHave('pbg_task_details');
|
||||
});
|
||||
})
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'potention':
|
||||
$bigdataResumeCount = PbgTask::whereIn('status', PbgTaskStatus::getPotention())
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'waiting-click-dpmptsp':
|
||||
$bigdataResumeCount = PbgTask::whereIn('status', PbgTaskStatus::getWaitingClickDpmptsp())
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'issuance-realization-pbg':
|
||||
$bigdataResumeCount = PbgTask::whereIn('status', PbgTaskStatus::getIssuanceRealizationPbg())
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'process-in-technical-office':
|
||||
$bigdataResumeCount = PbgTask::whereIn('status', PbgTaskStatus::getProcessInTechnicalOffice())
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'non-business-rab':
|
||||
$bigdataResumeCount = PbgTask::where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
$q2->where(function ($q3) {
|
||||
$q3->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%fungsi usaha%'])
|
||||
->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified());
|
||||
})
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('pbg_task_detail_data_lists')
|
||||
->whereColumn('pbg_task_detail_data_lists.pbg_task_uuid', 'pbg_task.uuid')
|
||||
->where('pbg_task_detail_data_lists.data_type', 3)
|
||||
->where('pbg_task_detail_data_lists.status', '!=', 1);
|
||||
})
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'non-business-krk':
|
||||
$bigdataResumeCount = PbgTask::where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
$q2->where(function ($q3) {
|
||||
$q3->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%fungsi usaha%'])
|
||||
->whereRaw("LOWER(TRIM(function_type)) NOT LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified());
|
||||
})
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('pbg_task_detail_data_lists')
|
||||
->whereColumn('pbg_task_detail_data_lists.pbg_task_uuid', 'pbg_task.uuid')
|
||||
->where('pbg_task_detail_data_lists.data_type', 2)
|
||||
->where('pbg_task_detail_data_lists.status', '!=', 1);
|
||||
})
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'business-rab':
|
||||
$bigdataResumeCount = PbgTask::where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
$q2->whereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%fungsi usaha%'])
|
||||
->orWhereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified());
|
||||
})
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('pbg_task_detail_data_lists')
|
||||
->whereColumn('pbg_task_detail_data_lists.pbg_task_uuid', 'pbg_task.uuid')
|
||||
->where('pbg_task_detail_data_lists.data_type', 3)
|
||||
->where('pbg_task_detail_data_lists.status', '!=', 1);
|
||||
})
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'business-krk':
|
||||
$bigdataResumeCount = PbgTask::where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
$q2->whereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%fungsi usaha%'])
|
||||
->orWhereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified());
|
||||
})
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('pbg_task_detail_data_lists')
|
||||
->whereColumn('pbg_task_detail_data_lists.pbg_task_uuid', 'pbg_task.uuid')
|
||||
->where('pbg_task_detail_data_lists.data_type', 2)
|
||||
->where('pbg_task_detail_data_lists.status', '!=', 1);
|
||||
})
|
||||
->count();
|
||||
break;
|
||||
|
||||
case 'business-dlh':
|
||||
$bigdataResumeCount = PbgTask::where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
$q2->whereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%fungsi usaha%'])
|
||||
->orWhereRaw("LOWER(TRIM(function_type)) LIKE ?", ['%sebagai tempat usaha%']);
|
||||
})
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified());
|
||||
})
|
||||
->where('is_valid', true)
|
||||
->whereYear('task_created_at', $year)
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('pbg_task_detail_data_lists')
|
||||
->whereColumn('pbg_task_detail_data_lists.pbg_task_uuid', 'pbg_task.uuid')
|
||||
->where('pbg_task_detail_data_lists.data_type', 5)
|
||||
->where('pbg_task_detail_data_lists.status', '!=', 1);
|
||||
})
|
||||
->count();
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::info('Unknown filter for consistency validation', [
|
||||
'filter' => $filter,
|
||||
'year' => $year
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($bigdataResumeCount !== null) {
|
||||
$isConsistent = ($actualCount === $bigdataResumeCount);
|
||||
|
||||
Log::info('RequestAssignment vs BigdataResume consistency check', [
|
||||
'filter' => $filter,
|
||||
'year' => $year,
|
||||
'request_assignment_count' => $actualCount,
|
||||
'bigdata_resume_count' => $bigdataResumeCount,
|
||||
'is_consistent' => $isConsistent,
|
||||
'difference' => $actualCount - $bigdataResumeCount
|
||||
]);
|
||||
|
||||
if (!$isConsistent) {
|
||||
Log::warning('INCONSISTENCY DETECTED between RequestAssignment and BigdataResume', [
|
||||
'filter' => $filter,
|
||||
'year' => $year,
|
||||
'request_assignment_count' => $actualCount,
|
||||
'bigdata_resume_count' => $bigdataResumeCount,
|
||||
'difference' => $actualCount - $bigdataResumeCount
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error in consistency validation', [
|
||||
'error' => $e->getMessage(),
|
||||
'filter' => $filter,
|
||||
'year' => $year
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,16 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Enums\ImportDatasourceStatus;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\RetrySyncronizeJob;
|
||||
use App\Jobs\ScrapingDataJob;
|
||||
use App\Jobs\SyncronizeSIMBG;
|
||||
use App\Models\ImportDatasource;
|
||||
use App\Traits\GlobalApiResponse;
|
||||
use App\Services\ServiceTokenSIMBG;
|
||||
use GuzzleHttp\Client;
|
||||
use App\Services\ServiceGoogleSheet;
|
||||
use App\Services\ServicePbgTask;
|
||||
use App\Services\ServiceTabPbgTask;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@@ -23,40 +30,33 @@ class ScrapingController extends Controller
|
||||
return $this->resError("Failed to execute while processing another scraping");
|
||||
}
|
||||
|
||||
// run service artisan command
|
||||
SyncronizeSIMBG::dispatch();
|
||||
// use ole schema synchronization
|
||||
// dispatch(new SyncronizeSIMBG());
|
||||
|
||||
// use new schema synchronization
|
||||
dispatch(new ScrapingDataJob());
|
||||
return $this->resSuccess(["message" => "Success execute scraping service on background, check status for more"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
public function retry_syncjob(string $import_datasource_id){
|
||||
try{
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
$import_datasource = ImportDatasource::find($import_datasource_id);
|
||||
if(!$import_datasource){
|
||||
return $this->resError("Invalid import datasource id", null, 404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
dispatch(new RetrySyncronizeJob($import_datasource->id));
|
||||
return response()->json([
|
||||
"success" => true,
|
||||
"message" => "Retrying scrape job on background, check status for more"
|
||||
]);
|
||||
}catch(\Exception $e){
|
||||
return response()->json([
|
||||
"success" => false,
|
||||
"message" => "Failed to retry sync job",
|
||||
"error" => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ class SpatialPlanningController extends Controller
|
||||
$search = $request->input('search', '');
|
||||
|
||||
$query = SpatialPlanning::query();
|
||||
|
||||
// Only include spatial plannings that are not yet issued (is_terbit = false)
|
||||
$query->where('is_terbit', false);
|
||||
|
||||
if ($search) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%$search%")
|
||||
@@ -42,9 +46,11 @@ class SpatialPlanningController extends Controller
|
||||
// Menambhakan nomor urut (No)
|
||||
$start = ($spatialPlannings->currentPage()-1) * $perPage + 1;
|
||||
|
||||
// Tambahkan nomor urut ke dalam data
|
||||
// Tambahkan nomor urut ke dalam data (calculated_retribution sudah auto-append)
|
||||
$data = $spatialPlannings->map(function ($item, $index) use ($start) {
|
||||
return array_merge($item->toArray(), ['no' => $start + $index]);
|
||||
$itemArray = $item->toArray();
|
||||
$itemArray['no'] = $start + $index;
|
||||
return $itemArray;
|
||||
});
|
||||
|
||||
info($data);
|
||||
@@ -104,9 +110,10 @@ class SpatialPlanningController extends Controller
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(SpatialPlanning $spatialPlanning): SpatialPlanning
|
||||
public function show(SpatialPlanning $spatialPlanning): array
|
||||
{
|
||||
return $spatialPlanning;
|
||||
// calculated_retribution and formatted_retribution are already appended via $appends
|
||||
return $spatialPlanning->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
64
app/Http/Controllers/Api/TaskAssignmentsController.php
Normal file
64
app/Http/Controllers/Api/TaskAssignmentsController.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\TaskAssignmentsResource;
|
||||
use App\Models\TaskAssignment;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TaskAssignmentsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request, $uuid)
|
||||
{
|
||||
try{
|
||||
$query = TaskAssignment::query()
|
||||
->where('pbg_task_uid', $uuid)
|
||||
->orderBy('id', 'desc');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$query->where('name', 'like', "%{$request->get('search')}%")
|
||||
->orWhere('email', 'like', "%{$request->get('search')}%");
|
||||
}
|
||||
|
||||
return TaskAssignmentsResource::collection($query->paginate(config('app.paginate_per_page', 50)));
|
||||
}catch(\Exception $exception){
|
||||
return response()->json(['message' => $exception->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
98
app/Http/Controllers/Api/TaxationsController.php
Normal file
98
app/Http/Controllers/Api/TaxationsController.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Exports\TaxationsExport;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ExcelUploadRequest;
|
||||
use App\Http\Requests\TaxationsRequest;
|
||||
use App\Http\Resources\TaxationsResource;
|
||||
use App\Imports\TaxationsImport;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Tax;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
|
||||
class TaxationsController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
try{
|
||||
$query = Tax::query()->orderBy('id', 'desc');
|
||||
|
||||
if($request->has('search') && !empty($request->get('search'))){
|
||||
$query->where('tax_no', 'like', '%'. $request->get('search') . '%')
|
||||
->orWhere('wp_name', 'like', '%'. $request->get('search') . '%')
|
||||
->orWhere('business_name', 'like', '%'. $request->get('search') . '%');
|
||||
}
|
||||
|
||||
return TaxationsResource::collection($query->paginate(config('app.paginate_per_page', 50)));
|
||||
}catch(\Exception $e){
|
||||
Log::info($e->getMessage());
|
||||
return response()->json([
|
||||
'error' => 'Failed to get data',
|
||||
'message' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function upload(ExcelUploadRequest $request)
|
||||
{
|
||||
try{
|
||||
if(!$request->hasFile('file')){
|
||||
return response()->json([
|
||||
'error' => 'No file provided'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$file = $request->file('file');
|
||||
Excel::import(new TaxationsImport, $file);
|
||||
return response()->json(['message' => 'File uploaded successfully'], 200);
|
||||
}catch(\Exception $e){
|
||||
Log::info($e->getMessage());
|
||||
return response()->json([
|
||||
'error' => 'Failed to upload file',
|
||||
'message' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function export(Request $request)
|
||||
{
|
||||
return Excel::download(new TaxationsExport, 'pajak_per_kecamatan.xlsx');
|
||||
}
|
||||
|
||||
public function delete(Request $request)
|
||||
{
|
||||
try{
|
||||
$tax = Tax::find($request->id);
|
||||
$tax->delete();
|
||||
return response()->json(['message' => 'Data deleted successfully'], 200);
|
||||
}catch(\Exception $e){
|
||||
Log::info($e->getMessage());
|
||||
return response()->json([
|
||||
'error' => 'Failed to delete data',
|
||||
'message' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(TaxationsRequest $request, string $id)
|
||||
{
|
||||
try{
|
||||
$tax = Tax::find($id);
|
||||
if($tax){
|
||||
$tax->update($request->validated());
|
||||
return response()->json(['message' => 'Successfully updated', new TaxationsResource($tax)]);
|
||||
} else {
|
||||
return response()->json(['message' => 'Tax not found'], 404);
|
||||
}
|
||||
}catch(\Exception $e){
|
||||
Log::info($e->getMessage());
|
||||
return response()->json([
|
||||
'error' => 'Failed to update tax',
|
||||
'message' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class TourismController extends Controller
|
||||
$tourisms->village_name = $village ? $village->village_name : null;
|
||||
|
||||
$district = DB::table('districts')->where('district_code', $tourisms->district_code)->first();
|
||||
$tourisms->district_name = $village ? $village->village_name : null;
|
||||
$tourisms->district_name = $district ? $district->district_name : null;
|
||||
return $tourisms;
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Traits\GlobalApiResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UsersController extends Controller
|
||||
{
|
||||
@@ -29,12 +30,15 @@ class UsersController extends Controller
|
||||
public function index(Request $request){
|
||||
$query = User::query();
|
||||
if($request->has('search') && !empty($request->get("search"))){
|
||||
$query->where('name', 'LIKE', '%'.$request->get('search').'%');
|
||||
$query->where('name', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('email', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
return UserResource::collection($query->paginate(config('app.paginate_per_page', 50)));
|
||||
}
|
||||
public function logout(Request $request){
|
||||
$request->user()->tokens()->delete();
|
||||
\Laravel\Sanctum\PersonalAccessToken::where('tokenable_id', $request->user()->id)
|
||||
->where('tokenable_type', get_class($request->user()))
|
||||
->delete();
|
||||
return response()->json(['message' => 'logged out successfully']);
|
||||
}
|
||||
public function store(UsersRequest $request){
|
||||
@@ -83,4 +87,17 @@ class UsersController extends Controller
|
||||
return response()->json(['message' => $e->getMessage()],500);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($id){
|
||||
try{
|
||||
$user = User::findOrFail($id);
|
||||
DB::beginTransaction();
|
||||
$user->delete();
|
||||
DB::commit();
|
||||
return response()->json(['message' => 'Successfully deleted'], 200);
|
||||
}catch(\Exception $e){
|
||||
Log::error('Failed to delete user: '. $e->getMessage());
|
||||
return response()->json(['message' => 'Failed to delete user'],500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
app/Http/Controllers/Approval/ApprovalController.php
Normal file
65
app/Http/Controllers/Approval/ApprovalController.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Approval;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ApprovalController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('approval.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
76
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Executable file → Normal file
76
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Executable file → Normal file
@@ -36,13 +36,77 @@ class AuthenticatedSessionController extends Controller
|
||||
// Ambil user yang sedang login
|
||||
$user = Auth::user();
|
||||
|
||||
// Buat token untuk API
|
||||
$token = $user->createToken(env('APP_KEY'))->plainTextToken;
|
||||
// Hapus token lama jika ada
|
||||
\Laravel\Sanctum\PersonalAccessToken::where('tokenable_id', $user->id)
|
||||
->where('tokenable_type', get_class($user))
|
||||
->delete();
|
||||
|
||||
// Simpan token di session (bisa digunakan di JavaScript)
|
||||
// Buat token untuk API dengan scope dan expiration
|
||||
$tokenName = config('app.name', 'Laravel') . '-' . $user->id . '-' . time();
|
||||
|
||||
// Token dengan scope (opsional)
|
||||
$token = $user->createToken($tokenName, ['*'], now()->addDays(30))->plainTextToken;
|
||||
|
||||
// Simpan token di session untuk digunakan di frontend
|
||||
session(['api_token' => $token]);
|
||||
|
||||
return redirect()->intended(RouteServiceProvider::HOME);
|
||||
// Simpan timestamp login untuk validasi multi-user
|
||||
session(['login_timestamp' => now()->timestamp]);
|
||||
session(['user_id' => $user->id]);
|
||||
|
||||
// Append menu_id dynamically to HOME
|
||||
$menuId = optional(\App\Models\Menu::where('name', 'Dashboard Pimpinan SIMBG')->first())->id;
|
||||
$home = RouteServiceProvider::HOME . ($menuId ? ('?menu_id=' . $menuId) : '');
|
||||
return redirect()->intended($home);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate API token for authenticated user
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function generateApiToken(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if (!$user) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
// Delete existing tokens
|
||||
\Laravel\Sanctum\PersonalAccessToken::where('tokenable_id', $user->id)
|
||||
->where('tokenable_type', get_class($user))
|
||||
->delete();
|
||||
|
||||
// Generate new token
|
||||
$tokenName = config('app.name', 'Laravel') . '-' . $user->id . '-' . time();
|
||||
$token = $user->createToken($tokenName, ['*'], now()->addDays(30))->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
'expires_in' => 30 * 24 * 60 * 60, // 30 days in seconds
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke API token for authenticated user
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function revokeApiToken(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if (!$user) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$user->tokens()->delete();
|
||||
|
||||
return response()->json(['message' => 'All tokens revoked successfully']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +118,9 @@ class AuthenticatedSessionController extends Controller
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
if($request->user()){
|
||||
$request->user()->tokens()->delete();
|
||||
\Laravel\Sanctum\PersonalAccessToken::where('tokenable_id', $request->user()->id)
|
||||
->where('tokenable_type', get_class($request->user()))
|
||||
->delete();
|
||||
}
|
||||
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
0
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/EmailVerificationNotificationController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/EmailVerificationNotificationController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/EmailVerificationPromptController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/EmailVerificationPromptController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/NewPasswordController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/NewPasswordController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/PasswordResetLinkController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/PasswordResetLinkController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/RegisteredUserController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/RegisteredUserController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/VerifyEmailController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/VerifyEmailController.php
Executable file → Normal file
@@ -4,63 +4,40 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\BusinessOrIndustry;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class BusinessOrIndustriesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('business-industries.index');
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
$permissions = $this->permissions[$menuId]?? []; // Avoid undefined index error
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
return view('business-industries.index', compact('creator', 'updater', 'destroyer','menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
public function create(Request $request)
|
||||
{
|
||||
return view("business-industries.create");
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
return view("business-industries.create", compact('menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
public function edit(string $id, Request $request)
|
||||
{
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
$data = BusinessOrIndustry::findOrFail($id);
|
||||
return view('business-industries.edit', compact('data'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
return view('business-industries.edit', compact('data', 'menuId'));
|
||||
}
|
||||
}
|
||||
|
||||
49
app/Http/Controllers/Controller.php
Executable file → Normal file
49
app/Http/Controllers/Controller.php
Executable file → Normal file
@@ -2,7 +2,54 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
protected array $permissions = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!Auth::check()) {
|
||||
return;
|
||||
}
|
||||
$this->setUserPermissions();
|
||||
}
|
||||
|
||||
protected function setUserPermissions()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if (!$user) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menus = $user->roles()
|
||||
->with(['menus' => function ($query) {
|
||||
$query->select('menus.id', 'menus.name')
|
||||
->withPivot(['allow_show' ,'allow_create', 'allow_update', 'allow_destroy']);
|
||||
}])
|
||||
->get()
|
||||
->pluck('menus')
|
||||
->flatten()
|
||||
->unique('id');
|
||||
|
||||
// Store permissions in an associative array
|
||||
foreach ($menus as $menu) {
|
||||
$this->permissions[$menu->id] = [
|
||||
'allow_show' => $menu->pivot->allow_show ?? 0,
|
||||
'allow_create' => $menu->pivot->allow_create ?? 0,
|
||||
'allow_update' => $menu->pivot->allow_update ?? 0,
|
||||
'allow_destroy' => $menu->pivot->allow_destroy ?? 0,
|
||||
];
|
||||
}
|
||||
|
||||
// Share permissions globally in views
|
||||
view()->share('permissions', $this->permissions);
|
||||
}
|
||||
|
||||
public function getPermissions()
|
||||
{
|
||||
return $this->permissions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,34 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Customer;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CustomersController extends Controller
|
||||
{
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('customers.index');
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
$permissions = $this->permissions[$menuId]?? []; // Avoid undefined index error
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
|
||||
return view('customers.index', compact('creator', 'updater', 'destroyer', 'menuId'));
|
||||
}
|
||||
public function create()
|
||||
public function create(Request $request)
|
||||
{
|
||||
return view('customers.create');
|
||||
$menuId = $request->query('menu_id');
|
||||
return view('customers.create', compact('menuId'));
|
||||
}
|
||||
public function edit(string $id)
|
||||
public function edit(Request $request, string $id)
|
||||
{
|
||||
$data = Customer::findOrFail($id);
|
||||
return view('customers.edit', compact('data'));
|
||||
$menuId = $request->query('menu_id');
|
||||
return view('customers.edit', compact('data', 'menuId'));
|
||||
}
|
||||
public function upload(){
|
||||
return view('customers.upload');
|
||||
public function upload(Request $request){
|
||||
$menuId = $request->query('menu_id');
|
||||
return view('customers.upload', compact('menuId'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Dashboards;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ImportDatasource;
|
||||
use App\Models\Menu;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BigDataController extends Controller
|
||||
@@ -12,11 +13,21 @@ class BigDataController extends Controller
|
||||
$latest_import_datasource = ImportDatasource::latest()->first();
|
||||
$latest_created = $latest_import_datasource ?
|
||||
$latest_import_datasource->created_at->format("j F Y H:i:s") : null;
|
||||
return view('dashboards.bigdata', compact('latest_created'));
|
||||
$menus = Menu::all();
|
||||
return view('dashboards.bigdata', compact('latest_created', 'menus'));
|
||||
}
|
||||
|
||||
public function pbg()
|
||||
{
|
||||
return view('dashboards.pbg');
|
||||
}
|
||||
|
||||
public function leader()
|
||||
{
|
||||
$latest_import_datasource = ImportDatasource::latest()->first();
|
||||
$latest_created = $latest_import_datasource ?
|
||||
$latest_import_datasource->created_at->format("j F Y H:i:s") : null;
|
||||
$menus = Menu::all();
|
||||
return view('dashboards.leader', compact('latest_created', 'menus'));
|
||||
}
|
||||
}
|
||||
|
||||
18
app/Http/Controllers/Dashboards/PotentialsController.php
Normal file
18
app/Http/Controllers/Dashboards/PotentialsController.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Dashboards;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Menu;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PotentialsController extends Controller
|
||||
{
|
||||
public function inside_system(){
|
||||
$menus = Menu::all();
|
||||
return view('dashboards.potentials.inside_system', compact('menus'));
|
||||
}
|
||||
public function outside_system(){
|
||||
return view('dashboards.potentials.outside_system');
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,23 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\Advertisement;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AdvertisementController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('data.advertisements.index');
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
$permissions = $this->permissions[$menuId] ?? []; // Avoid undefined index error
|
||||
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
|
||||
return view('data.advertisements.index', compact('creator', 'updater', 'destroyer','menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,8 +37,9 @@ class AdvertisementController extends Controller
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
public function create(Request $request)
|
||||
{
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
$title = 'Advertisement';
|
||||
$subtitle = 'Create Data';
|
||||
|
||||
@@ -47,14 +56,15 @@ class AdvertisementController extends Controller
|
||||
|
||||
// $route = 'advertisements.create';
|
||||
// info("AdvertisementController@edit diakses dengan ID: $title");
|
||||
return view('data.advertisements.form', compact('title', 'subtitle', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions'));
|
||||
return view('data.advertisements.form', compact('title', 'subtitle', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions','menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit($id)
|
||||
public function edit(Request $request, $id)
|
||||
{
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
info("AdvertisementController@edit diakses dengan ID: $id");
|
||||
$title = 'Advertisement';
|
||||
$subtitle = 'Update Data';
|
||||
@@ -86,7 +96,7 @@ class AdvertisementController extends Controller
|
||||
|
||||
// $route = 'advertisements.update'; // Menggunakan route update untuk form edit
|
||||
// info("AdvertisementController@edit diakses dengan route: $route");
|
||||
return view('data.advertisements.form', compact('title', 'subtitle', 'modelInstance', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions'));
|
||||
return view('data.advertisements.form', compact('title', 'subtitle', 'modelInstance', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions', 'menuId'));
|
||||
}
|
||||
|
||||
private function getFields()
|
||||
|
||||
36
app/Http/Controllers/Data/GoogleSheetsController.php
Normal file
36
app/Http/Controllers/Data/GoogleSheetsController.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Data;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PbgTaskGoogleSheet;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GoogleSheetsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$menu_id = $request->query('menu_id');
|
||||
$user_menu_permission = $this->permissions[$menu_id];
|
||||
return view('data.google-sheet.index', compact('user_menu_permission'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('data.google-sheet.create');
|
||||
}
|
||||
|
||||
public function show(string $id)
|
||||
{
|
||||
$data = PbgTaskGoogleSheet::find($id);
|
||||
return view('data.google-sheet.show', compact('data'));
|
||||
}
|
||||
|
||||
public function edit(string $id)
|
||||
{
|
||||
return view('data.google-sheet.edit');
|
||||
}
|
||||
}
|
||||
@@ -11,24 +11,33 @@ class SpatialPlanningController extends Controller
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('data.spatialPlannings.index');
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
$permissions = $this->permissions[$menuId] ?? []; // Avoid undefined index error
|
||||
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
|
||||
return view('data.spatialPlannings.index', compact('creator', 'updater', 'destroyer','menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* show the form for creating a new resource.
|
||||
*/
|
||||
public function bulkCreate()
|
||||
public function bulkCreate(Request $request)
|
||||
{
|
||||
return view('data.spatialPlannings.form-upload');
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
return view('data.spatialPlannings.form-upload', compact('menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
public function create(Request $request)
|
||||
{
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
$title = 'Rencana Tata Ruang';
|
||||
$subtitle = "Create Data";
|
||||
|
||||
@@ -39,30 +48,15 @@ class SpatialPlanningController extends Controller
|
||||
$fieldTypes = $this->getFieldTypes();
|
||||
|
||||
$apiUrl = url('/api/spatial-plannings');
|
||||
return view('data.spatialPlannings.form', compact('title', 'subtitle', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
return view('data.spatialPlannings.form', compact('title', 'subtitle', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions','menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
public function edit(Request $request,string $id)
|
||||
{
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
$title = 'Rencana Tata Ruang';
|
||||
$subtitle = 'Update Data';
|
||||
|
||||
@@ -78,23 +72,7 @@ class SpatialPlanningController extends Controller
|
||||
$fieldTypes = $this->getFieldTypes();
|
||||
|
||||
$apiUrl = url('/api/spatial-plannings');
|
||||
return view('data.spatialPlannings.form', compact('title', 'subtitle', 'modelInstance', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
return view('data.spatialPlannings.form', compact('title', 'subtitle', 'modelInstance', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions','menuId'));
|
||||
}
|
||||
|
||||
private function getFields()
|
||||
@@ -104,9 +82,15 @@ class SpatialPlanningController extends Controller
|
||||
"kbli"=> "KBLI",
|
||||
"activities"=> "Kegiatan",
|
||||
"area"=> "Luas (m2)",
|
||||
"land_area"=> "Luas Lahan (m2)",
|
||||
"location"=> "Lokasi",
|
||||
"number"=> "Nomor",
|
||||
"date"=> "Tanggal",
|
||||
"site_bcr"=> "BCR",
|
||||
"building_function"=> "Fungsi Bangunan",
|
||||
"business_type_info"=> "Jenis Usaha",
|
||||
"is_terbit"=> "Status Terbit",
|
||||
"calculated_retribution"=> "Retribusi",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -117,9 +101,15 @@ class SpatialPlanningController extends Controller
|
||||
"kbli"=> "text",
|
||||
"activities"=> "text",
|
||||
"area"=> "text",
|
||||
"land_area"=> "text",
|
||||
"location"=> "text",
|
||||
"number"=> "text",
|
||||
"date"=> "date",
|
||||
"site_bcr"=> "text",
|
||||
"building_function"=> "text",
|
||||
"business_type_info"=> "readonly",
|
||||
"is_terbit"=> "select",
|
||||
"calculated_retribution"=> "readonly",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,30 +6,39 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\Tourism;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class TourismController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('data.tourisms.index');
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
$permissions = $this->permissions[$menuId] ?? []; // Avoid undefined index error
|
||||
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
return view('data.tourisms.index', compact('creator', 'updater', 'destroyer', 'menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* show the form for creating a new rsource.
|
||||
*/
|
||||
public function bulkCreate()
|
||||
public function bulkCreate(Request $request)
|
||||
{
|
||||
return view('data.tourisms.form-upload');
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
return view('data.tourisms.form-upload', compact('menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show th form for creating a new resource
|
||||
*/
|
||||
public function create()
|
||||
public function create(Request $request)
|
||||
{
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
$title = 'Pariwisata';
|
||||
$subtitle = 'Create Data';
|
||||
|
||||
@@ -44,14 +53,15 @@ class TourismController extends Controller
|
||||
|
||||
$apiUrl = url('/api/tourisms');
|
||||
|
||||
return view('data.tourisms.form', compact('title', 'subtitle', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions'));
|
||||
return view('data.tourisms.form', compact('title', 'subtitle', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions', 'menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit($id)
|
||||
public function edit(Request $request, $id)
|
||||
{
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
$title = 'Pariwisata';
|
||||
$subtitle = 'Update Data';
|
||||
|
||||
@@ -78,7 +88,7 @@ class TourismController extends Controller
|
||||
|
||||
$apiUrl = url('/api/tourisms');
|
||||
|
||||
return view('data.tourisms.form', compact('title', 'subtitle', 'modelInstance', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions'));
|
||||
return view('data.tourisms.form', compact('title', 'subtitle', 'modelInstance', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions', 'menuId'));
|
||||
}
|
||||
|
||||
private function getFields()
|
||||
|
||||
@@ -6,30 +6,39 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\Umkm;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class UmkmController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('data.umkm.index');
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
$permissions = $this->permissions[$menuId] ?? []; // Avoid undefined index error
|
||||
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
return view('data.umkm.index', compact('creator', 'updater', 'destroyer', 'menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function bulkCreate()
|
||||
public function bulkCreate(Request $request)
|
||||
{
|
||||
return view('data.umkm.form-upload');
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
return view('data.umkm.form-upload', compact('menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
public function create(Request $request)
|
||||
{
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
$title = 'UMKM';
|
||||
$subtitle = 'Create Data';
|
||||
|
||||
@@ -47,14 +56,15 @@ class UmkmController extends Controller
|
||||
|
||||
$apiUrl = url('/api/umkm');
|
||||
|
||||
return view('data.umkm.form', compact('title', 'subtitle', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions'));
|
||||
return view('data.umkm.form', compact('title', 'subtitle', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions','menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit($id)
|
||||
public function edit(Request $request,$id)
|
||||
{
|
||||
$menuId = $request->query('menu_id', 0);
|
||||
$title = 'UMKM';
|
||||
$subtitle = 'Update Data';
|
||||
$modelInstance = Umkm::find($id);
|
||||
@@ -96,7 +106,7 @@ class UmkmController extends Controller
|
||||
$apiUrl = url('/api/umkm');
|
||||
|
||||
// dd($modelInstance->business_form_id, $dropdownOptions['business_form']);
|
||||
return view('data.umkm.form', compact('title', 'subtitle', 'modelInstance', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions'));
|
||||
return view('data.umkm.form', compact('title', 'subtitle', 'modelInstance', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions','menuId'));
|
||||
}
|
||||
|
||||
private function getFields()
|
||||
|
||||
@@ -8,23 +8,31 @@ use Exception;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Request as IndexRequest;
|
||||
|
||||
class DataSettingController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(IndexRequest $request)
|
||||
{
|
||||
return view("data-settings.index");
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
$permissions = $this->permissions[$menuId]?? []; // Avoid undefined index error
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
return view("data-settings.index", compact('creator', 'updater', 'destroyer','menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
public function create(IndexRequest $request)
|
||||
{
|
||||
return view("data-settings.create");
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
return view("data-settings.create", compact('menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,14 +65,15 @@ class DataSettingController extends Controller
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
public function edit(IndexRequest $request,string $id)
|
||||
{
|
||||
try{
|
||||
$data = DataSetting::findOrFail($id);
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
if(empty($data)){
|
||||
return redirect()->route('data-settings.index')->with('error', 'Invalid id');
|
||||
}
|
||||
return view("data-settings.edit", compact("data"));
|
||||
return view("data-settings.edit", compact("data", 'menuId'));
|
||||
}catch(Exception $ex){
|
||||
return redirect()->route("data-settings.index")->with("error", "Invalid id");
|
||||
}
|
||||
|
||||
12
app/Http/Controllers/InvitationsController.php
Normal file
12
app/Http/Controllers/InvitationsController.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class InvitationsController extends Controller
|
||||
{
|
||||
public function index(Request $request){
|
||||
return view('invitations.index');
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use Illuminate\Support\Facades\Hash;
|
||||
use App\Models\User;
|
||||
use App\Traits\GlobalApiResponse;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class UsersController extends Controller
|
||||
{
|
||||
@@ -21,13 +22,20 @@ class UsersController extends Controller
|
||||
$users = User::all();
|
||||
return $this->resSuccess($users);
|
||||
}
|
||||
public function index(){
|
||||
public function index(Request $request){
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
$permissions = $this->permissions[$menuId]?? []; // Avoid undefined index error
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
|
||||
$users = User::paginate();
|
||||
return view('master.users.index', compact('users'));
|
||||
return view('master.users.index', compact('users', 'creator', 'updater', 'destroyer','menuId'));
|
||||
}
|
||||
public function create(){
|
||||
public function create(Request $request){
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
$roles = Role::all();
|
||||
return view('master.users.create', compact('roles'));
|
||||
return view('master.users.create', compact('roles', 'menuId'));
|
||||
}
|
||||
public function store(UsersRequest $request){
|
||||
$request->validate([
|
||||
@@ -65,10 +73,11 @@ class UsersController extends Controller
|
||||
$user = User::find($id);
|
||||
return view('master.users.show', compact('user'));
|
||||
}
|
||||
public function edit($id){
|
||||
public function edit(Request $request, $id){
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
$user = User::find($id);
|
||||
$roles = Role::all();
|
||||
return view('master.users.edit', compact('user', 'roles'));
|
||||
return view('master.users.edit', compact('user', 'roles', 'menuId'));
|
||||
}
|
||||
public function update(Request $request, $id){
|
||||
$user = User::find($id);
|
||||
|
||||
@@ -12,18 +12,35 @@ class MenusController extends Controller
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('menus.index');
|
||||
$menuId = (int) $request->query('menu_id', 0);
|
||||
$permissions = $this->permissions[$menuId] ?? []; // Avoid undefined index error
|
||||
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
|
||||
return view('menus.index', compact('creator', 'updater', 'destroyer', 'menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
public function create(Request $request)
|
||||
{
|
||||
$parent_menus = Menu::whereNull('parent_id')->get();
|
||||
return view("menus.create", compact('parent_menus'));
|
||||
$menuId = $request->query('menu_id'); // Get menu_id from request
|
||||
$menu = Menu::with('children')->find($menuId); // Find the menu
|
||||
|
||||
// Get IDs of all child menus to exclude
|
||||
$excludedIds = $menu ? $this->getChildMenuIds($menu) : [$menuId];
|
||||
|
||||
// Fetch only menus that have children and are not in the excluded list
|
||||
$parent_menus = Menu::whereHas('children')
|
||||
->whereNotIn('id', $excludedIds)
|
||||
->get();
|
||||
|
||||
return view("menus.create", compact('parent_menus', 'menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,11 +73,16 @@ class MenusController extends Controller
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
public function edit(string $id, Request $request)
|
||||
{
|
||||
$menu = Menu::findOrFail($id);
|
||||
$parent_menus = Menu::whereNull('parent_id')->where('id','!=',$id)->get();
|
||||
return view("menus.edit", compact('menu','parent_menus'));
|
||||
$menuId = $request->query('menu_id');
|
||||
$menu = Menu::with('children')->find($id);
|
||||
$excludedIds = $menu ? $this->getChildMenuIds($menu) : [$id];
|
||||
|
||||
$parent_menus = Menu::whereHas('children')
|
||||
->whereNotIn('id', $excludedIds)
|
||||
->get();
|
||||
return view("menus.edit", compact('menu','parent_menus', 'menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,4 +132,15 @@ class MenusController extends Controller
|
||||
$child->delete();
|
||||
}
|
||||
}
|
||||
|
||||
private function getChildMenuIds($menu)
|
||||
{
|
||||
$ids = [$menu->id]; // Start with current menu ID
|
||||
|
||||
foreach ($menu->children as $child) {
|
||||
$ids = array_merge($ids, $this->getChildMenuIds($child)); // Recursively fetch children
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
}
|
||||
|
||||
64
app/Http/Controllers/PaymentRecapsController.php
Normal file
64
app/Http/Controllers/PaymentRecapsController.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PaymentRecapsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('payment-recaps.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
21
app/Http/Controllers/PbgTaskAttachmentsController.php
Normal file
21
app/Http/Controllers/PbgTaskAttachmentsController.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\PbgTask;
|
||||
use App\Models\PbgTaskAttachment;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PbgTaskAttachmentsController extends Controller
|
||||
{
|
||||
public function show(string $id, Request $request){
|
||||
try{
|
||||
$title = $request->get('type') == "berita-acara" ? "Berita Acara" : "Bukti Bayar";
|
||||
$data = PbgTaskAttachment::findOrFail($id);
|
||||
$pbg = PbgTask::findOrFail($data->pbg_task_id);
|
||||
return view('pbg-task-attachment.show', compact('data', 'pbg', 'title'));
|
||||
}catch(\Exception $e){
|
||||
return view('pages.404');
|
||||
}
|
||||
}
|
||||
}
|
||||
178
app/Http/Controllers/QuickSearchController.php
Normal file
178
app/Http/Controllers/QuickSearchController.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\PbgTaskApplicationTypes;
|
||||
use App\Enums\PbgTaskStatus;
|
||||
use App\Http\Resources\TaskAssignmentsResource;
|
||||
use App\Models\PbgTask;
|
||||
use App\Models\TaskAssignment;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class QuickSearchController extends Controller
|
||||
{
|
||||
public function index(){
|
||||
return view("quick-search.index");
|
||||
}
|
||||
|
||||
public function public_search(){
|
||||
return view("public-search.index");
|
||||
}
|
||||
|
||||
public function search_result(Request $request){
|
||||
$keyword = $request->get("keyword");
|
||||
|
||||
return view('quick-search.result', compact('keyword'));
|
||||
}
|
||||
|
||||
public function quick_search_datatable(Request $request)
|
||||
{
|
||||
try {
|
||||
// Gunakan subquery untuk performa yang lebih baik dan menghindari duplikasi
|
||||
$query = PbgTask::select([
|
||||
'pbg_task.*',
|
||||
DB::raw('(SELECT name_building FROM pbg_task_details WHERE pbg_task_details.pbg_task_uid = pbg_task.uuid LIMIT 1) as name_building'),
|
||||
DB::raw('(SELECT nilai_retribusi_bangunan FROM pbg_task_retributions WHERE pbg_task_retributions.pbg_task_uid = pbg_task.uuid LIMIT 1) as nilai_retribusi_bangunan'),
|
||||
DB::raw('(SELECT note FROM pbg_statuses WHERE pbg_statuses.pbg_task_uuid = pbg_task.uuid LIMIT 1) as note')
|
||||
])
|
||||
->orderBy('pbg_task.id', 'desc');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$search = trim($request->get('search'));
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('pbg_task.registration_number', 'LIKE', "%$search%")
|
||||
->orWhere('pbg_task.name', 'LIKE', "%$search%")
|
||||
->orWhere('pbg_task.owner_name', 'LIKE', "%$search%")
|
||||
->orWhere('pbg_task.address', 'LIKE', "%$search%")
|
||||
->orWhereExists(function ($subQuery) use ($search) {
|
||||
$subQuery->select(DB::raw(1))
|
||||
->from('pbg_task_details')
|
||||
->whereColumn('pbg_task_details.pbg_task_uid', 'pbg_task.uuid')
|
||||
->where('pbg_task_details.name_building', 'LIKE', "%$search%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return response()->json($query->paginate());
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("Error fetching datatable data: " . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Terjadi kesalahan saat mengambil data.',
|
||||
'error' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function public_search_datatable(Request $request)
|
||||
{
|
||||
try {
|
||||
// Hanya proses jika ada keyword search
|
||||
if (!$request->filled('search') || trim($request->get('search')) === '') {
|
||||
return response()->json([
|
||||
'data' => [],
|
||||
'total' => 0,
|
||||
'current_page' => 1,
|
||||
'last_page' => 1,
|
||||
'per_page' => 15,
|
||||
'from' => null,
|
||||
'to' => null
|
||||
]);
|
||||
}
|
||||
|
||||
$search = trim($request->get('search'));
|
||||
|
||||
// Validasi minimal 3 karakter
|
||||
if (strlen($search) < 3) {
|
||||
return response()->json([
|
||||
'data' => [],
|
||||
'total' => 0,
|
||||
'current_page' => 1,
|
||||
'last_page' => 1,
|
||||
'per_page' => 15,
|
||||
'from' => null,
|
||||
'to' => null,
|
||||
'message' => 'Minimal 3 karakter untuk pencarian'
|
||||
]);
|
||||
}
|
||||
|
||||
// Gunakan subquery untuk performa yang lebih baik dan menghindari duplikasi
|
||||
$query = PbgTask::select([
|
||||
'pbg_task.*',
|
||||
DB::raw('(SELECT name_building FROM pbg_task_details WHERE pbg_task_details.pbg_task_uid = pbg_task.uuid LIMIT 1) as name_building'),
|
||||
DB::raw('(SELECT nilai_retribusi_bangunan FROM pbg_task_retributions WHERE pbg_task_retributions.pbg_task_uid = pbg_task.uuid LIMIT 1) as nilai_retribusi_bangunan'),
|
||||
DB::raw('(SELECT note FROM pbg_statuses WHERE pbg_statuses.pbg_task_uuid = pbg_task.uuid LIMIT 1) as note')
|
||||
])
|
||||
->where(function ($q) use ($search) {
|
||||
$q->where('pbg_task.registration_number', 'LIKE', "%$search%")
|
||||
->orWhere('pbg_task.name', 'LIKE', "%$search%")
|
||||
->orWhere('pbg_task.owner_name', 'LIKE', "%$search%")
|
||||
->orWhere('pbg_task.address', 'LIKE', "%$search%")
|
||||
->orWhereExists(function ($subQuery) use ($search) {
|
||||
$subQuery->select(DB::raw(1))
|
||||
->from('pbg_task_details')
|
||||
->whereColumn('pbg_task_details.pbg_task_uid', 'pbg_task.uuid')
|
||||
->where('pbg_task_details.name_building', 'LIKE', "%$search%");
|
||||
});
|
||||
})
|
||||
->orderBy('pbg_task.id', 'desc');
|
||||
|
||||
$result = $query->paginate();
|
||||
|
||||
// Tambahkan message jika tidak ada hasil
|
||||
if ($result->total() === 0) {
|
||||
$result = $result->toArray();
|
||||
$result['message'] = 'Tidak ada data yang ditemukan';
|
||||
}
|
||||
|
||||
return response()->json($result);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("Error fetching datatable data: " . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Terjadi kesalahan saat mengambil data.',
|
||||
'error' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
try {
|
||||
$data = PbgTask::with([
|
||||
'pbg_task_retributions',
|
||||
'pbg_task_index_integrations',
|
||||
'pbg_task_retributions.pbg_task_prasarana',
|
||||
'pbg_status'
|
||||
])->findOrFail($id);
|
||||
|
||||
$statusOptions = PbgTaskStatus::getStatuses();
|
||||
$applicationTypes = PbgTaskApplicationTypes::labels();
|
||||
|
||||
return view("quick-search.detail", compact("data", 'statusOptions', 'applicationTypes'));
|
||||
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
|
||||
Log::warning("PbgTask with ID {$id} not found.");
|
||||
return redirect()->route('quick-search.index')->with('error', 'Data tidak ditemukan.');
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("Error in QuickSearchController@show: " . $e->getMessage());
|
||||
return response()->view('pages.404', [], 500); // Optional: create `resources/views/errors/500.blade.php`
|
||||
}
|
||||
}
|
||||
|
||||
public function task_assignments(Request $request, $uuid){
|
||||
try{
|
||||
$query = TaskAssignment::query()
|
||||
->where('pbg_task_uid', $uuid)
|
||||
->orderBy('id', 'desc');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$query->where('name', 'like', "%{$request->get('search')}%")
|
||||
->orWhere('email', 'like', "%{$request->get('search')}%");
|
||||
}
|
||||
|
||||
return TaskAssignmentsResource::collection($query->paginate(config('app.paginate_per_page', 50)));
|
||||
}catch(\Exception $exception){
|
||||
return response()->json(['message' => $exception->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
app/Http/Controllers/Report/GrowthReportsController.php
Normal file
14
app/Http/Controllers/Report/GrowthReportsController.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Report;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Menu;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GrowthReportsController extends Controller
|
||||
{
|
||||
public function index(){
|
||||
return view('report.growth-report.index');
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ class ReportTourismController extends Controller
|
||||
public function index()
|
||||
{
|
||||
$tourismBasedKBLI = TourismBasedKBLI::all();
|
||||
info($tourismBasedKBLI);
|
||||
return view('report.tourisms.index', compact('tourismBasedKBLI'));
|
||||
}
|
||||
}
|
||||
12
app/Http/Controllers/ReportPaymentRecapsController.php
Normal file
12
app/Http/Controllers/ReportPaymentRecapsController.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ReportPaymentRecapsController extends Controller
|
||||
{
|
||||
public function index(Request $request){
|
||||
return view('report-payment-recaps.index');
|
||||
}
|
||||
}
|
||||
12
app/Http/Controllers/ReportPbgPTSPController.php
Normal file
12
app/Http/Controllers/ReportPbgPTSPController.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ReportPbgPTSPController extends Controller
|
||||
{
|
||||
public function index(Request $request){
|
||||
return view('report-pbg-ptsp.index');
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,37 @@
|
||||
|
||||
namespace App\Http\Controllers\RequestAssignment;
|
||||
|
||||
use App\Enums\PbgTaskApplicationTypes;
|
||||
use App\Enums\PbgTaskFilterData;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PbgTask;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use App\Enums\PbgTaskStatus;
|
||||
|
||||
class PbgTaskController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('pbg_task.index');
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
$filter = $request->query('filter');
|
||||
|
||||
$permissions = $this->permissions[$menuId]?? []; // Avoid undefined index error
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
|
||||
return view('pbg_task.index', [
|
||||
'creator' => $creator,
|
||||
'updater' => $updater,
|
||||
'destroyer' => $destroyer,
|
||||
'filter' => $filter,
|
||||
'filterOptions' => PbgTaskFilterData::getAllOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,8 +56,24 @@ class PbgTaskController extends Controller
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
$data = PbgTask::with(['pbg_task_retributions','pbg_task_index_integrations', 'pbg_task_retributions.pbg_task_prasarana', 'taskAssignments'])->findOrFail($id);
|
||||
return view("pbg_task.show", compact("data"));
|
||||
$data = PbgTask::with([
|
||||
'pbg_task_retributions',
|
||||
'pbg_task_index_integrations',
|
||||
'pbg_task_retributions.pbg_task_prasarana',
|
||||
'pbg_task_detail',
|
||||
'pbg_status',
|
||||
'dataLists' => function($query) {
|
||||
$query->orderBy('data_type')->orderBy('name');
|
||||
}
|
||||
])->findOrFail($id);
|
||||
|
||||
// Group data lists by data_type for easier display
|
||||
$dataListsByType = $data->dataLists->groupBy('data_type');
|
||||
|
||||
$statusOptions = PbgTaskStatus::getStatuses();
|
||||
$applicationTypes = PbgTaskApplicationTypes::labels();
|
||||
|
||||
return view("pbg_task.show", compact("data", 'statusOptions', 'applicationTypes', 'dataListsByType'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,23 +10,31 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class RolesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view("roles.index");
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
$permissions = $this->permissions[$menuId]?? []; // Avoid undefined index error
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
|
||||
return view("roles.index", compact('creator', 'updater', 'destroyer', 'menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
public function create(Request $request)
|
||||
{
|
||||
return view("roles.create");
|
||||
$menuId = $request->query('menu_id');
|
||||
return view("roles.create", compact('menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,10 +67,11 @@ class RolesController extends Controller
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
public function edit(string $id, Request $request)
|
||||
{
|
||||
$menuId = $request->query('menu_id');
|
||||
$role = Role::findOrFail($id);
|
||||
return view("roles.edit", compact('role'));
|
||||
return view("roles.edit", compact('role', 'menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,12 +109,13 @@ class RolesController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
public function menu_permission(string $role_id){
|
||||
public function menu_permission(string $role_id, Request $request){
|
||||
try{
|
||||
$menuId = $request->query('menu_id');
|
||||
$role = Role::findOrFail($role_id);
|
||||
$menus = Menu::all();
|
||||
$role_menus = RoleMenu::where('role_id', $role_id)->get() ?? collect();
|
||||
return view('roles.role_menu', compact('role', 'menus', 'role_menus'));
|
||||
return view('roles.role_menu', compact('role', 'menus', 'role_menus', 'menuId'));
|
||||
}catch(\Exception $e){
|
||||
return redirect()->back()->with("error", $e->getMessage());
|
||||
}
|
||||
@@ -113,8 +123,9 @@ class RolesController extends Controller
|
||||
|
||||
public function update_menu_permission(Request $request, string $role_id){
|
||||
try{
|
||||
$menuId = $request->query('menu_id');
|
||||
$validateData = $request->validate([
|
||||
"permissions" => "array",
|
||||
"permissions" => "nullable|array",
|
||||
"permissions.*.allow_show" => "nullable|boolean",
|
||||
"permissions.*.allow_create" => "nullable|boolean",
|
||||
"permissions.*.allow_update" => "nullable|boolean",
|
||||
@@ -123,6 +134,13 @@ class RolesController extends Controller
|
||||
|
||||
$role = Role::find($role_id);
|
||||
|
||||
// Jika `permissions` tidak ada atau kosong, hapus semua permissions terkait
|
||||
if (!isset($validateData['permissions']) || empty($validateData['permissions'])) {
|
||||
$role->menus()->detach();
|
||||
return redirect()->route("roles.index", ['menu_id' => $menuId])
|
||||
->with('success', 'All menu permissions have been removed.');
|
||||
}
|
||||
|
||||
$permissionsArray = [];
|
||||
foreach ($validateData['permissions'] as $menu_id => $permission) {
|
||||
$permissionsArray[$menu_id] = [
|
||||
@@ -137,7 +155,7 @@ class RolesController extends Controller
|
||||
// Sync will update existing records and insert new ones
|
||||
$role->menus()->sync($permissionsArray);
|
||||
|
||||
return redirect()->route("role-menu.permission", $role_id)->with('success','Menu Permission updated successfully');
|
||||
return redirect()->route("roles.index", ['menu_id' => $menuId])->with('success','Menu Permission updated successfully');
|
||||
}catch(\Exception $e){
|
||||
Log::error("Error updating role_menu:", ["error" => $e->getMessage()]);
|
||||
return redirect()->route("role-menu.permission", $role_id)->with("error", $e->getMessage());
|
||||
|
||||
0
app/Http/Controllers/RoutingController.php
Executable file → Normal file
0
app/Http/Controllers/RoutingController.php
Executable file → Normal file
@@ -6,6 +6,8 @@ use App\Http\Controllers\Controller;
|
||||
use App\Services\ServiceSIMBG;
|
||||
use Illuminate\Http\Request;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
class SyncronizeController extends Controller
|
||||
{
|
||||
protected $service_simbg;
|
||||
@@ -13,7 +15,27 @@ class SyncronizeController extends Controller
|
||||
$this->service_simbg = $service_simbg;
|
||||
}
|
||||
public function index(Request $request){
|
||||
return view('settings.syncronize.index');
|
||||
$menuId = $request->query('menu_id');
|
||||
$user = Auth::user();
|
||||
$userId = $user->id;
|
||||
|
||||
// Ambil role_id yang dimiliki user
|
||||
$roleIds = DB::table('user_role')
|
||||
->where('user_id', $userId)
|
||||
->pluck('role_id');
|
||||
|
||||
// Ambil data akses berdasarkan role_id dan menu_id
|
||||
$roleAccess = DB::table('role_menu')
|
||||
->whereIn('role_id', $roleIds)
|
||||
->where('menu_id', $menuId)
|
||||
->first();
|
||||
|
||||
// Pastikan roleAccess tidak null sebelum mengakses properti
|
||||
$creator = $roleAccess->allow_create ?? 0;
|
||||
$updater = $roleAccess->allow_update ?? 0;
|
||||
$destroyer = $roleAccess->allow_destroy ?? 0;
|
||||
|
||||
return view('settings.syncronize.index', compact('creator', 'updater', 'destroyer'));
|
||||
}
|
||||
|
||||
public function syncPbgTask(){
|
||||
|
||||
78
app/Http/Controllers/TaxationController.php
Normal file
78
app/Http/Controllers/TaxationController.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Tax;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TaxationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
$permissions = $this->permissions[$menuId]?? []; // Avoid undefined index error
|
||||
$creator = $permissions['allow_create'] ?? 0;
|
||||
$updater = $permissions['allow_update'] ?? 0;
|
||||
$destroyer = $permissions['allow_destroy'] ?? 0;
|
||||
return view('taxation.index', compact('creator', 'updater', 'destroyer', 'menuId'));
|
||||
}
|
||||
|
||||
public function upload(Request $request)
|
||||
{
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
return view('taxation.upload', compact('menuId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(Request $request, string $id)
|
||||
{
|
||||
$menuId = $request->query('menu_id') ?? $request->input('menu_id');
|
||||
$data = Tax::find($id);
|
||||
return view('taxation.edit', compact('menuId', 'data'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
28
app/Http/Controllers/TpatptsController.php
Normal file
28
app/Http/Controllers/TpatptsController.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TpatptsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('tpa-tpt.index');
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
167
app/Http/Middleware/ValidateApiTokenForWeb.php
Normal file
167
app/Http/Middleware/ValidateApiTokenForWeb.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ValidateApiTokenForWeb
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
* Middleware ini memvalidasi token API untuk web requests
|
||||
* dan melakukan auto-logout jika token tidak valid
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
// Skip validation untuk non-authenticated routes
|
||||
if (!Auth::check()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// Skip validation untuk API routes (sudah ditangani oleh auth:sanctum)
|
||||
if ($request->is('api/*')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
$sessionToken = Session::get('api_token');
|
||||
|
||||
// Jika tidak ada token di session, generate token baru
|
||||
if (!$sessionToken) {
|
||||
$this->generateNewToken($user);
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// Validasi token API
|
||||
if (!$this->isTokenValid($sessionToken, $user)) {
|
||||
// Token invalid, check apakah ada user lain yang login
|
||||
if ($this->hasOtherUserLoggedIn($user)) {
|
||||
// User lain sudah login, force logout user ini
|
||||
$this->forceLogout($request, 'User lain telah login. Silakan login ulang.');
|
||||
return $this->redirectToLogin($request, 'User lain telah login. Silakan login ulang.');
|
||||
} else {
|
||||
// Generate token baru jika tidak ada user lain
|
||||
$this->generateNewToken($user);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check apakah token API masih valid
|
||||
*/
|
||||
private function isTokenValid($sessionToken, $user): bool
|
||||
{
|
||||
if (!$sessionToken || !$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract plain token dari session token
|
||||
$tokenParts = explode('|', $sessionToken);
|
||||
if (count($tokenParts) !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$plainToken = $tokenParts[1];
|
||||
|
||||
// Check token di database
|
||||
$validToken = PersonalAccessToken::where('tokenable_id', $user->id)
|
||||
->where('tokenable_type', get_class($user))
|
||||
->where('token', hash('sha256', $plainToken))
|
||||
->where(function($query) {
|
||||
$query->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->first();
|
||||
|
||||
return $validToken !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check apakah ada user lain yang login (token baru dibuat)
|
||||
*/
|
||||
private function hasOtherUserLoggedIn($currentUser): bool
|
||||
{
|
||||
$sessionUserId = Session::get('user_id');
|
||||
|
||||
// Jika ada user_id di session tapi tidak match dengan current user
|
||||
if ($sessionUserId && $sessionUserId != $currentUser->id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check apakah ada token aktif lain untuk user ini
|
||||
$activeTokens = PersonalAccessToken::where('tokenable_id', $currentUser->id)
|
||||
->where('tokenable_type', get_class($currentUser))
|
||||
->where(function($query) {
|
||||
$query->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->count();
|
||||
|
||||
// Jika tidak ada token aktif, kemungkinan user lain sudah login
|
||||
return $activeTokens === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate token baru untuk user
|
||||
*/
|
||||
private function generateNewToken($user): void
|
||||
{
|
||||
// Hapus token lama
|
||||
PersonalAccessToken::where('tokenable_id', $user->id)
|
||||
->where('tokenable_type', get_class($user))
|
||||
->delete();
|
||||
|
||||
// Generate token baru
|
||||
$tokenName = config('app.name', 'Laravel') . '-' . $user->id . '-' . time();
|
||||
$token = $user->createToken($tokenName, ['*'], now()->addDays(30))->plainTextToken;
|
||||
|
||||
// Simpan token di session
|
||||
Session::put('api_token', $token);
|
||||
Session::put('user_id', $user->id);
|
||||
Session::put('login_timestamp', now()->timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force logout user dan clear semua sessions
|
||||
*/
|
||||
private function forceLogout(Request $request, string $reason = 'Session tidak valid'): void
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($user) {
|
||||
// Delete all tokens for this user
|
||||
PersonalAccessToken::where('tokenable_id', $user->id)
|
||||
->where('tokenable_type', get_class($user))
|
||||
->delete();
|
||||
}
|
||||
|
||||
// Clear session
|
||||
Session::forget(['api_token', 'user_id', 'login_timestamp']);
|
||||
Auth::guard('web')->logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect ke login dengan pesan error
|
||||
*/
|
||||
private function redirectToLogin(Request $request, string $message): Response
|
||||
{
|
||||
if ($request->expectsJson() || $request->ajax()) {
|
||||
return response()->json([
|
||||
'error' => $message,
|
||||
'redirect' => route('login'),
|
||||
'force_logout' => true
|
||||
], 401);
|
||||
}
|
||||
|
||||
return redirect()->route('login')->with('error', $message);
|
||||
}
|
||||
}
|
||||
0
app/Http/Requests/Auth/LoginRequest.php
Executable file → Normal file
0
app/Http/Requests/Auth/LoginRequest.php
Executable file → Normal file
@@ -22,13 +22,14 @@ class SpatialPlanningRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'string',
|
||||
'kbli' => 'string',
|
||||
'activities' => 'string',
|
||||
'area' => 'string',
|
||||
'location' => 'string',
|
||||
'number' => 'string',
|
||||
'date' => 'date_format:Y-m-d',
|
||||
'name' => 'nullable|string',
|
||||
'kbli' => 'nullable|string',
|
||||
'activities' => 'nullable|string',
|
||||
'area' => 'nullable|string',
|
||||
'location' => 'nullable|string',
|
||||
'number' => 'nullable|string',
|
||||
'date' => 'nullable|date_format:Y-m-d',
|
||||
'is_terbit' => 'nullable|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class SpatialPlanningsRequest 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 [
|
||||
'name' => ['required','string','max:255'],
|
||||
'kbli' => ['required','string','max:255'],
|
||||
'kegiatan' => ['required','string'],
|
||||
'luas' => ['required','numeric','regex:/^\d{1,16}(\.\d{1,2})?$/'],
|
||||
'lokasi' => ['required','string'],
|
||||
'nomor' => ['required','string','max:255',Rule::unique('spatial_plannings')->ignore($this->id)],
|
||||
'sp_date' => ['required','date'],
|
||||
];
|
||||
}
|
||||
}
|
||||
38
app/Http/Requests/TaxationsRequest.php
Normal file
38
app/Http/Requests/TaxationsRequest.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class TaxationsRequest 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 [
|
||||
'tax_no' => ['required', 'string', Rule::unique('taxs')->ignore($this->id)],
|
||||
'tax_code' => ['required', 'string'],
|
||||
'wp_name' => ['required', 'string'],
|
||||
'business_name' => ['required', 'string'],
|
||||
'address' => ['required', 'string'],
|
||||
'start_validity' => ['required', 'date_format:Y-m-d'],
|
||||
'end_validity' => ['required', 'date_format:Y-m-d'],
|
||||
'tax_value' => ['required', 'numeric', 'regex:/^\d{1,16}(\.\d{1,2})?$/'],
|
||||
'subdistrict' => ['required', 'string'],
|
||||
'village' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,8 @@ class DataSettingResource extends JsonResource
|
||||
'key' => $this->key,
|
||||
'value' => $this->value,
|
||||
'type' => $this->type,
|
||||
'created_at' => $this->created_at->toDateTimeString(),
|
||||
'updated_at' => $this->updated_at->toDateTimeString(),
|
||||
'created_at' => $this->created_at ? $this->created_at->toDateTimeString() : null,
|
||||
'updated_at' => $this->updated_at ? $this->updated_at->toDateTimeString() : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
@@ -14,13 +15,21 @@ class ImportDatasourceResource extends JsonResource
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$startTime = $this->start_time ? Carbon::parse($this->start_time) : null;
|
||||
$finishTime = $this->finish_time ? Carbon::parse($this->finish_time) : null;
|
||||
return [
|
||||
"id"=> $this->id,
|
||||
"message" => $this->message,
|
||||
"response_body" => $this->response_body,
|
||||
"status" => $this->status,
|
||||
"start_time" => $startTime ? $startTime->toDateTimeString() : null,
|
||||
"duration" => ($startTime && $finishTime)
|
||||
? $finishTime->diff($startTime)->format('%H:%I:%S')
|
||||
: null,
|
||||
"finish_time" => $finishTime ? $finishTime->toDateTimeString() : null,
|
||||
"created_at" => $this->created_at->toDateTimeString(),
|
||||
"updated_at" => $this->updated_at->toDateTimeString(),
|
||||
"failed_uuid" => $this->failed_uuid
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,16 @@ class MenuResource extends JsonResource
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return parent::toArray($request);
|
||||
// return parent::toArray($request);
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'icon' => $this->icon,
|
||||
'url' => $this->url,
|
||||
'sort_order' => $this->sort_order,
|
||||
'parent' => $this->parent ? new MenuResource($this->parent) : null,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
19
app/Http/Resources/PbgTaskGoogleSheetResource.php
Normal file
19
app/Http/Resources/PbgTaskGoogleSheetResource.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class PbgTaskGoogleSheetResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return parent::toArray($request);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,17 @@ class RequestAssignmentResouce extends JsonResource
|
||||
'due_date' => $this->due_date,
|
||||
'land_certificate_phase' => $this->land_certificate_phase,
|
||||
'task_created_at' => $this->task_created_at,
|
||||
'attachment_berita_acara' => $this->attachments
|
||||
->where('pbg_type', 'berita_acara')
|
||||
->sortByDesc('created_at')
|
||||
->first(),
|
||||
'attachment_bukti_bayar' => $this->attachments
|
||||
->where('pbg_type', 'bukti_bayar')
|
||||
->sortByDesc('created_at')
|
||||
->first(),
|
||||
'pbg_task_retributions' => $this->pbg_task_retributions,
|
||||
'pbg_task_detail' => $this->pbg_task_detail,
|
||||
'pbg_status' => $this->pbg_status,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Http\Resources;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class SpatialPlanningsResource extends JsonResource
|
||||
class TaskAssignmentsResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
19
app/Http/Resources/TaxationsResource.php
Normal file
19
app/Http/Resources/TaxationsResource.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TaxationsResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return parent::toArray($request);
|
||||
}
|
||||
}
|
||||
78
app/Imports/TaxationsImport.php
Normal file
78
app/Imports/TaxationsImport.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Imports;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\ToCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithBatchInserts;
|
||||
use Maatwebsite\Excel\Concerns\WithChunkReading;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadingRow;
|
||||
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use App\Models\Tax;
|
||||
|
||||
class TaxationsImport implements ToCollection, WithMultipleSheets, WithChunkReading, WithBatchInserts, ShouldQueue, WithHeadingRow
|
||||
{
|
||||
/**
|
||||
* @param Collection $collection
|
||||
*/
|
||||
public function collection(Collection $collection)
|
||||
{
|
||||
$batchData = [];
|
||||
$batchSize = 1000;
|
||||
|
||||
foreach ($collection as $row) {
|
||||
|
||||
$masaPajak = trim($row['masa_pajak']) ?? '';
|
||||
|
||||
$masaParts = explode('-', $masaPajak);
|
||||
|
||||
$startValidity = null;
|
||||
$endValidity = null;
|
||||
|
||||
if (count($masaParts) === 2) {
|
||||
$startValidity = \Carbon\Carbon::createFromFormat('d/m/Y', trim($masaParts[0]))->format('Y-m-d');
|
||||
$endValidity = \Carbon\Carbon::createFromFormat('d/m/Y', trim($masaParts[1]))->format('Y-m-d');
|
||||
}
|
||||
|
||||
$batchData[] = [
|
||||
'tax_code' => trim($row['kode']) ?? '',
|
||||
'tax_no' => trim($row['no']) ?? '',
|
||||
'npwpd' => trim($row['npwpd']) ?? '',
|
||||
'wp_name' => trim($row['nama_wp']) ?? '',
|
||||
'business_name' => trim($row['nama_usaha']) ?? '',
|
||||
'address' => trim($row['alamat_usaha']) ?? '',
|
||||
'start_validity' => $startValidity,
|
||||
'end_validity' => $endValidity,
|
||||
'tax_value' => (float) str_replace(',', '', trim($row['nilai_pajak']) ?? '0'),
|
||||
'subdistrict' => trim($row['kecamatan']) ?? '',
|
||||
'village' => trim($row['desa']) ?? '',
|
||||
];
|
||||
|
||||
if (count($batchData) >= $batchSize) {
|
||||
Tax::upsert($batchData, ['tax_no'], ['tax_code', 'tax_no', 'npwpd', 'wp_name', 'business_name', 'address', 'start_validity', 'end_validity', 'tax_value', 'subdistrict', 'village']);
|
||||
$batchData = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($batchData)) {
|
||||
Tax::upsert($batchData, ['tax_no'], ['tax_code', 'tax_no', 'npwpd', 'wp_name', 'business_name', 'address', 'start_validity', 'end_validity', 'tax_value', 'subdistrict', 'village']);
|
||||
}
|
||||
|
||||
}
|
||||
public function sheets(): array {
|
||||
return [
|
||||
0 => $this
|
||||
];
|
||||
}
|
||||
|
||||
public function chunkSize(): int
|
||||
{
|
||||
return 1000;
|
||||
}
|
||||
|
||||
public function batchSize(): int
|
||||
{
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
73
app/Jobs/RetrySyncronizeJob.php
Normal file
73
app/Jobs/RetrySyncronizeJob.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\ImportDatasourceStatus;
|
||||
use App\Models\BigdataResume;
|
||||
use App\Models\ImportDatasource;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use App\Services\ServiceTabPbgTask;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class RetrySyncronizeJob implements ShouldQueue
|
||||
{
|
||||
use Queueable, Dispatchable, InteractsWithQueue, SerializesModels;
|
||||
private $import_datasource_id;
|
||||
public function __construct(int $import_datasource_id)
|
||||
{
|
||||
$this->import_datasource_id = $import_datasource_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
try{
|
||||
$service_tab_pbg_task = app(ServiceTabPbgTask::class);
|
||||
|
||||
$failed_import = ImportDatasource::find($this->import_datasource_id);
|
||||
|
||||
$failed_import->update([
|
||||
'message' => "Retrying from UUID: ". $failed_import->failed_uuid,
|
||||
'status' => ImportDatasourceStatus::Processing->value,
|
||||
'start_time' => now()
|
||||
]);
|
||||
|
||||
$current_failed_uuid = null;
|
||||
try{
|
||||
$service_tab_pbg_task->run_service($failed_import->failed_uuid);
|
||||
}catch(\Exception $e){
|
||||
$current_failed_uuid = $service_tab_pbg_task->getFailedUUID();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
BigdataResume::generateResumeData($failed_import->id, date('Y'), "simbg");
|
||||
|
||||
$failed_import->update([
|
||||
'status' => ImportDatasourceStatus::Success->value,
|
||||
'message' => "Retry completed successfully from UUID: ". $failed_import->failed_uuid,
|
||||
'finish_time' => now(),
|
||||
'failed_uuid' => null
|
||||
]);
|
||||
}catch(\Exception $e){
|
||||
Log::error("RetrySyncronizeJob Failed: ". $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
if(isset($failed_import)){
|
||||
$failed_import->update([
|
||||
'status' => ImportDatasourceStatus::Failed->value,
|
||||
'message' => "Retry failed from UUID: ". $failed_import->failed_uuid,
|
||||
'finish_time' => now(),
|
||||
'failed_uuid' => $current_failed_uuid
|
||||
]);
|
||||
}
|
||||
|
||||
$this->fail($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
229
app/Jobs/ScrapingDataJob.php
Normal file
229
app/Jobs/ScrapingDataJob.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\BigdataResume;
|
||||
use App\Models\ImportDatasource;
|
||||
use App\Models\PbgTask;
|
||||
use App\Services\ServiceGoogleSheet;
|
||||
use App\Services\ServicePbgTask;
|
||||
use App\Services\ServiceTabPbgTask;
|
||||
use App\Services\ServiceTokenSIMBG;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ScrapingDataJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Use dedicated scraping queue
|
||||
$this->queue = 'scraping';
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job with optimized schema:
|
||||
* 1. Scrape Google Sheet first
|
||||
* 2. Scrape PBG Task to get parent data
|
||||
* 3. Loop through parent tasks to scrape details via ServiceTabPbgTask
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$import_datasource = null;
|
||||
$failed_uuid = null;
|
||||
$processedTasks = 0;
|
||||
$totalTasks = 0;
|
||||
|
||||
try {
|
||||
Log::info("=== SCRAPING DATA JOB STARTED ===");
|
||||
|
||||
// Initialize services
|
||||
$service_google_sheet = app(ServiceGoogleSheet::class);
|
||||
$service_pbg_task = app(ServicePbgTask::class);
|
||||
$service_tab_pbg_task = app(ServiceTabPbgTask::class);
|
||||
|
||||
// Create ImportDatasource record
|
||||
$import_datasource = ImportDatasource::create([
|
||||
'message' => 'Starting optimized scraping process...',
|
||||
'response_body' => null,
|
||||
'status' => 'processing',
|
||||
'start_time' => now(),
|
||||
'failed_uuid' => null
|
||||
]);
|
||||
|
||||
Log::info("ImportDatasource created", ['id' => $import_datasource->id]);
|
||||
|
||||
// STEP 1: Scrape Google Sheet data first
|
||||
Log::info("=== STEP 1: SCRAPING GOOGLE SHEET ===");
|
||||
$import_datasource->update(['message' => 'Scraping Google Sheet data...']);
|
||||
|
||||
$service_google_sheet->run_service();
|
||||
Log::info("Google Sheet scraping completed successfully");
|
||||
|
||||
// STEP 2: Scrape PBG Task to get parent data
|
||||
Log::info("=== STEP 2: SCRAPING PBG TASK PARENT DATA ===");
|
||||
$import_datasource->update(['message' => 'Scraping PBG Task parent data...']);
|
||||
|
||||
$service_pbg_task->run_service();
|
||||
Log::info("PBG Task parent data scraping completed");
|
||||
|
||||
// STEP 3: Get all PBG tasks for detail scraping
|
||||
$totalTasks = PbgTask::count();
|
||||
Log::info("=== STEP 3: SCRAPING PBG TASK DETAILS ===", [
|
||||
'total_tasks' => $totalTasks
|
||||
]);
|
||||
|
||||
$import_datasource->update([
|
||||
'message' => "Scraping details for {$totalTasks} PBG tasks..."
|
||||
]);
|
||||
|
||||
// Process tasks in chunks for memory efficiency
|
||||
$chunkSize = 100;
|
||||
$processedTasks = 0;
|
||||
|
||||
PbgTask::orderBy('id')->chunk($chunkSize, function ($pbg_tasks) use (
|
||||
$service_tab_pbg_task,
|
||||
&$processedTasks,
|
||||
$totalTasks,
|
||||
$import_datasource,
|
||||
&$failed_uuid
|
||||
) {
|
||||
foreach ($pbg_tasks as $pbg_task) {
|
||||
try {
|
||||
// Scrape all details for this task
|
||||
$this->processTaskDetails($service_tab_pbg_task, $pbg_task->uuid);
|
||||
|
||||
$processedTasks++;
|
||||
|
||||
// Update progress every 10 tasks
|
||||
if ($processedTasks % 10 === 0) {
|
||||
$progress = round(($processedTasks / $totalTasks) * 100, 2);
|
||||
Log::info("Progress update", [
|
||||
'processed' => $processedTasks,
|
||||
'total' => $totalTasks,
|
||||
'progress' => "{$progress}%"
|
||||
]);
|
||||
|
||||
$import_datasource->update([
|
||||
'message' => "Processing details: {$processedTasks}/{$totalTasks} ({$progress}%)"
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::warning("Failed to process task details", [
|
||||
'uuid' => $pbg_task->uuid,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
// Store failed UUID but continue processing
|
||||
$failed_uuid = $pbg_task->uuid;
|
||||
|
||||
// Only stop if it's a critical error
|
||||
if ($this->isCriticalError($e)) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Log::info("Task details scraping completed", [
|
||||
'processed_tasks' => $processedTasks,
|
||||
'total_tasks' => $totalTasks
|
||||
]);
|
||||
|
||||
// STEP 4: Generate BigData Resume
|
||||
Log::info("=== STEP 4: GENERATING BIGDATA RESUME ===");
|
||||
$import_datasource->update(['message' => 'Generating BigData resume...']);
|
||||
|
||||
BigdataResume::generateResumeData($import_datasource->id, date('Y'), "simbg");
|
||||
|
||||
Log::info("BigData resume generated successfully");
|
||||
|
||||
// Update final status
|
||||
$import_datasource->update([
|
||||
'status' => 'success',
|
||||
'message' => "Scraping completed successfully. Processed {$processedTasks}/{$totalTasks} tasks.",
|
||||
'finish_time' => now(),
|
||||
'failed_uuid' => $failed_uuid // Store last failed UUID if any
|
||||
]);
|
||||
|
||||
Log::info("=== SCRAPING DATA JOB COMPLETED SUCCESSFULLY ===", [
|
||||
'import_datasource_id' => $import_datasource->id,
|
||||
'processed_tasks' => $processedTasks,
|
||||
'total_tasks' => $totalTasks,
|
||||
'has_failures' => !is_null($failed_uuid)
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('=== SCRAPING DATA JOB FAILED ===', [
|
||||
'error' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'processed_tasks' => $processedTasks,
|
||||
'total_tasks' => $totalTasks,
|
||||
'failed_uuid' => $failed_uuid,
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
// Update ImportDatasource with failure info
|
||||
if ($import_datasource) {
|
||||
$import_datasource->update([
|
||||
'status' => 'failed',
|
||||
'message' => "Scraping failed: {$e->getMessage()}. Processed {$processedTasks}/{$totalTasks} tasks.",
|
||||
'response_body' => 'Scraping process interrupted due to error',
|
||||
'finish_time' => now(),
|
||||
'failed_uuid' => $failed_uuid,
|
||||
]);
|
||||
}
|
||||
|
||||
// Don't retry this job
|
||||
$this->fail($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all detail endpoints for a single PBG task
|
||||
*/
|
||||
private function processTaskDetails(ServiceTabPbgTask $service, string $uuid): void
|
||||
{
|
||||
// Call all detail scraping methods for this task
|
||||
$service->scraping_task_details($uuid);
|
||||
$service->scraping_pbg_data_list($uuid);
|
||||
$service->scraping_task_retributions($uuid);
|
||||
$service->scraping_task_integrations($uuid);
|
||||
$service->scraping_task_detail_status($uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an error is critical enough to stop the entire process
|
||||
*/
|
||||
private function isCriticalError(\Exception $e): bool
|
||||
{
|
||||
$criticalMessages = [
|
||||
'authentication failed',
|
||||
'token expired',
|
||||
'database connection',
|
||||
'memory exhausted',
|
||||
'maximum execution time'
|
||||
];
|
||||
|
||||
$errorMessage = strtolower($e->getMessage());
|
||||
|
||||
foreach ($criticalMessages as $critical) {
|
||||
if (strpos($errorMessage, $critical) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user