Hướng dẫn thực hành tạo App đăng ký làm thêm giờ (OT)

Trạng thái: Pending / Approved / Rejected


0) Thông tin bài học

0.1. Đối tượng

  • Người mới học Power Apps (đã biết thao tác máy tính cơ bản)
  • Nhân sự / IT / Vận hành muốn số hóa quy trình OT

0.2. Thời lượng gợi ý

  • 2.5 – 3 giờ (thực hành đầy đủ)

0.3. Mục tiêu sau buổi học

Học viên có thể:

  1. Thiết kế dữ liệu cho quy trình OT (SharePoint List)
  2. Tạo Canvas App từ đầu
  3. Tạo màn hình:
    • Nhân viên tạo đơn OT
    • Nhân viên xem lịch sử OT
    • Quản lý duyệt đơn OT (Approve/Reject)
  4. Thiết lập trạng thái và màu hiển thị theo trạng thái
  5. Áp dụng các công thức quan trọng:
    • SubmitForm(), Patch(), Filter(), SortByColumns(), Switch(), If()
  6. Biết các lỗi hay gặp và cách sửa

1) Business Flow (Luồng nghiệp vụ)

1.1. Vai trò

  • Employee (Nhân viên): tạo đơn OT, xem trạng thái
  • Manager (Quản lý): duyệt đơn (Approve/Reject), ghi chú

1.2. Luồng xử lý chuẩn

  1. Nhân viên mở App → tạo đơn OT
  2. Khi bấm Submit → hệ thống lưu đơn với trạng thái mặc định Pending
  3. Quản lý mở App → xem danh sách đơn Pending
  4. Quản lý bấm:
    • Approve → Status = Approved
    • Reject → Status = Rejected (thường kèm lý do)
  5. Nhân viên xem lại đơn → thấy trạng thái cập nhật

Ghi chú triển khai thực tế: Có thể mở rộng thêm gửi email/Teams bằng Power Automate, hoặc phân quyền theo phòng ban.


2) Thiết kế dữ liệu (SharePoint List)

Mục tiêu phần này: Tạo đúng cấu trúc dữ liệu để Power Apps hoạt động mượt, dễ lọc và dễ báo cáo.

2.1. Tạo SharePoint List

  • Tên List: OvertimeRequests

Cách tạo (từng bước)

  1. Vào SharePoint Site của bạn
  2. Chọn NewList
  3. Chọn Blank list
  4. Đặt tên: OvertimeRequests
  5. Create

2.2. Cấu trúc cột (Columns)

SharePoint luôn có sẵn cột Title. Ta tận dụng Title làm Mã đơn.

Tên cộtKiểu dữ liệuBắt buộcÝ nghĩaVí dụ
TitleSingle line (mặc định)Mã đơn OTOT-001
EmployeeEmailSingle lineEmail nhân viên (để lọc “đơn của tôi”)a@cty.com
EmployeeNameSingle lineHọ tên nhân viênNguyễn Văn A
DepartmentChoicePhòng banIT / HR / Sales
OvertimeDateDate onlyNgày làm thêm2026-02-01
StartTimeDate & TimeGiờ bắt đầu2026-02-01 18:00
EndTimeDate & TimeGiờ kết thúc2026-02-01 21:00
ReasonMultiple linesLý do OTDeploy hệ thống
StatusChoiceTrạng tháiPending/Approved/Rejected
ManagerCommentMultiple linesKhôngGhi chú quản lý khi duyệtThiếu người nên OK
CreatedByPerson (optional)KhôngNgười tạo (nếu muốn)(tự động)

2.2.1. Tạo cột Choice “Department”

  1. List OvertimeRequestsAdd column
  2. Chọn Choice
  3. Name: Department
  4. Nhập choices:
    • IT
    • HR
    • Sales
    • Marketing
    • Finance
  5. Save

2.2.2. Tạo cột Choice “Status”

  1. Add columnChoice
  2. Name: Status
  3. Choices:
    • Pending
    • Approved
    • Rejected
  4. Default value (nếu có): Pending
  5. Save

Nếu SharePoint cho chọn Default value thì đặt luôn Pending để giảm lỗi. Nếu không, ta sẽ set Pending trong Power Apps.


2.3. Dữ liệu mẫu (15 dòng)

Mục tiêu: có dữ liệu để test lọc, duyệt, hiển thị màu.

Bạn có thể nhập thủ công 5 dòng, còn lại copy theo mẫu bên dưới.

TitleEmployeeEmailEmployeeNameDepartmentOvertimeDateStartTimeEndTimeReasonStatusManagerComment
OT-001a@cty.comNguyễn Văn AIT2026-02-012026-02-01 18:002026-02-01 21:00Deploy hệ thốngPending
OT-002b@cty.comTrần Thị BHR2026-02-022026-02-02 17:302026-02-02 20:00Tuyển dụng gấpApprovedOK
OT-003c@cty.comLê Minh CSales2026-02-032026-02-03 18:002026-02-03 22:00Chạy KPI cuối thángRejectedKhông đủ lý do
OT-004d@cty.comPhạm H DIT2026-02-042026-02-04 18:002026-02-04 21:30Fix bug productionPending
OT-005e@cty.comVõ Thị EMarketing2026-02-052026-02-05 17:002026-02-05 20:00Chạy quảng cáoApproved
OT-006f@cty.comHoàng FIT2026-02-062026-02-06 19:002026-02-06 23:00Backup serverPending
OT-007g@cty.comNguyễn GSales2026-02-072026-02-07 18:002026-02-07 21:00Gặp khách hàngRejectedKhông có plan
OT-008h@cty.comTrần HHR2026-02-082026-02-08 17:302026-02-08 19:30Tính lươngApproved
OT-009i@cty.comLê IIT2026-02-092026-02-09 18:002026-02-09 20:30Update hệ thốngPending
OT-010j@cty.comPhạm JMarketing2026-02-102026-02-10 17:002026-02-10 19:00Event gấpPending
OT-011k@cty.comVõ KIT2026-02-112026-02-11 18:002026-02-11 22:00TestingApproved
OT-012l@cty.comHoàng LSales2026-02-122026-02-12 18:002026-02-12 21:00Báo cáoPending
OT-013m@cty.comNguyễn MHR2026-02-132026-02-13 17:302026-02-13 20:00Phỏng vấnRejectedThiếu nhân sự duyệt
OT-014n@cty.comTrần NIT2026-02-142026-02-14 18:002026-02-14 21:00Triển khaiPending
OT-015o@cty.comLê OMarketing2026-02-152026-02-15 17:002026-02-15 19:30Content gấpApprovedOK

3) Tạo Canvas App

3.1. Tạo App

  1. Vào make.powerapps.com
  2. Chọn Create
  3. Chọn Canvas app from blank
  4. Name: OT Request App
  5. Format: Tablet (khuyến nghị để demo rõ)
  6. Create

3.2. Kết nối dữ liệu SharePoint

  1. Mở tab Data (biểu tượng hình database)
  2. Add data
  3. Chọn SharePoint
  4. Chọn Connect (nếu lần đầu)
  5. Chọn đúng Site
  6. Chọn List: OvertimeRequests
  7. Add

Kiểm tra: Trong Data panel phải thấy OvertimeRequests.


4) Thiết kế giao diện tổng thể (3 màn hình)

Bạn sẽ tạo 3 màn hình chuẩn:

  1. scr_Request: Nhân viên tạo đơn OT
  2. scr_MyRequests: Nhân viên xem lịch sử & trạng thái
  3. scr_Manager: Quản lý duyệt đơn

5) Màn hình 1 — scr_Request (Tạo đơn OT)

5.1. Tạo màn hình

  1. Tree view → New screen
  2. Chọn Blank
  3. Đổi tên Screen: scr_Request

5.2. Thêm tiêu đề

  1. Insert → Label
  2. Text: Đăng ký làm thêm giờ (OT)
  3. FontSize: 24
  4. Align: Center
  5. Width: bằng màn hình

5.3. Thêm Edit Form

  1. Insert → FormsEdit form
  2. Đổi tên form: frmOT
  3. Ở panel bên phải:
    • DataSource: OvertimeRequests
    • DefaultMode: FormMode.New

5.3.1. Chọn field hiển thị trong Form

  1. Chọn frmOT → Edit fields
  2. Add các field:
    • Title
    • EmployeeEmail
    • EmployeeName
    • Department
    • OvertimeDate
    • StartTime
    • EndTime
    • Reason
    • Status (có thể ẩn khỏi người dùng)
  3. Sắp xếp theo thứ tự hợp lý: thông tin nhân viên → thời gian → lý do

5.4. Tự động set dữ liệu nhân viên

Mục tiêu: tự điền email, tên để giảm sai.

5.4.1. EmployeeEmail Default

  1. Click DataCard EmployeeEmail
  2. Unlock (nếu bị khóa)
  3. Chọn TextInput bên trong (thường tên: DataCardValue...)
  4. Thuộc tính Default:
User().Email

5.4.2. EmployeeName Default

  • Default:
User().FullName

Nếu môi trường không trả FullName, có thể dùng User().Email tạm, hoặc lấy từ danh bạ (phần nâng cao).


5.5. Sinh mã đơn (Title) tự động

Cách demo đơn giản: tạo Title dựa trên thời gian.

  1. Chọn DataCard Title → Unlock
  2. Chọn TextInput trong Title → Default:
"OT-" & Text(Now(), "yyyymmdd-hhnnss")

Lưu ý: Đây là mã duy nhất theo thời gian, đủ cho demo. Nếu cần dạng OT-0001 chạy số, sẽ làm ở phần nâng cao.


5.6. Đặt Status mặc định = Pending và không cho sửa

5.6.1. Default của Status

  1. Chọn DataCard Status → Unlock
  2. Chọn ComboBox/Dropdown trong DataCard (thường là ComboBox)
  3. Thuộc tính DefaultSelectedItems (với Choice):
[{Value:"Pending"}]

5.6.2. Ẩn Status khỏi nhân viên

Chọn DataCard Status → thuộc tính Visible:

false

5.7. Nút Submit (Gửi đơn)

  1. Insert → Button
  2. Text: Gửi đơn OT
  3. Đổi tên: btnSubmit

5.7.1. OnSelect của btnSubmit

SubmitForm(frmOT)

5.7.2. frmOT – OnSuccess (sau khi lưu thành công)

Chọn frmOT → thuộc tính OnSuccess:

Notify("Gửi đơn OT thành công! Trạng thái: Pending", NotificationType.Success);
NewForm(frmOT)

5.7.3. frmOT – OnFailure (nếu lưu lỗi)

frmOT → OnFailure:

Notify("Gửi đơn thất bại. Vui lòng kiểm tra dữ liệu hoặc kết nối!", NotificationType.Error)

6) Màn hình 2 — scr_MyRequests (Lịch sử đơn của tôi)

6.1. Tạo màn hình

New screen → Blank → đổi tên: scr_MyRequests

6.2. Thêm tiêu đề

Label Text: Đơn OT của tôi

6.3. Thêm Dropdown lọc trạng thái

Insert → Dropdown → đổi tên drpStatus

  • Items:
["All","Pending","Approved","Rejected"]
  • Default:
"All"

6.4. Thêm Gallery hiển thị đơn

Insert → Vertical gallery → đổi tên galMyOT

6.4.1. Items của galMyOT (lọc theo người dùng + trạng thái)

With(
    { _email: User().Email },
    If(
        drpStatus.Selected.Value = "All",
        SortByColumns(
            Filter(OvertimeRequests, EmployeeEmail = _email),
            "Created",
            SortOrder.Descending
        ),
        SortByColumns(
            Filter(
                OvertimeRequests,
                EmployeeEmail = _email && Status.Value = drpStatus.Selected.Value
            ),
            "Created",
            SortOrder.Descending
        )
    )
)

6.4.2. Hiển thị thông tin trong Gallery

Trong template gallery, thêm các label:

  • lblEmpName: ThisItem.EmployeeName
  • lblDate: Text(ThisItem.OvertimeDate, “dd/mm/yyyy”)
  • lblTime: Text(ThisItem.StartTime, “hh:mm”) & ” – ” & Text(ThisItem.EndTime, “hh:mm”)
  • lblStatus: ThisItem.Status.Value

6.4.3. Màu trạng thái

Chọn label Status → thuộc tính Color:

Switch(
    ThisItem.Status.Value,
    "Pending", ColorValue("#F59E0B"),   // cam
    "Approved", ColorValue("#10B981"),  // xanh lá
    "Rejected", ColorValue("#EF4444"),  // đỏ
    Black
)

7) Màn hình 3 — scr_Manager (Duyệt OT)

7.1. Tạo màn hình

New screen → Blank → đổi tên scr_Manager

7.2. Tiêu đề

Label Text: Duyệt đơn OT

7.3. Dropdown lọc nhanh

Insert → Dropdown → drpManagerFilter

  • Items:
["Pending","Approved","Rejected","All"]
  • Default:
"Pending"

7.4. Gallery danh sách đơn để duyệt

Insert → Vertical gallery → galApprove

7.4.1. Items

If(
    drpManagerFilter.Selected.Value = "All",
    SortByColumns(OvertimeRequests, "Created", SortOrder.Descending),
    SortByColumns(
        Filter(OvertimeRequests, Status.Value = drpManagerFilter.Selected.Value),
        "Created",
        SortOrder.Descending
    )
)

7.5. Chọn 1 đơn → hiển thị chi tiết

Cách dễ dạy: dùng Display Form bên cạnh gallery.

  1. Insert → Forms → Display form
  2. Đổi tên: frmDetail
  3. DataSource: OvertimeRequests
  4. Item:
galApprove.Selected
  1. Fields: EmployeeName, Department, OvertimeDate, StartTime, EndTime, Reason, Status, ManagerComment

7.6. Ô nhập “ManagerComment” khi Reject

Cách nhanh: đặt 1 TextInput riêng (không bind trực tiếp form).

  1. Insert → Text input (Multiline) → txtComment
  2. HintText: Nhập lý do/ghi chú của quản lý...

7.7. Nút Approve / Reject

7.7.1. Nút Approve

Insert → Button → Text: Approve
OnSelect:

Patch(
    OvertimeRequests,
    galApprove.Selected,
    {
        Status: {Value:"Approved"},
        ManagerComment: txtComment.Text
    }
);
Notify("Đã duyệt đơn!", NotificationType.Success);
Reset(txtComment)

7.7.2. Nút Reject (bắt buộc nhập lý do)

Insert → Button → Text: Reject
OnSelect:

If(
    IsBlank(Trim(txtComment.Text)),
    Notify("Vui lòng nhập lý do từ chối (ManagerComment)!", NotificationType.Warning),
    Patch(
        OvertimeRequests,
        galApprove.Selected,
        {
            Status: {Value:"Rejected"},
            ManagerComment: txtComment.Text
        }
    );
    Notify("Đã từ chối đơn!", NotificationType.Error);
    Reset(txtComment)
)

8) Điều hướng (Menu) giữa các màn hình

8.1. Thêm 3 nút ở đầu App (hoặc dùng icon)

  • Button 1: “Tạo đơn” → OnSelect:
Navigate(scr_Request, ScreenTransition.Fade)
  • Button 2: “Đơn của tôi” → OnSelect:
Navigate(scr_MyRequests, ScreenTransition.Fade)
  • Button 3: “Duyệt (Manager)” → OnSelect:
Navigate(scr_Manager, ScreenTransition.Fade)

Thực tế cần phân quyền (chỉ Manager mới thấy). Phần này sẽ làm ở nâng cao.


9) Kiểm thử theo kịch bản (Checklist cho học viên)

9.1. Test tạo đơn

  1. Mở scr_Request
  2. Nhập Department, OvertimeDate, StartTime, EndTime, Reason
  3. Bấm Submit
    ✅ Kỳ vọng:
  • Lưu được record trong SharePoint
  • Status = Pending
  • App báo “Gửi đơn thành công”

9.2. Test xem lịch sử

  1. Mở scr_MyRequests
  2. Chọn filter All/Pending/Approved/Rejected
    ✅ Kỳ vọng:
  • Chỉ thấy đơn của email hiện tại
  • Lọc đúng theo trạng thái

9.3. Test duyệt

  1. Mở scr_Manager
  2. Chọn 1 record Pending
  3. Nhập comment → Approve
    ✅ Kỳ vọng:
  • Status đổi Approved
  • Comment được lưu
  1. Chọn 1 record Pending → Reject (không nhập comment)
    ✅ Kỳ vọng:
  • Bị cảnh báo yêu cầu nhập lý do

10) Lỗi thường gặp & cách xử lý (rất quan trọng)

10.1. Lỗi “Status không cập nhật”

Hiện tượng: bấm Approve nhưng Status không đổi
Nguyên nhân: Cột Status là Choice → phải patch theo dạng record {Value:"..."}
✅ Cách sửa:

Status: {Value:"Approved"}

10.2. Lỗi “Gallery không lọc được theo Status”

Nguyên nhân: So sánh sai kiểu. Status là Choice → phải dùng Status.Value
✅ Cách sửa:

Filter(OvertimeRequests, Status.Value = "Pending")

10.3. Lỗi SubmitForm không lưu

Nguyên nhân thường gặp:

  • Form chưa ở chế độ New
  • Thiếu field bắt buộc
    ✅ Cách sửa:
  • frmOT.DefaultMode = FormMode.New
  • Kiểm tra Required field trong SharePoint

10.4. Lỗi thời gian Start/End sai ngày

Nguyên nhân: người dùng chọn giờ nhưng date bị lệch
✅ Cách giảm lỗi:

  • Dùng DatePicker cho ngày + Dropdown giờ riêng (nâng cao)
  • Hoặc hướng dẫn nhập đúng ngày cho Start/End


Scroll to Top