Đối tượng sử dụng: Giám đốc
Mục đích: Thống kê số lượng bác sĩ, bệnh nhân theo tháng/năm
Mô tả khái quát: Người dùng chọn chức năng thống kê báo cáo. Sau đó có thể xem thống kê bác sĩ hoặc bệnh nhân theo tháng/năm
Chức năng tham chiếu: R5 Sơ đồ luồng sự kiện: Luồng chính:
1.Người dùng chọn chức năng thống kê báo cáo 2. Hệ thống hiển thị giao diện thống kê báo cáo
3. Người dùng chọn thống kê số lượng bác sĩ hoặc bệnh nhân và chọn thời gian thống kê
4. Hệ thống kiểm tra và đáp ứng yêu cầu 5. Kết thúc ca sử dụng
c) Xây dựng các biểu đồ
Biều đồ tuần tự
Biểu đồ tuần tự đăng nhập hệ thống
Sau đây là biểu đồ phân tích tuần tự của ca sử dụng đăng nhập hệ thống, với ca sử dụng này các tác nhân là Người quản trị hệt hống, Bác sĩ, Bệnh nhân và Giám đốc
đều sử dụng đến nó nên Actor trong trường hợp này là Người dùng thay vì phải viết chi tiết từng Actor
Hình 3.8. Biểu đồ tuần tự chức năng đăng nhập
Biểu đồ tuần tự tạo tài khoản bệnh nhân
Sau đây là biểu đồ phân tích tuần tự của ca sử dụng tạo tài khoản bệnh nhân
Hình 3.9. Biểu đồ tuần tự chức năng tạo tài khoản bệnh nhân
Sau đây là biểu đồ phân tích tuần tự của ca sử dụng chỉnh sửa thơng tin bác sĩ
Hình 3.10. Biểu đồ t̀n tự chức năng chỉnh sửa thông tin bác sĩ
Biểu đồ tuần tự chức năng tìm kiếm bác sĩ
Sau đây là biểu đồ phân tích t̀n tự của ca sử dụng tìm kiếm bác sĩ
Hình 3.11. Biểu đồ tuần tự chức năng tìm kiếm bác sĩ Biểu đồ hoạt động
Hình 3.12. Biểu đồ hoạt động đăng nhập hệ thống
Hoạt động thêm bệnh nhân
Hình 3.13. Biểu đồ hoạt động thêm bệnh nhân
Hình 3.14. Biểu đồ hoạt động cập nhập thông tin bệnh nhân
Biể đồ lớp
Biểu đồ trạng thái
Biểu đồ trạng thái lớp bệnh nhân
Hình 3.17. Biểu đồ trạng thái lớp bệnh nhân
Biểu đồ trạng thái lớp tài khoản
Trạng thái lớp bệnh nhân
Hình 3.19. Trạng thái lớp bệnh nhân
Trạng thái cuộc gọi điện
Biểu đồ thành phần
Hình 3.21. Biểu đồ thành phần Biểu đồ triển khai Biểu đồ triển khai
Hình 3.22. Biểu đồ triển khai
3.4.3. Thiết kế hệ thống:
Giao diện đăng nhập hệ thống
Hình 3.23. Giao diện đăng nhập hệ thống Giao diện bác sĩ Giao diện bác sĩ
Giao diện quản lý bệnh nhân
Giao diện quản lý hồ sơ bệnh án
Hình 3.25. Giao diện quản lý hồ sơ bệnh án
Giao diện hiển thị thơng tin bệnh nhân
Giao diện thêm bệnh nhân
Hình 3.27. Giao diện thêm bệnh nhân Giao diện bệnh nhân Giao diện bệnh nhân
Giao diện chính
Giao diện hiển thị thơng tin cá nhân
Hình 3.29. Giao diện hiển thị thông tin cá nhân
Giao diện tư vấn từ xa
Giao diện giám đốc
Hình 3.31. Giao diện giám đốc
b) Thiết kế cơ sở dữ liệu
Cơ sở dữ liệu của hệ thống được thiết kế gồm có 5 Collection, gồm: Bac_si, Benh_nhan, Benh_an, Tin_nhan, Xet_nghiem. Mỗi Collection được nhúng thêm các Document nhỏ để tăng thêm khả năng lưu trữ dữ liệu. Do Giám đốc và quản lý có thơng tin trùng với bác sĩ nên sẽ được lưu chung tại Colletion Bac_si và được phân biệt qua Field ChucDanh. Dưới đây là thiết kế của các Colletion.
Colletion BacSi
Colletion BenhNhan
Hình 3.33. Colletion BenhNhan
Colletion BenhAn
Colletion TinNhan
Hình 3.35. Colletion TinNhan
Colletion XetNghiem
Hình 3.36. Colletion XetNghiem
c) Thiết kế các ràng buộc
Ràng buộc trong thiết kế hệ thống
Quy trình:
Định hướng, nghiên cứu tính khả thi Bản kế hoạch dự án Bản đặc tả yêu cầu hệ thống Phân tích, thiết kế hệ thống Xây dựng và kiểm thử hệ thống Công cụ phát triển: Chrome DevTools MongoDB Compass
d) Thiết kế kiến trúc hệ thống
Sử dụng mơ hình kiến trúc Model-View-Controller. Mơ hình này tách riêng phần biểu diễn và phần tương tác ra khỏi dữ liệu hệ thống. Hệ thống được cấu trúc hóa thành ba component logic tương tác với nhau:
+Model component: quản lý dữ liệu hệ thống và các thao tác trên dữ trên dữ liệu đó.
+View component: định nghĩa và quản lý cách dữ liệu được biểu diễn tới người dùng như thế nào.
+Controller component: Quản lý tương tác người dùng và chuyển các tương tác này tới View và Model.
3.5. Cài đặt (Implementation)
3.5.1. Cài đặt Modul:
Hệ thống hỗ trợ tương tác trong quá trình điều trị bệnh nhân từ xa được xây dựng dựa trên mơ hình MVC. Trong chương trình sử dụng các lớp sau:
Controller dùng để tiếp nhận những yêu cầu xử lý từ phía người dùng, nó gồm các function xử lý nhiều nghiệp vụ logic giúp lấy đúng dữ liệu thông tin cần thiết nhờ các nghiệp nghiệp vụ lớp Model cung cấp và hiển thị dữ liệu đó ra cho người dùng ở lớp View. Lớp controller là lớp trung gian để lớp Model và lớp View tương tác với nhau qua lớp Controller. Ví dụ như lớp “patientController”
const express = require('express'); const mongoose = require('mongoose'); const multer = require('multer'); const path = require('path'); const fs = require('fs');
const Patient = mongoose.model('Patient'); //
exports.procedureRequest = function(req, res, next){ res.render("patients/procedurerequest", {
title: 'procedurerequest' });
} //
exports.listPatients = function(req, res) { return Patient.find((err, docs) => { if (!err) {
res.render("patients/index", { title: 'List patients', list: docs,
patientSlideBarActive:true, });
} else {
console.log('Error in retrieving patients list :' + err); }
}); }
//
exports.searchPatients = function (req, res, next) { var q = req.query.q
Patient.find({ name: { $regex: q, $options: 'i' } }, { _id: 0, __v: 0 }, (err, docs) => {
if (!err) {
res.json(docs) } else {
}
}).limit(5); }
exports.searchResults = function (req, res, next) { var q = req.query.q
Patient.find({ name: { $regex: q, $options: 'i' } }, (err, docs) => { if (!err) { console.log(docs) res.render("patients/index", { title: 'patients', patientSlideBarActive: true, list: docs, }); } else {
console.log('Error in retrieving patients list :' + err); }
}); }
exports.editPatient = function(req, res, next) { Patient.findById(req.params.id, (err, doc) => { if (!err) {
console.log(doc)
res.render("patients/create_edit", { title: "Edit patients",
isEdit: true, patient: doc }); } }); }
exports.detailsPatient = function(req, res, next) { Patient.findById(req.params.id, (err, doc) => { if (!err) {
var date = new Date(doc.birthDate); var date1 = new Date(Date.now())
var age = parseInt(date1.getFullYear()) - parseInt(date.getFullYear());
console.log(age);
res.render("patients/details", { title: "Details Patient", isEdit: true,
patient: doc,
birthDate: date.getDate() + '/' + date.getMonth() + '/' + date.getFullYear(),
Age: age, });
}); }
const storageEngine = multer.diskStorage({ destination: './public/files',
filename: function(req, file, fn) {
fn(null, new Date().getTime().toString() + '- ' + file.fieldname + path.extname(file.originalname)); // console.log(file)
} });
const uploadPhoto = multer({ storage: storageEngine, limits: {
fileSize: 2000000 },
fileFilter: function(req, file, callback) { validateFile(file, callback);
}
}).single('photo');
var validateFile = function(file, cb) { allowedFileTypes = /jpeg|jpg|png|gif/;
const extension = allowedFileTypes.test(path.extname(file.originaln ame).toLowerCase());
const mimeType = allowedFileTypes.test(file.mimetype); if (extension && mimeType) {
return cb(null, true); } else {
cb("Invalid file type. Only JPEG, PNG and GIF file are allowed. ")
} }
exports.insertRecord = function(req, res, next) { uploadPhoto(req, res, (error) => {
if (error) {
console.log("Error 1"); } else {
var patient = new Patient(); patient.id = req.body.id; patient.name = req.body.name; patient.telecom = req.body.telecom; patient.birthDate = req.body.birthDate; patient.sex = req.body.sex; patient.job = req.body.job; patient.address = req.body.address; patient.account = [{ useruser: req.body.user, password: req.body.password, }]; if (req.file == undefined) { patient.photo = "files/donganh.png"
var fullPath = "files/" + req.file.filename; patient.photo = fullPath; } console.log(req.body) patient.save((err, doc) => { if (!err) { return res.redirect('/patients/list'); } else { if (err.name == 'ValidationError') { handleValidationError(err, req.body); console.log(req.body) res.render("patients/create_edit", { viewTitle: "Insert Patient", isEdit: false,
patient: req.body });
} else
console.log('Error during record insertion : ' + err); } }); } }); } //
exports.updateRecord = function updateRecord(req, res) { uploadPhoto(req, res, (error) => {
if (error) { console.log('Error 1'); } else { Patient.findOneAndUpdate({ _id: req.body._id }, req.body, { runValidators: true }, (err, doc) => { if (!err) { console.log(doc)
if (req.file == undefined) {} else {
var fullPath = "./public/" + doc.photo
if (fullPath != "./public/files/donganh.png") { fs.unlink(fullPath, (error) => { if (error) { console.error(error) return } })
var fullPath1 = "files/" + req.file.filename; doc.photo = fullPath1; } console.log(req.file) doc.communication[0].language =req.body.language doc.save(); res.redirect('/patients/list'); } else { if (err.name == 'ValidationError') { handleValidationError(err, req.body); res.render("/patients/create_edit", { viewTitle: 'Update Patient', isEdit: true,
patient: req.body });
} else
console.log('Error during record update : ' + e rr); } }); } }); }
exports.deleteRecord = function(req, res) {
Patient.findByIdAndRemove(req.params.id, (err, doc) => { if (!err) {
res.redirect('/patients/list');
var fullPath = "./public/" + doc.photo
if (fullPath != "./public/files/donganh.png") { fs.unlink(fullPath, (err) => { if (err) { console.error(err) return } }) } } else {
console.log('Error in patients delete :' + err); }
}); }
//
function handleValidationError(err, body) { for (field in err.errors) {
switch (err.errors[field].path) { case 'name':
body['nameError'] = err.errors[field].message; break;
break; }
} }
Lớp Model: Là nơi chứa những nghiệp vụ tương tác với hệ quản trị cơ sở dữ liệu (MongoDB ); Lớp model được định nghĩa thông qua giao diện Schema. Schema sẽ định nghĩa các trường được lưu trong mỗi document đi kèm với điều kiện xác thực, giá trị mặc định của nó. Schema sau đó được "biên dịch" thành mơ hình qua phương thức mongoose.model(). Một khi đã có mơ hình thì ta có thể dùng nó để tìm, thêm, sửa, và xố các đối tượng cùng kiểu. Dưới đây là ví dụ file patient.model.js
const mongoose = require('mongoose'); var patientSchema = new mongoose.Schema({ id: {
type: String },
name: {
type: String,
required: 'This field is required.' }, account:[{ user:{ type: String }, password:{ type: Boolean, }, }], telecom:{ type: Number }, sex:{ type: Boolean }, birthDate:{ type: Date }, job:{ type: String }, address:{ type: String } , }); patientSchema.path('telecom').validate((val) => { return /[0-9]{10,12}/.test(val); }, 'Invalid telecom'); mongoose.model('Patient', patientSchema);
Lớp Views: Là lớp chứa các file giao diện phía người dùng, nó là nơi chứa những giao diện như một nút bấm, khung nhập, menu, hình ảnh… nó đảm nhiệm nhiệm vụ hiển thị dữ liệu và giúp người dùng tương tác với hệ thống. Trong Views gồm 2 flile là file create_edit và file index. File create để thiết kế giao diện người dùng, gọi đến AIP lấy
dự liệu từ CSDL, file index là file giao diện người dùng sau khi sử dụng nút bấm. Sau đây là ví dụ lớp viewer của đối tượng bệnh nhân:
File “create_edit.hbs”
<div class="row">
<div class="col-md-12">
<div class="card card-primary">
<div class="card-header no-border">
<h3 class="card-title">Thêm bệnh nhân</h3> <div class="d-flex justify-content-between"> </div>
</div>
<form action="{{#if isEdit}}/patients/update{{else}}/patients/sto re{{/if}}" method="POST" autocomplete="off" enctype="multipart/form-data"> <div class="card-body">
<input type="hidden" name="_id" value="{{patient._id}}"> <div class="row">
<label class="col-md-2">Mã bệnh nhân</label> <div class="col-md-4">
<input name="id" class="form- control" value="{{patient.id}}"/>
</div>
<label class="col-md-2">Họ và tên</label> <div class="col-md-4">
<input name="name" class="form- control" value="{{patient.name}}"/>
</div> </div> <br>
<div class="row">
<label class="col-md-2">Tài khoản</label> </div>
<br>
<div class="row">
<label class="col-md-2">User</label> <div class="col-md-4">
<input type="textarea" name="user" class="form- control" value="{{patient.account.user}}" />
</div>
<label class="col-md-2">Password</label> <div class="col-md-4">
<input type="text" name="password" class="form- control" value="{{patient.account.password}}" />
</div> </div> <br>
<div class="row">
<label class="col-md-2">Số điện thoại</label> <div class="col-md-4">
<input name="telecom" class="form- control" value="{{patient.telecom}}"/>
</div>
<label class="col-md-2">Giới tính</label> <div class="col-md-4">
<select name="sex" class="form-control"> <option value="1">Male</option> <option value="2">Female</option> </select>
<br>
<div class="row">
<label class="col-md-2">Ngày sinh</label> <div class="col-md-4">
<input name="birthDate" class="form- control datepicker" value="{{patient.birthDate}}"/>
</div>
<label class="col-md-2">Nghề nghiệp</label> <div class="col-md-4">
<input name="job" class="form- control" value="{{patient.job}}"/>
</div> </div> <br>
<div class="row">
<label class="col-md-2">Địa chỉ</label> <div class="col-md-4">
<input name="address" class="form- control" value="{{patient.address}}"/> </div> </div> <br> <div class="card-footer"> <center><button class="btn btn- default">Submit</button></center> </div> </form> </div> </div> </div> <script> $(document).ready(function(){ $('.datepicker').datepicker(); }); </script> File “index.hbs” <div class="row"> <div class="col-md-12"> <div class="card"> <div class="card-header"> <div class="row"> <h3 class="card-title">Danh sách bệnh nhân</h3>
<a href="/patients/create" class="btn btn-primary btn- sm" style="margin-left: 10px;">Thêm mới</a>
</div>
<div class="card-tools"> <div class="row">
<form class="form-inline ml- 3" action="/patients/list/result" method="get">
<div class="input-group input-group-sm"> <input class="form-control float- right" style="background-
color:white;" id="search" name="q" type="search" placeholder="Search" aria- label="Search" autocomplete="off">
<div class="input-group-append"> <button class="btn btn-
navbar" type="submit">
<i class="fa fa-search"> </i>
</div>
<div class="input-group input-group-sm" id="search- results" style="position: absolute;width:203px;top:32px;">
</div> </form> </div> </div> </div> <div class="card-body">
<table class="table table-hover"> <thead> <tr> <th>#</th> <th>Tên</th> <th>Tuổi</th> <th>Địa chỉ</th> <th>Action</th> </tr> </thead> <tbody id="tbody_table_user"></tbody> </table> <div id="paginate"> </div> </div> </div> </div> </div> <script>
var sources = function () { var result = [];
{{#each list}}
var date = new Date("{{this.birthDate}}"); var date1 = new Date(Date.now())
var age = parseInt(date1.getFullYear()) - parseInt(date.getFullYear()); result.push({name:"{{this.name}}",age:age,id:"{{this._id}}",address :"{{this.address}}"}); {{/each}} //console.log(result) return result; }(); </script> <script src="/js/Search/searchPatients.js"></script> <script src = /js/pagination/paginationPatients.js></script>
Vì hệ thống phải quản lý nhiều thông tin của các đối tượng. Nên cần rất nhiều dòng lệnh route cho mỗi đối tượng. Trong bài xây dựng hệ thống thông tin này em phân chia các route này theo từng đối tượng. Mỗi đối tượng có một file route riêng để việc bảo trì sẽ dễ dàng hơn.file routes dùng để định tuyến, các client gửi yêu cầu lên server qua đường link được định tuyến ở routes, server phản hồi lại tới client. Sau đây là ví dụ về file “patient.js”
var express = require('express'); var router = express.Router();
var patient_controller = require('../controllers/patientController'); const auth = require('../middlewares/auth');
});
router.get('/create', auth, function(req, res, next){
res.render('patients/create_edit', { title: 'Create new patient', isEdit: false });
});
router.post('/store', function(req, res, next) { patient_controller.insertRecord(req, res, next); });
router.get('/delete/:id',function(req,res,next){ patient_controller.deleteRecord(req,res)
})
router.get('/details/:id', function(req, res, next){ patient_controller.detailsPatient(req,res,next); });
router.get('/edit/:id', auth, function(req, res, next){ patient_controller.editPatient(req, res, next);
});
router.post('/update', function(req, res, next) { patient_controller.updateRecord(req, res, next); });
router.get('/delete/:id', auth, function(req, res, next){ patient_controller.deleteRecord(req, res, next);
});
router.get('/procedurerequest', auth, function(req, res, next){ patient_controller.procedureRequest(req, res, next);
});
module.exports = router;
Public : ngoài ra hệ thống còn sử dụng folder Public chứa các file ảnh, video, để phía clinet nhìn được.
File “www” để tạo và thiết lập các thông số của server, Server tạo Socket, gán số hiệu cổng và lắng nghe yêu cầu nối kết.
File “app.js” để thiết lập cho folder “public” ở dạng static để phía clinet có thể truy cập đc, tạo ra link api truy cập tới các modle tương ứng
link truy cập tới api
File db: Để kết nối tới CSDL
Source code chi tiết xem tại đĩa CD đính kèm.
3.5.2. Chuyển giao cơ sở dữ liệu
Hệ cơ sở dữ liệu hiện tại của phòng khám nha khoa Như Ngọc hỗ trợ lưu trữ từ các phiếu chỉ định cho đến các phiếu thu chi của phòng khám. Trong quá trình xây dựng hệ thống hỗ trợ điều trị từ xa, nhận thấy một số Collection khơng cịn phù hợp hệ thống mới nên em đã tiến hành một số sự điều chỉnh sau:
Đầu tiên, ta xác định các Collection phù hợp có thể sử dụng lại để bảo tồn thơng tin dữ liệu của phòng khám, các Collection cần thay thế và điều chỉnh cấu trúc, loại bỏ các Collection dư thừa không phù hợp. Một số Collection bị loại bỏ là : Collection DichVu, Collection HoaDon, Collection ThuThuat.
Tiếp theo, đối với các Collection cần thay thế và điều chỉnh cấu trúc, ta tiến hành tạo mới Collection có cấu trúc ổn định và phù hợp hơn so với hệ thống.
Cuối cùng, chuyển dữ liệu của các Collection phù hợp từ hệ cơ sở dữ liệu hiện tại của phòng khám nha khoa Như Ngọc sang hệ cơ sở dữ liệu mới của hệ thống.
3.5.3. Kiếm thử hệ thống
Kiểm thử phần mềm là quá trình thực thi 1 chương trình với mục đích tìm ra lỗi. Kiểm thử phần mềm đảm bảo sản phẩm phần mềm đáp ứng chính xác, đầy đủ và đúng theo yêu cầu của khách hàng, yêu cầu của sản phẩm đề đã đặt ra. Kiểm thử phần mềm cũng cung cấp mục tiêu, cái nhìn độc lập về phần mềm, điều này cho phép việc đánh giá và hiểu rõ các rủi ro khi thực thi phần mềm.
Trong bài khóa luận này, em sử dụng kỹ thuật kiểm thử hộp đen để kiểm thử hệ thống. Kiểm thử hộp đen là một phương pháp kiểm thử phần mềm được thực hiện mà không biết được cấu tạo bên trong của phần mềm, là cách mà các tester kiểm tra xem