docs: add remaining implementation plans
Adds plan docs that were pending in the working tree, matching the existing docs/superpowers/plans/ pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mf6qFtiQoRuSh9zQSPF1wS
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
24e25f8c9d
commit
4471b224cf
@@ -0,0 +1,965 @@
|
||||
# Between-Year Draft → Send-to-Planning Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add a draft/send-to-planning workflow to the "คำขอระหว่างปี" (between-year budget request) menu — save as draft, then explicitly select rows and click "ส่งกองนโยบายและแผน" to send them to the planning office, locking them from further edits.
|
||||
|
||||
**Architecture:** No new tables/endpoints. Everything hinges on the existing `request_budget.status_id` column (0 = draft, 1 = sent) on the SAME record. The list already shows both states (once we remove a stale query filter); a new checkbox + bulk-send button flips `status_id` 0→1 via the existing generic `RequestBudgetService.updateMany()`. The list-level UI lives in `list-between-year.component.ts/html`; the record-level form lives in a large shared component (`RequestOtherExpensesFormComponent`) reused by many other project-proposal types, so every change there is gated behind `this.typeUrl == 21` (the between-year route's type) to avoid touching other flows.
|
||||
|
||||
**Tech Stack:** Angular 17, Angular Material (`mat-table`, `mat-checkbox`), Angular CDK `SelectionModel`, RxJS, SweetAlert2 (via `SweetalertService`).
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Scope is strictly the "คำขอระหว่างปี" menu (`typeUrl == 21` / `is_between_year`). Do not change behavior for any other `typeUrl` value in shared files (`request-budget-statistics.container.ts`, `request-budget-statistics-list.component.ts/html`, `request-other-expenses-form.component.ts/html`) — every new branch must be explicitly guarded by `typeUrl == 21`.
|
||||
- This repo has no meaningful unit-test culture for these Angular components (only CLI-boilerplate `.spec.ts` files exist project-wide; none for the files touched here). Per-task verification therefore uses `ng build --configuration=production` (from `/Users/nut.looknut/Project/rmutr/rmutr-web`) to catch compile/template errors, plus a manual browser check via `npm start` (dev server with HMR) — this matches how this project has actually been verified throughout this session (Playwright-driven manual checks), not fabricated unit tests.
|
||||
- Follow existing code conventions exactly: raw `Swal.fire(...)` in `request-other-expenses-form.component.ts` (it does NOT use `SweetalertService`), vs. `this.swSV.confirmSave()/.updateSuccess()/.errText()` in `request-budget-statistics.container.ts` (it DOES use `SweetalertService`). Don't introduce a new pattern in either file.
|
||||
- `BaseService.updateMany(data)` (PUT `{fullUrl}/s`) is the existing generic bulk-update method — reuse it, don't add a new API method.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Fix stale `status_id=1` filter blocking drafts from the list
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.ts:199`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: nothing new
|
||||
- Produces: `dataSourceBetweenYear$` now returns both draft (`status_id=0`) and sent (`status_id=1`) rows — later tasks (checkbox/status badge in Task 4) rely on both being present in the same list.
|
||||
|
||||
- [ ] **Step 1: Read the current line to confirm context**
|
||||
|
||||
The `getAll()` method currently has (line 199):
|
||||
```ts
|
||||
this.dataSourceBetweenYear$ = this.requestBudgetSV.queryString(`?is_between_year=true&status_id=1`)
|
||||
```
|
||||
This hardcodes `status_id=1`, meaning newly-created drafts (`status_id=0`, added by Task 2) would never show up in the list at all. The search path (`onsearch()`, same file, `typeUrl == 21` branch) already has no such filter — this line is inconsistent with it and must be fixed.
|
||||
|
||||
- [ ] **Step 2: Edit the line**
|
||||
|
||||
Change:
|
||||
```ts
|
||||
this.dataSourceBetweenYear$ = this.requestBudgetSV.queryString(`?is_between_year=true&status_id=1`)
|
||||
```
|
||||
to:
|
||||
```ts
|
||||
this.dataSourceBetweenYear$ = this.requestBudgetSV.queryString(`?is_between_year=true`)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Build to confirm no errors**
|
||||
|
||||
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && ng build --configuration=production`
|
||||
Expected: build succeeds with no new errors.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.ts
|
||||
git commit -m "fix: stop filtering out draft between-year requests from the list"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Set `status_id=0` on create + lock the form once sent (shared form component)
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-other-expenses/presenter/form/request-other-expenses-form/request-other-expenses-form.component.ts`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `this.typeUrl` (already set at line 391 from route data; between-year route resolves this to `21`, confirmed live via browser console), `this.state` (`'add'`/`'edit'`, set by `BaseFormV2`)
|
||||
- Produces: `isLocked: boolean` class field — Task 3 (template) reads this to hide the save button and show a locked banner.
|
||||
|
||||
- [ ] **Step 1: Add the `isLocked` field**
|
||||
|
||||
Find (around line 199):
|
||||
```ts
|
||||
is_plan_log: boolean = false
|
||||
```
|
||||
Change to:
|
||||
```ts
|
||||
is_plan_log: boolean = false
|
||||
isLocked: boolean = false
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Set `status_id = 0` on create, scoped to `typeUrl == 21`**
|
||||
|
||||
Find, at the top of `save()` (around line 4455-4458):
|
||||
```ts
|
||||
save() {
|
||||
console.log('ททท',this.request_budget_type_url)
|
||||
console.log(this.typeUrl)
|
||||
|
||||
if(this.typeUrl == 2001){
|
||||
```
|
||||
Change to:
|
||||
```ts
|
||||
save() {
|
||||
console.log('ททท',this.request_budget_type_url)
|
||||
console.log(this.typeUrl)
|
||||
|
||||
if (this.typeUrl == 21 && this.state === 'add') {
|
||||
this.form.get('status_id').setValue(0)
|
||||
}
|
||||
|
||||
if(this.typeUrl == 2001){
|
||||
```
|
||||
This only ever fires for the between-year route (`typeUrl == 21`) on first creation, leaving every other `typeUrl` branch (including the `typeUrl == 2001` review flow right below, which manages its own `status_id` independently) untouched. On `'edit'` saves of an existing draft, `status_id` is left as whatever was loaded (already `0`), so this is a no-op there too.
|
||||
|
||||
- [ ] **Step 3: Lock the form when an already-sent between-year record loads**
|
||||
|
||||
Find, in `setUpform(res)` (around line 975-990):
|
||||
```ts
|
||||
setUpform(res) {
|
||||
console.log(res)
|
||||
this.change_year(res.budget_year_uid)
|
||||
if (res.budget_strategy_uid) this.change_budget_strategy_edit(res.budget_strategy_uid)
|
||||
if (res.budget_strategy_target_uid) this.change_budget_strategy_target_edit(res.budget_strategy_target_uid)
|
||||
if (res.budget_reform_uid) this.change_budget_reform_edit(res.budget_reform_uid)
|
||||
if (res.budget_reform_target_uid) this.change_budget_reform_target_edit(res.budget_reform_target_uid)
|
||||
if (res.budget_economic_uid) this.change_budget_economic_edit(res.budget_economic_uid)
|
||||
if (res.budget_economic_target_uid) this.change_budget_economic_target_edit(res.budget_economic_target_uid)
|
||||
if (res.budget_strategy_faculty_uid) this.change_budget_strategy_faculty_strategic_edit(res.budget_strategy_faculty_uid)
|
||||
if (res.budget_unit_uid) this.change_budget_unit_edit(res.budget_unit_uid)
|
||||
if (res.service_insurance_level_uid) this.on_search_Insurance_fac(res.service_insurance_level_uid)
|
||||
if (res.service_insurance_faculty_uid) this.on_search_Insurance(res.service_insurance_faculty_uid)
|
||||
this.click_faculty_edit(res)
|
||||
this.form.patchValue(res)
|
||||
```
|
||||
Change the last line (`this.form.patchValue(res)`) to add the lock check right after it:
|
||||
```ts
|
||||
this.click_faculty_edit(res)
|
||||
this.form.patchValue(res)
|
||||
|
||||
if (this.typeUrl == 21) {
|
||||
this.isLocked = res.status_id !== 0
|
||||
if (this.isLocked) {
|
||||
this.form.disable()
|
||||
}
|
||||
}
|
||||
```
|
||||
`setUpform` is shared by every project type, so this must stay guarded by `typeUrl == 21` — for any other type, `isLocked` stays `false` and `form.disable()` never runs, preserving current behavior exactly.
|
||||
|
||||
- [ ] **Step 4: Build to confirm no errors**
|
||||
|
||||
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && ng build --configuration=production`
|
||||
Expected: build succeeds with no new errors.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add rmutr-web/src/app/feature/budget-request/request/request-other-expenses/presenter/form/request-other-expenses-form/request-other-expenses-form.component.ts
|
||||
git commit -m "feat: save between-year requests as draft (status_id=0) and lock the form once sent"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Split the save button into "บันทึก" / "บันทึกฉบับร่าง" and show a locked banner
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-other-expenses/presenter/form/request-other-expenses-form/request-other-expenses-form.component.html:876-877`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `typeUrl`, `isLocked` (from Task 2), `save()` (unchanged method — same call for both buttons)
|
||||
- Produces: nothing new for later tasks (this is the leaf-most template change)
|
||||
|
||||
- [ ] **Step 1: Edit the button block**
|
||||
|
||||
Find (lines 876-877):
|
||||
```html
|
||||
<button mat-raised-button cdkFocusInitial (click)="close_request_budget()">ยกเลิก</button>
|
||||
<button mat-raised-button class="bg-bpi-primary-color" (click)="save()" *ngIf="typeUrl != 2001" color="primary">บันทึก</button>
|
||||
```
|
||||
Change to:
|
||||
```html
|
||||
<span *ngIf="typeUrl == 21 && isLocked" style="color:#ef4444;font-weight:600;margin-right:12px;">ส่งกองนโยบายและแผนแล้ว — ไม่สามารถแก้ไขได้</span>
|
||||
<button mat-raised-button cdkFocusInitial (click)="close_request_budget()">ยกเลิก</button>
|
||||
<button mat-raised-button class="bg-bpi-primary-color" (click)="save()" *ngIf="typeUrl != 2001 && typeUrl != 21" color="primary">บันทึก</button>
|
||||
<button mat-raised-button class="bg-bpi-primary-color" (click)="save()" *ngIf="typeUrl == 21 && !isLocked" color="primary">บันทึกฉบับร่าง</button>
|
||||
```
|
||||
The original button's `*ngIf` now excludes `typeUrl == 21` (so between-year never shows the generic "บันทึก" label), and a new button shows "บันทึกฉบับร่าง" only for between-year and only while unlocked (draft). When locked, neither save button renders — only the banner and "ยกเลิก".
|
||||
|
||||
- [ ] **Step 2: Build to confirm no errors**
|
||||
|
||||
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && ng build --configuration=production`
|
||||
Expected: build succeeds with no new errors.
|
||||
|
||||
- [ ] **Step 3: Manual verification**
|
||||
|
||||
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && npm start`, then in a browser:
|
||||
- Open the "คำขอระหว่างปี" menu → "เพิ่มคำขอระหว่างปี" (add). Confirm the button reads "บันทึกฉบับร่าง" (not "บันทึก").
|
||||
- Save it, then edit that same record. Confirm "บันทึกฉบับร่าง" still shows (not locked yet — Task 5 is what flips it to sent).
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add rmutr-web/src/app/feature/budget-request/request/request-other-expenses/presenter/form/request-other-expenses-form/request-other-expenses-form.component.html
|
||||
git commit -m "feat: label between-year save button as draft and show locked banner when sent"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Checkbox column, status badge, and locked delete icon in the list
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list-between-year/list-between-year.component.ts`
|
||||
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list-between-year/list-between-year.component.html`
|
||||
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list-between-year/list-between-year.component.scss`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `dataSource` input rows now include both `status_id: 0` (draft) and `status_id: 1` (sent) rows (Task 1), each row has `is_ok`/`is_modify`/`status_id` fields already returned by the API.
|
||||
- Produces: `@Output() onsend = new EventEmitter<any[]>()` — emits the array of selected draft rows when "ส่งกองนโยบายและแผน" is clicked. Task 5 wires this output up through the presenter list and container.
|
||||
|
||||
- [ ] **Step 1: Add `SelectionModel` and the send output to the component class**
|
||||
|
||||
Find the full current file:
|
||||
```ts
|
||||
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Input, Output, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { PageEvent } from '@angular/material/paginator';
|
||||
import { Router } from '@angular/router';
|
||||
import { BaseList } from 'src/app/core/base/base-list';
|
||||
import { RequestPlanUidService } from 'src/app/core/service/request-budget/request-plan-uid.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-between-year',
|
||||
templateUrl: './list-between-year.component.html',
|
||||
styleUrls: ['./list-between-year.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ListBetweenYearComponent extends BaseList implements OnInit, OnChanges {
|
||||
|
||||
@Input() dataSource: any = [];
|
||||
@Output() onchange = new EventEmitter();
|
||||
@Output() onedit = new EventEmitter();
|
||||
@Output() ondelete = new EventEmitter();
|
||||
@Output() onexcel = new EventEmitter();
|
||||
data
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private RequestPlanUidSV: RequestPlanUidService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if ('dataSource' in changes && changes?.dataSource?.currentValue) {
|
||||
this.data = this.dataSource;
|
||||
this.dataSource = this.updateMatTable(this.dataSource ? this.dataSource : []);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
add() {
|
||||
this.RequestPlanUidSV.is_between_year$.next(true);
|
||||
this.RequestPlanUidSV.request_budget_type$.next('ae9afcdb-a8d6-4be6-b440-931e7a27fa73');
|
||||
this.router.navigate(['app/request-budget-between-year/add-between-year/add']);
|
||||
}
|
||||
|
||||
edit(val) {
|
||||
this.router.navigate(['app/request-budget-between-year/add-between-year/edit', val.request_budget_uid]);
|
||||
}
|
||||
|
||||
delete(val) {
|
||||
this.ondelete.emit(val);
|
||||
}
|
||||
|
||||
change(event: PageEvent) {
|
||||
const page: number = event.pageIndex + 1;
|
||||
const table: any = { page, size: event.pageSize };
|
||||
this.onchange.emit(table);
|
||||
}
|
||||
|
||||
excel(val) {
|
||||
this.onexcel.emit(val);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Replace it entirely with:
|
||||
```ts
|
||||
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Input, Output, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { PageEvent } from '@angular/material/paginator';
|
||||
import { Router } from '@angular/router';
|
||||
import { BaseList } from 'src/app/core/base/base-list';
|
||||
import { RequestPlanUidService } from 'src/app/core/service/request-budget/request-plan-uid.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-between-year',
|
||||
templateUrl: './list-between-year.component.html',
|
||||
styleUrls: ['./list-between-year.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ListBetweenYearComponent extends BaseList implements OnInit, OnChanges {
|
||||
|
||||
@Input() dataSource: any = [];
|
||||
@Output() onchange = new EventEmitter();
|
||||
@Output() onedit = new EventEmitter();
|
||||
@Output() ondelete = new EventEmitter();
|
||||
@Output() onexcel = new EventEmitter();
|
||||
@Output() onsend = new EventEmitter();
|
||||
selection = new SelectionModel<any>(true, []);
|
||||
data
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private RequestPlanUidSV: RequestPlanUidService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if ('dataSource' in changes && changes?.dataSource?.currentValue) {
|
||||
this.data = this.dataSource;
|
||||
this.dataSource = this.updateMatTable(this.dataSource ? this.dataSource : []);
|
||||
this.selection.clear();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
add() {
|
||||
this.RequestPlanUidSV.is_between_year$.next(true);
|
||||
this.RequestPlanUidSV.request_budget_type$.next('ae9afcdb-a8d6-4be6-b440-931e7a27fa73');
|
||||
this.router.navigate(['app/request-budget-between-year/add-between-year/add']);
|
||||
}
|
||||
|
||||
edit(val) {
|
||||
this.router.navigate(['app/request-budget-between-year/add-between-year/edit', val.request_budget_uid]);
|
||||
}
|
||||
|
||||
delete(val) {
|
||||
this.ondelete.emit(val);
|
||||
}
|
||||
|
||||
change(event: PageEvent) {
|
||||
const page: number = event.pageIndex + 1;
|
||||
const table: any = { page, size: event.pageSize };
|
||||
this.onchange.emit(table);
|
||||
}
|
||||
|
||||
excel(val) {
|
||||
this.onexcel.emit(val);
|
||||
}
|
||||
|
||||
private draftRows(): any[] {
|
||||
return (this.data || []).filter((x: any) => x.status_id === 0);
|
||||
}
|
||||
|
||||
isAllSelected(): boolean {
|
||||
const draft = this.draftRows();
|
||||
return draft.length > 0 && this.selection.selected.length === draft.length;
|
||||
}
|
||||
|
||||
masterToggle(): void {
|
||||
if (this.isAllSelected()) {
|
||||
this.selection.clear();
|
||||
return;
|
||||
}
|
||||
this.selection.select(...this.draftRows());
|
||||
}
|
||||
|
||||
checkboxLabel(row?: any): string {
|
||||
if (!row) {
|
||||
return `${this.isAllSelected() ? 'deselect' : 'select'} all`;
|
||||
}
|
||||
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row`;
|
||||
}
|
||||
|
||||
sendToStrategic(): void {
|
||||
this.onsend.emit(this.selection.selected);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
Selection is cleared on every `dataSource` change (Angular gives new row object instances on every refresh, so stale selected references would otherwise linger); only draft (`status_id === 0`) rows are selectable, matching the same "already-sent rows can't be selected" pattern used by `list6`/`list24` elsewhere in this codebase.
|
||||
|
||||
- [ ] **Step 2: Add the checkbox column, fix the status column, lock the delete icon, add the send button**
|
||||
|
||||
Find the full current file:
|
||||
```html
|
||||
<div class="list-card">
|
||||
<div class="toolbar">
|
||||
<button mat-raised-button color="primary" class="btn-add" (click)="add()">
|
||||
<mat-icon>add</mat-icon>
|
||||
เพิ่มคำขอระหว่างปี
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table mat-table [dataSource]="dataSource" class="list-table">
|
||||
<tr mat-header-row
|
||||
*matHeaderRowDef="['action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','total_budget']">
|
||||
</tr>
|
||||
<tr mat-row
|
||||
*matRowDef="let myRowData; columns: ['action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','total_budget']">
|
||||
</tr>
|
||||
<tr mat-footer-row
|
||||
*matFooterRowDef="['action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','total_budget']">
|
||||
</tr>
|
||||
|
||||
<!-- Action -->
|
||||
<ng-container matColumnDef="action">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-action"></th>
|
||||
<td mat-cell *matCellDef="let x" class="col-action">
|
||||
<span class="material-icons action-icon" (click)="edit(x)" title="แก้ไข">edit</span>
|
||||
<span class="material-icons action-icon action-icon--delete" (click)="delete(x)" title="ลบ">delete</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- ปีงบประมาณ -->
|
||||
<ng-container matColumnDef="budget_year_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-year">ปีงบฯ</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-year">
|
||||
<span class="badge-year">{{x.budget_year_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- พื้นที่ -->
|
||||
<ng-container matColumnDef="budget_location_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-location">พื้นที่</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-location">
|
||||
<span class="text-clip">{{x.budget_location_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- หน่วยงาน/คณะ -->
|
||||
<ng-container matColumnDef="faculty_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-faculty">หน่วยงาน/คณะ</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-faculty">
|
||||
<span class="text-clip">{{x.responsible_faculty_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- ชื่อโครงการ -->
|
||||
<ng-container matColumnDef="project_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-project">ชื่อโครงการ</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-project">
|
||||
<span class="text-clip project-name">{{x.project_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- ผลผลิต -->
|
||||
<ng-container matColumnDef="budget_project_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-project-type">ผลผลิต</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-project-type">
|
||||
<span class="text-clip">{{x.budget_project_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- ด้าน -->
|
||||
<ng-container matColumnDef="budget_topic_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-topic">ด้าน</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-topic">
|
||||
<span class="text-clip">{{x.budget_topic_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- ประเภทคำขอ -->
|
||||
<ng-container matColumnDef="request_type">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-request-type">ประเภทคำขอ</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-request-type">
|
||||
<span class="badge badge-between">คำขอระหว่างปี</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- สถานะ -->
|
||||
<!-- <ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-status">สถานะ</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-status">
|
||||
<span *ngIf="x.is_ok == true" class="badge badge-success">ตรวจสอบแล้ว</span>
|
||||
<span *ngIf="x.is_modify == true" class="badge badge-warning">ส่งแก้ไข</span>
|
||||
<span *ngIf="!x.is_ok && !x.is_modify && x.status_id == 1" class="badge badge-pending">รอตรวจสอบ</span>
|
||||
<span *ngIf="x.status_id == 0" class="badge badge-draft">ร่าง</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef class="footer-label">จำนวนเงินรวม</td>
|
||||
</ng-container> -->
|
||||
|
||||
<!-- วงเงิน -->
|
||||
<ng-container matColumnDef="total_budget">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-amount">วงเงิน (บาท)</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-amount">
|
||||
{{x.total_budget | number:'1.2-2'}}
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef class="col-amount footer-total">
|
||||
{{data | total:'total_budget' | number:'1.2-2'}}
|
||||
</td>
|
||||
</ng-container>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<mat-paginator [pageSizeOptions]="[20, 50, 100]" showFirstLastButtons (page)="change($event)"></mat-paginator>
|
||||
</div>
|
||||
```
|
||||
|
||||
Replace it entirely with:
|
||||
```html
|
||||
<div class="list-card">
|
||||
<div class="toolbar">
|
||||
<button mat-raised-button color="primary" class="btn-add" (click)="add()">
|
||||
<mat-icon>add</mat-icon>
|
||||
เพิ่มคำขอระหว่างปี
|
||||
</button>
|
||||
<button mat-raised-button color="accent" class="btn-send" [disabled]="selection.selected.length === 0" (click)="sendToStrategic()">
|
||||
<mat-icon>send</mat-icon>
|
||||
ส่งกองนโยบายและแผน
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table mat-table [dataSource]="dataSource" class="list-table">
|
||||
<tr mat-header-row
|
||||
*matHeaderRowDef="['checkbox','action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','status','total_budget']">
|
||||
</tr>
|
||||
<tr mat-row
|
||||
*matRowDef="let myRowData; columns: ['checkbox','action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','status','total_budget']">
|
||||
</tr>
|
||||
<tr mat-footer-row
|
||||
*matFooterRowDef="['checkbox','action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','status','total_budget']">
|
||||
</tr>
|
||||
|
||||
<!-- เลือก -->
|
||||
<ng-container matColumnDef="checkbox">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-checkbox">
|
||||
<mat-checkbox
|
||||
(change)="$event ? masterToggle() : null"
|
||||
[checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-checkbox">
|
||||
<mat-checkbox
|
||||
(click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(x) : null"
|
||||
[checked]="selection.isSelected(x)"
|
||||
[aria-label]="checkboxLabel(x)"
|
||||
[disabled]="x.status_id !== 0">
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Action -->
|
||||
<ng-container matColumnDef="action">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-action"></th>
|
||||
<td mat-cell *matCellDef="let x" class="col-action">
|
||||
<span class="material-icons action-icon" (click)="edit(x)" title="แก้ไข">edit</span>
|
||||
<span *ngIf="x.status_id === 0" class="material-icons action-icon action-icon--delete" (click)="delete(x)" title="ลบ">delete</span>
|
||||
<span *ngIf="x.status_id !== 0" class="material-icons action-icon action-icon--disabled" title="ส่งไปงานแผนแล้ว ลบไม่ได้">delete</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- ปีงบประมาณ -->
|
||||
<ng-container matColumnDef="budget_year_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-year">ปีงบฯ</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-year">
|
||||
<span class="badge-year">{{x.budget_year_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- พื้นที่ -->
|
||||
<ng-container matColumnDef="budget_location_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-location">พื้นที่</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-location">
|
||||
<span class="text-clip">{{x.budget_location_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- หน่วยงาน/คณะ -->
|
||||
<ng-container matColumnDef="faculty_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-faculty">หน่วยงาน/คณะ</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-faculty">
|
||||
<span class="text-clip">{{x.responsible_faculty_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- ชื่อโครงการ -->
|
||||
<ng-container matColumnDef="project_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-project">ชื่อโครงการ</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-project">
|
||||
<span class="text-clip project-name">{{x.project_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- ผลผลิต -->
|
||||
<ng-container matColumnDef="budget_project_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-project-type">ผลผลิต</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-project-type">
|
||||
<span class="text-clip">{{x.budget_project_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- ด้าน -->
|
||||
<ng-container matColumnDef="budget_topic_name_th">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-topic">ด้าน</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-topic">
|
||||
<span class="text-clip">{{x.budget_topic_name_th}}</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- ประเภทคำขอ -->
|
||||
<ng-container matColumnDef="request_type">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-request-type">ประเภทคำขอ</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-request-type">
|
||||
<span class="badge badge-between">คำขอระหว่างปี</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- สถานะ -->
|
||||
<ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-status">สถานะ</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-status">
|
||||
<span *ngIf="x.is_ok == true" class="badge badge-success">ตรวจสอบแล้ว</span>
|
||||
<span *ngIf="x.is_modify == true" class="badge badge-warning">ส่งแก้ไข</span>
|
||||
<span *ngIf="!x.is_ok && !x.is_modify && x.status_id == 1" class="badge badge-pending">ส่งงานแผน</span>
|
||||
<span *ngIf="x.status_id == 0" class="badge badge-draft">ร่าง</span>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- วงเงิน -->
|
||||
<ng-container matColumnDef="total_budget">
|
||||
<th mat-header-cell *matHeaderCellDef class="col-amount">วงเงิน (บาท)</th>
|
||||
<td mat-cell *matCellDef="let x" class="col-amount">
|
||||
{{x.total_budget | number:'1.2-2'}}
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef class="col-amount footer-total">
|
||||
{{data | total:'total_budget' | number:'1.2-2'}}
|
||||
</td>
|
||||
</ng-container>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<mat-paginator [pageSizeOptions]="[20, 50, 100]" showFirstLastButtons (page)="change($event)"></mat-paginator>
|
||||
</div>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add the checkbox column width and locked-icon styles**
|
||||
|
||||
Find, in the `.scss` file:
|
||||
```scss
|
||||
.action-icon {
|
||||
font-size: 17px !important;
|
||||
width: 17px !important;
|
||||
height: 17px !important;
|
||||
color: #f8a300;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin: 0 4px;
|
||||
transition: opacity 0.15s;
|
||||
vertical-align: middle;
|
||||
|
||||
&:hover { opacity: 0.7; }
|
||||
|
||||
&--delete {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
```
|
||||
Change to:
|
||||
```scss
|
||||
.action-icon {
|
||||
font-size: 17px !important;
|
||||
width: 17px !important;
|
||||
height: 17px !important;
|
||||
color: #f8a300;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin: 0 4px;
|
||||
transition: opacity 0.15s;
|
||||
vertical-align: middle;
|
||||
|
||||
&:hover { opacity: 0.7; }
|
||||
|
||||
&--delete {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: #d1d5db;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.col-checkbox {
|
||||
width: 44px !important;
|
||||
min-width: 44px !important;
|
||||
max-width: 44px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
```
|
||||
Also find, near the top of the file:
|
||||
```scss
|
||||
.btn-add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
```
|
||||
Change to:
|
||||
```scss
|
||||
.btn-add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-send {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Build to confirm no errors**
|
||||
|
||||
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && ng build --configuration=production`
|
||||
Expected: build succeeds with no new errors.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list-between-year/
|
||||
git commit -m "feat: add checkbox selection, status badge, and locked delete icon to between-year list"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Wire the send action end-to-end and implement the bulk status update
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/request-budget-statistics-list.component.ts`
|
||||
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/request-budget-statistics-list.component.html`
|
||||
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.html`
|
||||
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.ts`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `list-between-year`'s `@Output() onsend` (Task 4) — emits `any[]` of selected draft rows.
|
||||
- Produces: nothing further downstream — this is the final task; the sent rows automatically appear in the existing "ตรวจสอบคำเสนอขอโครงการ" menu (`check-project-proposal`) because that menu already queries `status_id=1` on the same table (`request_budget_and_strategic`), unchanged by this plan.
|
||||
|
||||
- [ ] **Step 1: Add the output + forwarding method to the presenter list component**
|
||||
|
||||
Find, in `request-budget-statistics-list.component.ts`:
|
||||
```ts
|
||||
@Output() onsave22 = new EventEmitter();
|
||||
@Output() onsendPlan22 = new EventEmitter();
|
||||
```
|
||||
Change to:
|
||||
```ts
|
||||
@Output() onsave22 = new EventEmitter();
|
||||
@Output() onsendPlan22 = new EventEmitter();
|
||||
@Output() onsendBetweenYear = new EventEmitter();
|
||||
```
|
||||
|
||||
Find:
|
||||
```ts
|
||||
save22(val){
|
||||
this.onsave22.emit(val)
|
||||
}
|
||||
|
||||
sendPlan22(val){
|
||||
this.onsendPlan22.emit(val)
|
||||
}
|
||||
```
|
||||
Change to:
|
||||
```ts
|
||||
save22(val){
|
||||
this.onsave22.emit(val)
|
||||
}
|
||||
|
||||
sendPlan22(val){
|
||||
this.onsendPlan22.emit(val)
|
||||
}
|
||||
|
||||
sendBetweenYear(val){
|
||||
this.onsendBetweenYear.emit(val)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Wire the child `(onsend)` into the new method in the presenter list's template**
|
||||
|
||||
Find, in `request-budget-statistics-list.component.html`:
|
||||
```html
|
||||
<ng-container *ngIf="typeUrl == 21">
|
||||
<app-list-between-year
|
||||
[dataSource]="dataSourceBetweenYear"
|
||||
(ondelete)="onDelete($event)"
|
||||
(onchange)="change($event)"
|
||||
(onexcel)="excel($event)"
|
||||
></app-list-between-year>
|
||||
</ng-container>
|
||||
```
|
||||
Change to:
|
||||
```html
|
||||
<ng-container *ngIf="typeUrl == 21">
|
||||
<app-list-between-year
|
||||
[dataSource]="dataSourceBetweenYear"
|
||||
(ondelete)="onDelete($event)"
|
||||
(onchange)="change($event)"
|
||||
(onexcel)="excel($event)"
|
||||
(onsend)="sendBetweenYear($event)"
|
||||
></app-list-between-year>
|
||||
</ng-container>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Wire the presenter list's `(onsendBetweenYear)` into the container's template**
|
||||
|
||||
Find, in `request-budget-statistics.container.html`:
|
||||
```html
|
||||
(onedit)="onedit($event)"
|
||||
(onedit_request_budget)="onedit_request_budget($event)"
|
||||
(ondelete)="ondelete($event)"
|
||||
(onchange)="onchange($event)"
|
||||
```
|
||||
Change to:
|
||||
```html
|
||||
(onedit)="onedit($event)"
|
||||
(onedit_request_budget)="onedit_request_budget($event)"
|
||||
(ondelete)="ondelete($event)"
|
||||
(onchange)="onchange($event)"
|
||||
(onsendBetweenYear)="sendBetweenYearToStrategic($event)"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Implement the bulk-send handler in the container**
|
||||
|
||||
Find, in `request-budget-statistics.container.ts`, the `ondelete(value)` method:
|
||||
```ts
|
||||
ondelete(value){
|
||||
// console.log(value)
|
||||
this.swSV.confirm().pipe(
|
||||
filter(x => x.isConfirmed),
|
||||
concatMap(() => {
|
||||
const delete$ = (this.typeUrl == 7 || this.typeUrl == 22 || this.typeUrl == 9 || this.typeUrl == 11)
|
||||
? this.requestBudgetAnalysis5SV.deleteData(value.request_budget_analysis_5_uid)
|
||||
: this.requestBudgetSV.deleteData(value.request_budget_uid)
|
||||
return delete$.pipe(
|
||||
tap(() => this.swSV.removeSuccess()),
|
||||
tap(() => this.ngOnInit()),
|
||||
tap(() => this.cdRef.detectChanges())
|
||||
)
|
||||
})
|
||||
).subscribe()
|
||||
}
|
||||
```
|
||||
Add a new method directly after it:
|
||||
```ts
|
||||
ondelete(value){
|
||||
// console.log(value)
|
||||
this.swSV.confirm().pipe(
|
||||
filter(x => x.isConfirmed),
|
||||
concatMap(() => {
|
||||
const delete$ = (this.typeUrl == 7 || this.typeUrl == 22 || this.typeUrl == 9 || this.typeUrl == 11)
|
||||
? this.requestBudgetAnalysis5SV.deleteData(value.request_budget_analysis_5_uid)
|
||||
: this.requestBudgetSV.deleteData(value.request_budget_uid)
|
||||
return delete$.pipe(
|
||||
tap(() => this.swSV.removeSuccess()),
|
||||
tap(() => this.ngOnInit()),
|
||||
tap(() => this.cdRef.detectChanges())
|
||||
)
|
||||
})
|
||||
).subscribe()
|
||||
}
|
||||
|
||||
sendBetweenYearToStrategic(value: any[]){
|
||||
const selected = (value || []).filter(v => v?.status_id === 0)
|
||||
if (selected.length === 0) {
|
||||
this.swSV.errText('กรุณาเลือกรายการที่ต้องการส่ง')
|
||||
return
|
||||
}
|
||||
this.swSV.confirmSave('ต้องการส่งรายการที่เลือกไปกองนโยบายและแผนหรือไม่?').pipe(
|
||||
filter(x => x.isConfirmed),
|
||||
concatMap(() => {
|
||||
const putData = selected.map(v => ({ ...v, status_id: 1, is_examine: true }))
|
||||
return this.requestBudgetSV.updateMany(putData).pipe(
|
||||
catchError(err => {
|
||||
this.swSV.errText(err?.error?.description || 'ส่งไม่สำเร็จ')
|
||||
return throwError(err)
|
||||
})
|
||||
)
|
||||
}),
|
||||
tap(() => this.swSV.updateSuccess()),
|
||||
tap(() => this.getAll()),
|
||||
tap(() => this.cdRef.detectChanges())
|
||||
).subscribe()
|
||||
}
|
||||
```
|
||||
`filter`, `concatMap`, `tap`, `catchError`, and `throwError` are already imported at the top of this file (used throughout the other handlers above), so no new imports are needed.
|
||||
|
||||
- [ ] **Step 5: Build to confirm no errors**
|
||||
|
||||
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && ng build --configuration=production`
|
||||
Expected: build succeeds with no new errors.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/request-budget-statistics-list.component.ts rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/request-budget-statistics-list.component.html rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.html rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.ts
|
||||
git commit -m "feat: wire checkbox send action to bulk-update between-year requests to sent status"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: End-to-end manual verification
|
||||
|
||||
**Files:** none (verification only)
|
||||
|
||||
**Interfaces:** none
|
||||
|
||||
- [ ] **Step 1: Start the dev server**
|
||||
|
||||
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && npm start`
|
||||
|
||||
- [ ] **Step 2: Draft creation**
|
||||
|
||||
In the browser, open "แผนงานและโครงการ > คำขอระหว่างปี" → "เพิ่มคำขอระหว่างปี" → fill required fields → click "บันทึกฉบับร่าง".
|
||||
Expected: record appears in the list with badge "ร่าง", checkbox enabled, both "แก้ไข" and "ลบ" icons active.
|
||||
|
||||
- [ ] **Step 3: Edit a draft**
|
||||
|
||||
Click "แก้ไข" on the drafted row.
|
||||
Expected: form loads fully editable, button still reads "บันทึกฉบับร่าง", no locked banner.
|
||||
|
||||
- [ ] **Step 4: Send to planning**
|
||||
|
||||
Back in the list, tick the checkbox on 1-2 draft rows → click "ส่งกองนโยบายและแผน" → confirm the dialog.
|
||||
Expected: success toast, rows now show badge "ส่งงานแผน", checkbox greyed out and unchecked, "ลบ" icon greyed out/disabled, "แก้ไข" icon still clickable.
|
||||
|
||||
- [ ] **Step 5: View a sent (locked) record**
|
||||
|
||||
Click "แก้ไข" on a now-sent row.
|
||||
Expected: form loads with all fields disabled, red banner "ส่งกองนโยบายและแผนแล้ว — ไม่สามารถแก้ไขได้" visible, no save button, only "ยกเลิก".
|
||||
|
||||
- [ ] **Step 6: Confirm it reached the planning menu**
|
||||
|
||||
Open "แผนงานและโครงการ > ตรวจสอบคำเสนอขอโครงการ" → tab "จัดการคำเสนอขอ".
|
||||
Expected: the just-sent project(s) appear there (this is existing, unmodified behavior — confirms `status_id=1` propagation works end-to-end).
|
||||
|
||||
- [ ] **Step 7: Regression check on an unrelated menu**
|
||||
|
||||
Open "แผนงานและโครงการ > ต้นฉบับเสนอโครงการ ง.5" (typeUrl 1, uses the same shared form component) → add or edit a record.
|
||||
Expected: behaves exactly as before — plain "บันทึก" button (not "บันทึกฉบับร่าง"), no locked banner, saves normally. This confirms the `typeUrl == 21` guards in Task 2/3 didn't leak into other project types.
|
||||
Reference in New Issue
Block a user