# Project Job Costing

- Module key: `ProjectJobCosting`
- Category: `Project/Logistik`
- Path: `app/Modules/ProjectJobCosting`

## Ringkasan

Project Job Costing berada di kategori Project/Logistik. Modul ini memiliki 59 definisi route, 7 controller, 4 model, dan 10 view. Database module terdeteksi pada tabel: `project_jobs`, `project_job_assignments`, `project_job_work_logs`, `project_job_cost_rates`.

## Flowchart

```mermaid
flowchart TD
    A[User / Operator] --> B[Menu Project Job Costing]
    B --> C[Routes: 59]
    C --> D[Controllers: 7]
    D --> E[Views: 10]
    D --> F[(Tables: 4<br/>project_jobs<br/>project_job_assignments<br/>project_job_work_logs<br/>project_job_cost_rates)]
    D --> G[Validation / Business Rules]
    G --> F
    F --> H[Report / Export / Audit]
```


## Routes

- `Route::middleware(['auth', 'ip.block', 'menu.access'])->group(function () {`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::prefix('project-job-refs')->name('project_job_refs.')->group(function () {`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('karyawans', [ProjectJobRefController::class, 'karyawans'])->name('karyawans');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('jobs', [ProjectJobRefController::class, 'jobs'])->name('jobs');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::prefix('project-jobs')->name('project_jobs.')->group(function () {`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('/', [ProjectJobController::class, 'index'])->name('index');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('data', [ProjectJobController::class, 'data'])->name('data');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('sync-master', [ProjectJobController::class, 'syncMaster'])->name('sync_master');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('/', [ProjectJobController::class, 'store'])->name('store');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::put('{id}', [ProjectJobController::class, 'update'])->name('update');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::delete('{id}', [ProjectJobController::class, 'destroy'])->name('destroy');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::prefix('project-job-cost-rates')->name('project_job_cost_rates.')->group(function () {`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('/', [ProjectJobCostRateController::class, 'index'])->name('index');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('data', [ProjectJobCostRateController::class, 'data'])->name('data');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('template', [ProjectJobCostRateController::class, 'template'])->name('template');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('export-existing', [ProjectJobCostRateController::class, 'exportExisting'])->name('export_existing');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('error-report/{token}', [ProjectJobCostRateController::class, 'downloadErrorReport'])->name('error_report');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('import', [ProjectJobCostRateController::class, 'import'])->name('import');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('prefill-master', [ProjectJobCostRateController::class, 'prefillFromMaster'])->name('prefill_master');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('/', [ProjectJobCostRateController::class, 'store'])->name('store');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::put('{id}', [ProjectJobCostRateController::class, 'update'])->name('update');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::delete('{id}', [ProjectJobCostRateController::class, 'destroy'])->name('destroy');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::prefix('project-job-assignments')->name('project_job_assignments.')->group(function () {`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('/', [ProjectJobAssignmentController::class, 'index'])->name('index');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('present-employees', [ProjectJobAssignmentController::class, 'presentEmployees'])->name('present_employees');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('template', [ProjectJobAssignmentController::class, 'template'])->name('template');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('export-existing', [ProjectJobAssignmentController::class, 'exportExisting'])->name('export_existing');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('data', [ProjectJobAssignmentController::class, 'data'])->name('data');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('import', [ProjectJobAssignmentController::class, 'import'])->name('import');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('/', [ProjectJobAssignmentController::class, 'store'])->name('store');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::delete('{id}', [ProjectJobAssignmentController::class, 'destroy'])->name('destroy');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::prefix('project-job-work-logs')->name('project_job_work_logs.')->group(function () {`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('/', [ProjectJobWorkLogController::class, 'index'])->name('index');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('summary-page', [ProjectJobWorkLogController::class, 'summaryPage'])->name('summary_page');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('summary-export-xlsx', [ProjectJobWorkLogController::class, 'exportSummaryXlsx'])->name('summary_export_xlsx');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('summary-export-pdf', [ProjectJobWorkLogController::class, 'exportSummaryPdf'])->name('summary_export_pdf');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('template', [ProjectJobWorkLogController::class, 'template'])->name('template');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('export-existing', [ProjectJobWorkLogController::class, 'exportExisting'])->name('export_existing');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('data', [ProjectJobWorkLogController::class, 'data'])->name('data');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('import', [ProjectJobWorkLogController::class, 'import'])->name('import');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('generate-default', [ProjectJobWorkLogController::class, 'generateDefault'])->name('generate_default');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('/', [ProjectJobWorkLogController::class, 'store'])->name('store');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::put('{id}', [ProjectJobWorkLogController::class, 'update'])->name('update');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::delete('{id}', [ProjectJobWorkLogController::class, 'destroy'])->name('destroy');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('summary', [ProjectJobWorkLogController::class, 'summary'])->name('summary');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::prefix('project-job-timegrid')->name('project_job_timegrid.')->group(function () {`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('/', [ProjectJobTimegridController::class, 'index'])->name('index');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('template', [ProjectJobTimegridController::class, 'template'])->name('template');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('export-existing', [ProjectJobTimegridController::class, 'exportExisting'])->name('export_existing');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('context', [ProjectJobTimegridController::class, 'context'])->name('context');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('import', [ProjectJobTimegridController::class, 'import'])->name('import');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('save', [ProjectJobTimegridController::class, 'save'])->name('save');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::prefix('project-job-timeline')->name('project_job_timeline.')->group(function () {`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('/', [ProjectJobTimelineController::class, 'index'])->name('index');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('data', [ProjectJobTimelineController::class, 'data'])->name('data');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::get('pdf', [ProjectJobTimelineController::class, 'exportPdf'])->name('pdf');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::post('/', [ProjectJobTimelineController::class, 'store'])->name('store');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::put('{id}', [ProjectJobTimelineController::class, 'update'])->name('update');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`
- `Route::delete('{id}', [ProjectJobTimelineController::class, 'destroy'])->name('destroy');`  
  File: `app/Modules/ProjectJobCosting/routes/web.php`

## Controllers

- `app/Modules/ProjectJobCosting/Http/Controllers/ProjectJobAssignmentController.php`
- `app/Modules/ProjectJobCosting/Http/Controllers/ProjectJobController.php`
- `app/Modules/ProjectJobCosting/Http/Controllers/ProjectJobCostRateController.php`
- `app/Modules/ProjectJobCosting/Http/Controllers/ProjectJobRefController.php`
- `app/Modules/ProjectJobCosting/Http/Controllers/ProjectJobTimegridController.php`
- `app/Modules/ProjectJobCosting/Http/Controllers/ProjectJobTimelineController.php`
- `app/Modules/ProjectJobCosting/Http/Controllers/ProjectJobWorkLogController.php`

## Models

- `app/Modules/ProjectJobCosting/Models/ProjectJobAssignmentModel.php`
- `app/Modules/ProjectJobCosting/Models/ProjectJobCostRateModel.php`
- `app/Modules/ProjectJobCosting/Models/ProjectJobModel.php`
- `app/Modules/ProjectJobCosting/Models/ProjectJobWorkLogModel.php`

## Views

- `app/Modules/ProjectJobCosting/Resources/views/assignments.blade.php`
- `app/Modules/ProjectJobCosting/Resources/views/cost_rates.blade.php`
- `app/Modules/ProjectJobCosting/Resources/views/jobs.blade.php`
- `app/Modules/ProjectJobCosting/Resources/views/partials/nav.blade.php`
- `app/Modules/ProjectJobCosting/Resources/views/summary.blade.php`
- `app/Modules/ProjectJobCosting/Resources/views/summary_pdf.blade.php`
- `app/Modules/ProjectJobCosting/Resources/views/timegrid.blade.php`
- `app/Modules/ProjectJobCosting/Resources/views/timeline.blade.php`
- `app/Modules/ProjectJobCosting/Resources/views/timeline_pdf.blade.php`
- `app/Modules/ProjectJobCosting/Resources/views/work_logs.blade.php`

## Database / Migration

### `project_jobs`
- Actions: `create`
- Columns: 8
  - `code:string`
  - `name:string`
  - `effective_start:date`
  - `effective_end:date`
  - `is_active:boolean`
  - `created_by:unsignedBigInteger`
  - `created_by:foreign`
  - `is_active:index`
- Migrations:
  - `app/Modules/ProjectJobCosting/Database/migrations/2026_03_18_010000_create_project_jobs_table.php`
### `project_job_assignments`
- Actions: `create`
- Columns: 10
  - `karyawan_id:unsignedBigInteger`
  - `job_id:unsignedBigInteger`
  - `work_date:date`
  - `is_active:boolean`
  - `created_by:unsignedBigInteger`
  - `karyawan_id:foreign`
  - `job_id:foreign`
  - `created_by:foreign`
  - `karyawan_id:unique`
  - `work_date:index`
- Migrations:
  - `app/Modules/ProjectJobCosting/Database/migrations/2026_03_18_010010_create_project_job_assignments_table.php`
### `project_job_work_logs`
- Actions: `create`
- Columns: 15
  - `karyawan_id:unsignedBigInteger`
  - `job_id:unsignedBigInteger`
  - `work_date:date`
  - `start_time:time`
  - `end_time:time`
  - `minutes_total:unsignedInteger`
  - `source:string`
  - `notes:text`
  - `created_by:unsignedBigInteger`
  - `is_active:boolean`
  - `karyawan_id:foreign`
  - `job_id:foreign`
  - `created_by:foreign`
  - `karyawan_id:index`
  - `job_id:index`
- Migrations:
  - `app/Modules/ProjectJobCosting/Database/migrations/2026_03_18_010020_create_project_job_work_logs_table.php`
### `project_job_cost_rates`
- Actions: `create`
- Columns: 14
  - `karyawan_id:unsignedBigInteger`
  - `basis_type:string`
  - `hourly_rate:decimal`
  - `monthly_cost:decimal`
  - `basis_hours:decimal`
  - `effective_start:date`
  - `effective_end:date`
  - `notes:text`
  - `is_active:boolean`
  - `created_by:unsignedBigInteger`
  - `karyawan_id:foreign`
  - `created_by:foreign`
  - `karyawan_id:index`
  - `effective_start:index`
- Migrations:
  - `app/Modules/ProjectJobCosting/Database/migrations/2026_03_25_090000_create_project_job_cost_rates_table.php`
