Compare commits
2 Commits
30d51c89bb
...
b04a11a62f
| Author | SHA1 | Date | |
|---|---|---|---|
| b04a11a62f | |||
| b079b27343 |
@ -974,9 +974,13 @@ function BudgetAdjustSection({ ev, readOnly }: { ev: PeDetailBundle; readOnly: b
|
||||
const isDrafter = currentUser?.id != null && ev.drafterUserId === currentUser.id
|
||||
const isDrafterPhase = ev.phase === PurchaseEvaluationPhase.DangSoanThao
|
||||
|| ev.phase === PurchaseEvaluationPhase.TraLai
|
||||
// Approver currentLevel match — phase ChoDuyet + actor in current approval level
|
||||
// F4 Approver scope (Mig 30): phase ChoDuyet + actor in currentApproval.approvers
|
||||
// + currentLevel có flag AllowApproverEditBudget=true (admin Designer tick per slot).
|
||||
const actorInCurrentLevel = ev.currentApproval?.approvers?.some(a => a.userId === currentUser?.id) ?? false
|
||||
const isApproverChoDuyet = ev.phase === PurchaseEvaluationPhase.ChoDuyet && actorInCurrentLevel
|
||||
const approverEditBudgetAllowed = ev.currentLevelOptions?.allowApproverEditBudget ?? false
|
||||
const isApproverChoDuyet = ev.phase === PurchaseEvaluationPhase.ChoDuyet
|
||||
&& actorInCurrentLevel
|
||||
&& approverEditBudgetAllowed
|
||||
|
||||
const canAdjust = !readOnly && (isAdmin || (isDrafter && isDrafterPhase) || isApproverChoDuyet)
|
||||
|
||||
|
||||
@ -42,11 +42,13 @@ type LevelDto = {
|
||||
approverUserName: string | null
|
||||
approverEmail: string | null
|
||||
// Mig 29 (S21 t5) — 5 Allow* options per slot Approver
|
||||
// Mig 30 (S22+5) — +AllowApproverEditBudget cho Section ngân sách
|
||||
allowReturnOneLevel: boolean
|
||||
allowReturnOneStep: boolean
|
||||
allowReturnToAssignee: boolean
|
||||
allowReturnToDrafter: boolean
|
||||
allowApproverEditDetails: boolean
|
||||
allowApproverEditBudget: boolean
|
||||
}
|
||||
type StepDto = {
|
||||
id: string
|
||||
@ -86,11 +88,13 @@ type EditLevelEntry = {
|
||||
approverUserId: string
|
||||
// Mig 29 (S21 t5) — 5 Allow* per slot (default backward compat S17: chỉ
|
||||
// AllowReturnToDrafter=true, 4 còn lại false).
|
||||
// Mig 30 (S22+5) — +AllowApproverEditBudget cho Section ngân sách (default false).
|
||||
allowReturnOneLevel: boolean
|
||||
allowReturnOneStep: boolean
|
||||
allowReturnToAssignee: boolean
|
||||
allowReturnToDrafter: boolean
|
||||
allowApproverEditDetails: boolean
|
||||
allowApproverEditBudget: boolean
|
||||
}
|
||||
type EditStep = { name: string; departmentId: string | null; levelEntries: EditLevelEntry[] }
|
||||
|
||||
@ -137,6 +141,7 @@ function copyFromDefinition(d: DefinitionDto): EditStep[] {
|
||||
allowReturnToAssignee: l.allowReturnToAssignee ?? false,
|
||||
allowReturnToDrafter: l.allowReturnToDrafter ?? true,
|
||||
allowApproverEditDetails: l.allowApproverEditDetails ?? false,
|
||||
allowApproverEditBudget: l.allowApproverEditBudget ?? false,
|
||||
})),
|
||||
}))
|
||||
}
|
||||
@ -152,6 +157,7 @@ function makeDefaultLevelEntry(order: LevelOrder, approverUserId: string): EditL
|
||||
allowReturnToAssignee: false,
|
||||
allowReturnToDrafter: true,
|
||||
allowApproverEditDetails: false,
|
||||
allowApproverEditBudget: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -553,6 +559,7 @@ function Designer({
|
||||
allowReturnToAssignee: e.allowReturnToAssignee,
|
||||
allowReturnToDrafter: e.allowReturnToDrafter,
|
||||
allowApproverEditDetails: e.allowApproverEditDetails,
|
||||
allowApproverEditBudget: e.allowApproverEditBudget,
|
||||
})),
|
||||
})),
|
||||
})
|
||||
@ -911,6 +918,15 @@ function Designer({
|
||||
/>
|
||||
<span>Cho phép chỉnh sửa Section 2 (Hạng mục/NCC/Báo giá) lúc đang duyệt</span>
|
||||
</label>
|
||||
<label className="col-span-2 flex items-center gap-1 text-[11px] text-slate-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-3 w-3"
|
||||
checked={entry.allowApproverEditBudget}
|
||||
onChange={e => updateField('allowApproverEditBudget', e.target.checked)}
|
||||
/>
|
||||
<span>Cho phép chỉnh sửa Section ngân sách lúc đang duyệt</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -355,6 +355,7 @@ export type ApprovalWorkflowOptions = {
|
||||
allowReturnToAssignee: boolean
|
||||
allowReturnToDrafter: boolean
|
||||
allowApproverEditDetails: boolean
|
||||
allowApproverEditBudget: boolean // Mig 30 (S22+5) — F4 Section ngân sách
|
||||
}
|
||||
|
||||
// Mig 28 (S21 t4) — F1 mode Trả lại payload gửi BE
|
||||
|
||||
@ -981,8 +981,12 @@ function BudgetAdjustSection({ ev, readOnly }: { ev: PeDetailBundle; readOnly: b
|
||||
const isDrafter = currentUser?.id != null && ev.drafterUserId === currentUser.id
|
||||
const isDrafterPhase = ev.phase === PurchaseEvaluationPhase.DangSoanThao
|
||||
|| ev.phase === PurchaseEvaluationPhase.TraLai
|
||||
// F4 Approver scope (Mig 30): ChoDuyet + actor in approvers + flag tick.
|
||||
const actorInCurrentLevel = ev.currentApproval?.approvers?.some(a => a.userId === currentUser?.id) ?? false
|
||||
const isApproverChoDuyet = ev.phase === PurchaseEvaluationPhase.ChoDuyet && actorInCurrentLevel
|
||||
const approverEditBudgetAllowed = ev.currentLevelOptions?.allowApproverEditBudget ?? false
|
||||
const isApproverChoDuyet = ev.phase === PurchaseEvaluationPhase.ChoDuyet
|
||||
&& actorInCurrentLevel
|
||||
&& approverEditBudgetAllowed
|
||||
|
||||
const canAdjust = !readOnly && (isAdmin || (isDrafter && isDrafterPhase) || isApproverChoDuyet)
|
||||
|
||||
|
||||
@ -353,6 +353,7 @@ export type ApprovalWorkflowOptions = {
|
||||
allowReturnToAssignee: boolean
|
||||
allowReturnToDrafter: boolean
|
||||
allowApproverEditDetails: boolean
|
||||
allowApproverEditBudget: boolean // Mig 30 (S22+5) — F4 Section ngân sách
|
||||
}
|
||||
|
||||
// Mig 28 (S21 t4) — F1 mode Trả lại payload gửi BE
|
||||
|
||||
@ -28,12 +28,14 @@ public record AwLevelDto(
|
||||
string? ApproverUserName,
|
||||
string? ApproverEmail,
|
||||
// Mig 29 (S21 t5) — 5 advanced options per slot Approver (F1 mode Trả lại
|
||||
// + F3 Edit Section 2). Mỗi NV trong workflow có quyền riêng.
|
||||
// + F3 Edit Section 2). Mig 30 (S22+5) — F4 +AllowApproverEditBudget.
|
||||
// Mỗi NV trong workflow có quyền riêng.
|
||||
bool AllowReturnOneLevel,
|
||||
bool AllowReturnOneStep,
|
||||
bool AllowReturnToAssignee,
|
||||
bool AllowReturnToDrafter,
|
||||
bool AllowApproverEditDetails);
|
||||
bool AllowApproverEditDetails,
|
||||
bool AllowApproverEditBudget);
|
||||
|
||||
public record AwStepDto(
|
||||
Guid Id,
|
||||
@ -152,7 +154,7 @@ public class GetAwAdminOverviewQueryHandler(
|
||||
// Mig 29 (S21 t5) — 5 Allow* flag per slot Level
|
||||
return new AwLevelDto(l.Id, l.Order, l.Name, l.ApproverUserId, info.FullName, info.Email,
|
||||
l.AllowReturnOneLevel, l.AllowReturnOneStep, l.AllowReturnToAssignee,
|
||||
l.AllowReturnToDrafter, l.AllowApproverEditDetails);
|
||||
l.AllowReturnToDrafter, l.AllowApproverEditDetails, l.AllowApproverEditBudget);
|
||||
}).ToList()
|
||||
)).ToList());
|
||||
|
||||
@ -184,12 +186,13 @@ public record CreateAwLevelInput(
|
||||
Guid ApproverUserId,
|
||||
// Mig 29 (S21 t5) — 5 Allow* options per slot. Admin Designer tick per
|
||||
// Level row. Default backward compat: AllowReturnToDrafter=true, 4 còn lại
|
||||
// false (admin opt-in từng slot).
|
||||
// false (admin opt-in từng slot). Mig 30 (S22+5) — F4 AllowApproverEditBudget.
|
||||
bool AllowReturnOneLevel = false,
|
||||
bool AllowReturnOneStep = false,
|
||||
bool AllowReturnToAssignee = false,
|
||||
bool AllowReturnToDrafter = true,
|
||||
bool AllowApproverEditDetails = false);
|
||||
bool AllowApproverEditDetails = false,
|
||||
bool AllowApproverEditBudget = false);
|
||||
|
||||
public record CreateAwStepInput(
|
||||
int Order,
|
||||
@ -315,6 +318,7 @@ public class CreateAwDefinitionCommandHandler(IApplicationDbContext db)
|
||||
AllowReturnToAssignee = l.AllowReturnToAssignee,
|
||||
AllowReturnToDrafter = l.AllowReturnToDrafter,
|
||||
AllowApproverEditDetails = l.AllowApproverEditDetails,
|
||||
AllowApproverEditBudget = l.AllowApproverEditBudget,
|
||||
}).ToList(),
|
||||
})
|
||||
.ToList(),
|
||||
|
||||
@ -82,12 +82,14 @@ public record PurchaseEvaluationChangelogDto(
|
||||
// FE eOffice filter Trả lại dropdown + Edit Section 2 enabled theo flag của
|
||||
// Cấp hiện tại NV đang duyệt. Null nếu phiếu V1 legacy hoặc không ChoDuyet.
|
||||
// F2 (Drafter skip) đã move sang `PeDetailBundleDto.DrafterAllowSkipToFinal`.
|
||||
// Mig 30 (S22+5) — F4 +AllowApproverEditBudget cho Section "Điều chỉnh ngân sách".
|
||||
public record ApprovalWorkflowOptionsDto(
|
||||
bool AllowReturnOneLevel,
|
||||
bool AllowReturnOneStep,
|
||||
bool AllowReturnToAssignee,
|
||||
bool AllowReturnToDrafter,
|
||||
bool AllowApproverEditDetails);
|
||||
bool AllowApproverEditDetails,
|
||||
bool AllowApproverEditBudget);
|
||||
|
||||
public record PurchaseEvaluationWorkflowSummaryDto(
|
||||
string PolicyName,
|
||||
|
||||
@ -290,7 +290,9 @@ public class AdjustPurchaseEvaluationBudgetCommandHandler(
|
||||
}
|
||||
else if (entity.Phase == PurchaseEvaluationPhase.ChoDuyet)
|
||||
{
|
||||
// Approver scope — actor phải là ApproverUserId của currentLevel
|
||||
// F4 (Mig 30 — S22+5) — Approver scope chỉ accept khi
|
||||
// currentLevel.AllowApproverEditBudget=true (admin Designer tick
|
||||
// per slot) AND actor match ApproverUserId.
|
||||
if (entity.ApprovalWorkflowId is not Guid awId)
|
||||
throw new ConflictException("Phiếu V1 legacy không hỗ trợ điều chỉnh ngân sách lúc đang duyệt.");
|
||||
if (entity.CurrentWorkflowStepIndex is not int csi || entity.CurrentApprovalLevelOrder is not int curLvl)
|
||||
@ -307,7 +309,14 @@ public class AdjustPurchaseEvaluationBudgetCommandHandler(
|
||||
throw new ConflictException("Pointer step out of range — schema lỗi.");
|
||||
var step = stepsOrdered[csi];
|
||||
var level = step.Levels.FirstOrDefault(l => l.Order == curLvl);
|
||||
if (level?.ApproverUserId != actorId)
|
||||
if (level is null)
|
||||
throw new ConflictException("Cấp duyệt không tìm thấy — schema lỗi.");
|
||||
if (!level.AllowApproverEditBudget)
|
||||
throw new ConflictException(
|
||||
$"Cấp Approver hiện tại (Bước {step.Order} / Cấp {curLvl}) " +
|
||||
"không được cấp quyền chỉnh sửa Section ngân sách. " +
|
||||
"Liên hệ Admin Designer cấp quyền slot.");
|
||||
if (level.ApproverUserId != actorId)
|
||||
throw new ForbiddenException(
|
||||
$"Chỉ NV phụ trách Bước {step.Order} / Cấp {curLvl} mới được điều chỉnh ngân sách lúc đang duyệt.");
|
||||
actorTag = $"[Approver Bước {step.Order}/Cấp {curLvl}]";
|
||||
@ -761,7 +770,8 @@ public class GetPurchaseEvaluationQueryHandler(
|
||||
curLevel.AllowReturnOneStep,
|
||||
curLevel.AllowReturnToAssignee,
|
||||
curLevel.AllowReturnToDrafter,
|
||||
curLevel.AllowApproverEditDetails);
|
||||
curLevel.AllowApproverEditDetails,
|
||||
curLevel.AllowApproverEditBudget);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -98,5 +98,11 @@ public class ApprovalWorkflowLevel : BaseEntity
|
||||
/// duyệt. KHÔNG đụng PE Header, KHÔNG reset workflow.
|
||||
public bool AllowApproverEditDetails { get; set; }
|
||||
|
||||
/// F4 (Mig 30 — S22+5) — Cho phép NV này edit Section "Điều chỉnh ngân sách"
|
||||
/// (BudgetId / BudgetManualName / BudgetManualAmount) lúc đang duyệt. Default
|
||||
/// false (admin opt-in per slot). Logic edit ngân sách giữ nguyên Drafter
|
||||
/// Nháp/Trả lại — flag này CHỈ mở thêm scope cho Approver ChoDuyet.
|
||||
public bool AllowApproverEditBudget { get; set; }
|
||||
|
||||
public ApprovalWorkflowStep? Step { get; set; }
|
||||
}
|
||||
|
||||
@ -77,5 +77,9 @@ public class ApprovalWorkflowLevelConfiguration : IEntityTypeConfiguration<Appro
|
||||
e.Property(x => x.AllowReturnToAssignee).HasDefaultValue(false);
|
||||
e.Property(x => x.AllowReturnToDrafter).HasDefaultValue(true);
|
||||
e.Property(x => x.AllowApproverEditDetails).HasDefaultValue(false);
|
||||
|
||||
// Mig 30 (S22+5) — F4 per-NV: cho phép edit Section "Điều chỉnh ngân sách"
|
||||
// lúc đang duyệt. Default false (admin opt-in).
|
||||
e.Property(x => x.AllowApproverEditBudget).HasDefaultValue(false);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAllowApproverEditBudgetToLevels : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "AllowApproverEditBudget",
|
||||
table: "ApprovalWorkflowLevels",
|
||||
type: "bit",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AllowApproverEditBudget",
|
||||
table: "ApprovalWorkflowLevels");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -188,6 +188,11 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("AllowApproverEditBudget")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("AllowApproverEditDetails")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
|
||||
Reference in New Issue
Block a user