21#ifdef PACS_WITH_DATABASE_SYSTEM
23#include <database/query_builder.h>
27using kcenon::common::make_error;
28using kcenon::common::ok;
32auto get_string(
const database_row& row,
const std::string& key) -> std::string {
33 auto it = row.find(key);
34 return (it != row.end()) ? it->second : std::string{};
37auto get_int64(
const database_row& row,
const std::string& key) -> int64_t {
38 auto it = row.find(key);
39 if (it == row.end() || it->second.empty()) {
44 return std::stoll(it->second);
53 : base_repository(std::
move(db),
"mpps",
"mpps_pk") {}
55auto mpps_repository::parse_timestamp(
const std::string& str)
const
56 -> std::chrono::system_clock::time_point {
62 if (std::sscanf(str.c_str(),
"%d-%d-%d %d:%d:%d", &tm.tm_year, &tm.tm_mon,
63 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
71 auto time = _mkgmtime(&tm);
73 auto time = timegm(&tm);
76 return std::chrono::system_clock::from_time_t(time);
79auto mpps_repository::format_timestamp(
80 std::chrono::system_clock::time_point tp)
const -> std::string {
81 if (tp == std::chrono::system_clock::time_point{}) {
85 auto time = std::chrono::system_clock::to_time_t(tp);
94 std::strftime(buf,
sizeof(buf),
"%Y-%m-%d %H:%M:%S", &tm);
98auto mpps_repository::create_mpps(std::string_view mpps_uid,
99 std::string_view station_ae,
100 std::string_view modality,
101 std::string_view study_uid,
102 std::string_view accession_no,
103 std::string_view start_datetime)
106 record.mpps_uid = std::string(mpps_uid);
107 record.station_ae = std::string(station_ae);
108 record.modality = std::string(modality);
109 record.study_uid = std::string(study_uid);
110 record.accession_no = std::string(accession_no);
111 record.start_datetime = std::string(start_datetime);
112 record.status =
"IN PROGRESS";
113 return create_mpps(record);
116auto mpps_repository::create_mpps(
const mpps_record& record) -> Result<int64_t> {
117 if (
record.mpps_uid.empty()) {
118 return make_error<int64_t>(-1,
"MPPS SOP Instance UID is required",
122 if (!
record.status.empty() &&
record.status !=
"IN PROGRESS") {
123 return make_error<int64_t>(
124 -1,
"N-CREATE must create MPPS with status 'IN PROGRESS'",
128 if (!db() || !db()->is_connected()) {
129 return make_error<int64_t>(-1,
"Database not connected",
"storage");
132 auto builder = query_builder();
133 builder.insert_into(
"mpps")
134 .values({{
"mpps_uid",
record.mpps_uid},
135 {
"status", std::string(
"IN PROGRESS")},
136 {
"start_datetime",
record.start_datetime},
137 {
"end_datetime",
record.end_datetime},
138 {
"station_ae",
record.station_ae},
139 {
"station_name",
record.station_name},
140 {
"modality",
record.modality},
141 {
"study_uid",
record.study_uid},
142 {
"accession_no",
record.accession_no},
143 {
"scheduled_step_id",
record.scheduled_step_id},
144 {
"requested_proc_id",
record.requested_proc_id},
145 {
"performed_series",
record.performed_series}});
147 auto insert_result = db()->insert(builder.build());
148 if (insert_result.is_err()) {
149 return make_error<int64_t>(
151 kcenon::pacs::compat::format(
"Failed to create MPPS: {}",
152 insert_result.error().message),
156 auto inserted = find_mpps(
record.mpps_uid);
157 if (!inserted.has_value()) {
158 return make_error<int64_t>(
159 -1,
"MPPS inserted but could not retrieve record",
"storage");
165auto mpps_repository::update_mpps(std::string_view mpps_uid,
166 std::string_view new_status,
167 std::string_view end_datetime,
168 std::string_view performed_series)
170 if (new_status !=
"COMPLETED" && new_status !=
"DISCONTINUED" &&
171 new_status !=
"IN PROGRESS") {
172 return make_error<std::monostate>(
174 "Invalid status. Must be 'IN PROGRESS', 'COMPLETED', or "
179 auto current = find_mpps(mpps_uid);
180 if (!current.has_value()) {
181 return make_error<std::monostate>(-1,
"MPPS not found",
"storage");
184 if (current->status ==
"COMPLETED" || current->status ==
"DISCONTINUED") {
185 return make_error<std::monostate>(
187 kcenon::pacs::compat::format(
188 "Cannot update MPPS in final state '{}'. COMPLETED and "
189 "DISCONTINUED are final states.",
194 if (!db() || !db()->is_connected()) {
195 return make_error<std::monostate>(-1,
"Database not connected",
199 std::map<std::string, database::core::database_value> update_data{
200 {
"status", std::string(new_status)},
201 {
"updated_at", std::string(
"datetime('now')")}};
202 if (!end_datetime.empty()) {
203 update_data[
"end_datetime"] = std::string(end_datetime);
205 if (!performed_series.empty()) {
206 update_data[
"performed_series"] = std::string(performed_series);
209 auto builder = query_builder();
210 builder.update(
"mpps")
212 .where(
"mpps_uid",
"=", std::string(mpps_uid));
214 auto result = db()->update(builder.build());
215 if (result.is_err()) {
216 return make_error<std::monostate>(
218 kcenon::pacs::compat::format(
"Failed to update MPPS: {}",
219 result.error().message),
226auto mpps_repository::update_mpps(
const mpps_record& record) -> VoidResult {
227 if (
record.mpps_uid.empty()) {
228 return make_error<std::monostate>(-1,
"MPPS UID is required for update",
236auto mpps_repository::find_mpps(std::string_view mpps_uid)
237 -> std::optional<mpps_record> {
238 if (!db() || !db()->is_connected()) {
242 auto builder = query_builder();
243 auto query = builder.select(select_columns())
245 .where(
"mpps_uid",
"=", std::string(mpps_uid))
248 auto result = db()->select(query);
249 if (result.is_err() || result.value().empty()) {
253 return map_row_to_entity(result.value()[0]);
256auto mpps_repository::find_mpps_by_pk(int64_t pk) -> std::optional<mpps_record> {
257 auto result = find_by_id(pk);
258 if (result.is_err()) {
261 return result.value();
264auto mpps_repository::list_active_mpps(std::string_view station_ae)
265 -> Result<std::vector<mpps_record>> {
266 if (!db() || !db()->is_connected()) {
267 return make_error<std::vector<mpps_record>>(-1,
268 "Database not connected",
272 auto builder = query_builder();
273 auto query = builder.select(select_columns())
275 .where(
"status",
"=", std::string(
"IN PROGRESS"))
276 .where(
"station_ae",
"=", std::string(station_ae))
277 .order_by(
"start_datetime", database::sort_order::desc)
280 auto result = db()->select(query);
281 if (result.is_err()) {
282 return Result<std::vector<mpps_record>>::err(result.error());
285 std::vector<mpps_record> items;
286 items.reserve(result.value().size());
287 for (
const auto& row : result.value()) {
288 items.push_back(map_row_to_entity(row));
291 return ok(std::move(items));
294auto mpps_repository::find_mpps_by_study(std::string_view study_uid)
295 -> Result<std::vector<mpps_record>> {
296 if (!db() || !db()->is_connected()) {
297 return make_error<std::vector<mpps_record>>(-1,
298 "Database not connected",
302 auto builder = query_builder();
303 auto query = builder.select(select_columns())
305 .where(
"study_uid",
"=", std::string(study_uid))
306 .order_by(
"start_datetime", database::sort_order::desc)
309 auto result = db()->select(query);
310 if (result.is_err()) {
311 return Result<std::vector<mpps_record>>::err(result.error());
314 std::vector<mpps_record> items;
315 items.reserve(result.value().size());
316 for (
const auto& row : result.value()) {
317 items.push_back(map_row_to_entity(row));
320 return ok(std::move(items));
323auto mpps_repository::search_mpps(
const mpps_query& query)
324 -> Result<std::vector<mpps_record>> {
325 if (!db() || !db()->is_connected()) {
326 return make_error<std::vector<mpps_record>>(-1,
327 "Database not connected",
331 auto builder = query_builder();
332 builder.select(select_columns()).from(table_name());
334 if (
query.mpps_uid.has_value()) {
335 builder.where(
"mpps_uid",
"=", *
query.mpps_uid);
337 if (
query.status.has_value()) {
338 builder.where(
"status",
"=", *
query.status);
340 if (
query.station_ae.has_value()) {
341 builder.where(
"station_ae",
"=", *
query.station_ae);
343 if (
query.modality.has_value()) {
344 builder.where(
"modality",
"=", *
query.modality);
346 if (
query.study_uid.has_value()) {
347 builder.where(
"study_uid",
"=", *
query.study_uid);
349 if (
query.accession_no.has_value()) {
350 builder.where(
"accession_no",
"=", *
query.accession_no);
352 if (
query.start_date_from.has_value()) {
353 builder.where(
"substr(start_datetime, 1, 8)",
">=",
354 *
query.start_date_from);
356 if (
query.start_date_to.has_value()) {
357 builder.where(
"substr(start_datetime, 1, 8)",
"<=",
358 *
query.start_date_to);
361 builder.order_by(
"start_datetime", database::sort_order::desc);
363 if (
query.limit > 0) {
364 builder.limit(
query.limit);
366 if (
query.offset > 0) {
367 builder.offset(
query.offset);
370 auto result = db()->select(builder.build());
371 if (result.is_err()) {
372 return Result<std::vector<mpps_record>>::err(result.error());
375 std::vector<mpps_record> items;
376 items.reserve(result.value().size());
377 for (
const auto& row : result.value()) {
378 items.push_back(map_row_to_entity(row));
381 return ok(std::move(items));
384auto mpps_repository::delete_mpps(std::string_view mpps_uid) -> VoidResult {
385 if (!db() || !db()->is_connected()) {
386 return make_error<std::monostate>(-1,
"Database not connected",
390 auto builder = query_builder();
391 auto query = builder.delete_from(table_name())
392 .where(
"mpps_uid",
"=", std::string(mpps_uid))
395 auto result = db()->remove(query);
396 if (result.is_err()) {
397 return make_error<std::monostate>(
399 kcenon::pacs::compat::format(
"Failed to delete MPPS: {}",
400 result.error().message),
407auto mpps_repository::mpps_count() -> Result<size_t> {
411auto mpps_repository::mpps_count(std::string_view status) -> Result<size_t> {
412 if (!db() || !db()->is_connected()) {
413 return make_error<size_t>(-1,
"Database not connected",
"storage");
416 auto query = kcenon::pacs::compat::format(
417 "SELECT COUNT(*) as count FROM {} WHERE status = '{}'",
418 table_name(), std::string(status));
419 auto result = db()->select(query);
420 if (result.is_err()) {
421 return Result<size_t>::err(result.error());
424 if (result.value().empty()) {
425 return ok(
size_t{0});
429 return ok(
static_cast<size_t>(
430 std::stoull(result.value()[0].at(
"count"))));
431 }
catch (
const std::exception& e) {
432 return make_error<size_t>(
434 kcenon::pacs::compat::format(
"Failed to parse MPPS count: {}", e.what()),
439auto mpps_repository::map_row_to_entity(
const database_row& row)
const
442 record.pk = get_int64(row,
"mpps_pk");
443 record.mpps_uid = get_string(row,
"mpps_uid");
444 record.status = get_string(row,
"status");
445 record.start_datetime = get_string(row,
"start_datetime");
446 record.end_datetime = get_string(row,
"end_datetime");
447 record.station_ae = get_string(row,
"station_ae");
448 record.station_name = get_string(row,
"station_name");
449 record.modality = get_string(row,
"modality");
450 record.study_uid = get_string(row,
"study_uid");
451 record.accession_no = get_string(row,
"accession_no");
452 record.scheduled_step_id = get_string(row,
"scheduled_step_id");
453 record.requested_proc_id = get_string(row,
"requested_proc_id");
454 record.performed_series = get_string(row,
"performed_series");
456 auto created_at = get_string(row,
"created_at");
457 if (!created_at.empty()) {
458 record.created_at = parse_timestamp(created_at);
461 auto updated_at = get_string(row,
"updated_at");
462 if (!updated_at.empty()) {
463 record.updated_at = parse_timestamp(updated_at);
469auto mpps_repository::entity_to_row(
const mpps_record& entity)
const
470 -> std::map<std::string, database_value> {
472 {
"mpps_uid", entity.mpps_uid},
473 {
"status", entity.status},
474 {
"start_datetime", entity.start_datetime},
475 {
"end_datetime", entity.end_datetime},
476 {
"station_ae", entity.station_ae},
477 {
"station_name", entity.station_name},
478 {
"modality", entity.modality},
479 {
"study_uid", entity.study_uid},
480 {
"accession_no", entity.accession_no},
481 {
"scheduled_step_id", entity.scheduled_step_id},
482 {
"requested_proc_id", entity.requested_proc_id},
483 {
"performed_series", entity.performed_series},
484 {
"created_at", format_timestamp(entity.created_at)},
485 {
"updated_at", format_timestamp(entity.updated_at)},
489auto mpps_repository::get_pk(
const mpps_record& entity)
const -> int64_t {
493auto mpps_repository::has_pk(
const mpps_record& entity)
const ->
bool {
494 return entity.pk > 0;
497auto mpps_repository::select_columns() const -> std::vector<std::
string> {
498 return {
"mpps_pk",
"mpps_uid",
"status",
499 "start_datetime",
"end_datetime",
"station_ae",
500 "station_name",
"modality",
"study_uid",
501 "accession_no",
"scheduled_step_id",
"requested_proc_id",
502 "performed_series",
"created_at",
"updated_at"};
513using kcenon::common::make_error;
514using kcenon::common::ok;
518auto parse_datetime(
const char* str) -> std::chrono::system_clock::time_point {
519 if (!str || *str ==
'\0') {
520 return std::chrono::system_clock::time_point{};
524 std::istringstream ss(str);
525 ss >> std::get_time(&tm,
"%Y-%m-%d %H:%M:%S");
527 return std::chrono::system_clock::time_point{};
530 return std::chrono::system_clock::from_time_t(std::mktime(&tm));
533auto get_text(sqlite3_stmt* stmt,
int col) -> std::string {
535 reinterpret_cast<const char*
>(sqlite3_column_text(stmt, col));
536 return text ? std::string(
text) : std::string{};
552 if (
this != &other) {
560 -> std::chrono::system_clock::time_point {
561 return parse_datetime(str.c_str());
565 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
568 record.pk = sqlite3_column_int64(stmt, 0);
569 record.mpps_uid = get_text(stmt, 1);
570 record.status = get_text(stmt, 2);
571 record.start_datetime = get_text(stmt, 3);
572 record.end_datetime = get_text(stmt, 4);
573 record.station_ae = get_text(stmt, 5);
574 record.station_name = get_text(stmt, 6);
575 record.modality = get_text(stmt, 7);
576 record.study_uid = get_text(stmt, 8);
577 record.accession_no = get_text(stmt, 9);
578 record.scheduled_step_id = get_text(stmt, 10);
579 record.requested_proc_id = get_text(stmt, 11);
580 record.performed_series = get_text(stmt, 12);
581 record.created_at = parse_datetime(get_text(stmt, 13).c_str());
582 record.updated_at = parse_datetime(get_text(stmt, 14).c_str());
588 std::string_view station_ae,
589 std::string_view modality,
590 std::string_view study_uid,
591 std::string_view accession_no,
592 std::string_view start_datetime)
595 record.mpps_uid = std::string(mpps_uid);
596 record.station_ae = std::string(station_ae);
597 record.modality = std::string(modality);
598 record.study_uid = std::string(study_uid);
599 record.accession_no = std::string(accession_no);
600 record.start_datetime = std::string(start_datetime);
601 record.status =
"IN PROGRESS";
602 return create_mpps(record);
606 if (record.mpps_uid.empty()) {
607 return make_error<int64_t>(-1,
"MPPS SOP Instance UID is required",
611 if (!record.status.empty() && record.status !=
"IN PROGRESS") {
612 return make_error<int64_t>(
613 -1,
"N-CREATE must create MPPS with status 'IN PROGRESS'",
617 const char* sql = R
"(
619 mpps_uid, status, start_datetime, station_ae, station_name,
620 modality, study_uid, accession_no, scheduled_step_id,
621 requested_proc_id, performed_series, updated_at
622 ) VALUES (?, 'IN PROGRESS', ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
626 sqlite3_stmt* stmt = nullptr;
627 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt,
nullptr);
628 if (rc != SQLITE_OK) {
629 return make_error<int64_t>(
631 kcenon::pacs::compat::format(
"Failed to prepare statement: {}",
632 sqlite3_errmsg(db_)),
636 sqlite3_bind_text(stmt, 1, record.mpps_uid.c_str(), -1, SQLITE_TRANSIENT);
637 sqlite3_bind_text(stmt, 2, record.start_datetime.c_str(), -1,
639 sqlite3_bind_text(stmt, 3, record.station_ae.c_str(), -1, SQLITE_TRANSIENT);
640 sqlite3_bind_text(stmt, 4, record.station_name.c_str(), -1,
642 sqlite3_bind_text(stmt, 5, record.modality.c_str(), -1, SQLITE_TRANSIENT);
643 sqlite3_bind_text(stmt, 6, record.study_uid.c_str(), -1, SQLITE_TRANSIENT);
644 sqlite3_bind_text(stmt, 7, record.accession_no.c_str(), -1,
646 sqlite3_bind_text(stmt, 8, record.scheduled_step_id.c_str(), -1,
648 sqlite3_bind_text(stmt, 9, record.requested_proc_id.c_str(), -1,
650 sqlite3_bind_text(stmt, 10, record.performed_series.c_str(), -1,
653 rc = sqlite3_step(stmt);
654 if (rc != SQLITE_ROW) {
655 auto error_msg = sqlite3_errmsg(db_);
656 sqlite3_finalize(stmt);
657 return make_error<int64_t>(
658 rc, kcenon::pacs::compat::format(
"Failed to create MPPS: {}", error_msg),
662 auto pk = sqlite3_column_int64(stmt, 0);
663 sqlite3_finalize(stmt);
668 std::string_view new_status,
669 std::string_view end_datetime,
670 std::string_view performed_series)
672 if (new_status !=
"COMPLETED" && new_status !=
"DISCONTINUED" &&
673 new_status !=
"IN PROGRESS") {
674 return make_error<std::monostate>(
676 "Invalid status. Must be 'IN PROGRESS', 'COMPLETED', or "
681 auto current = find_mpps(mpps_uid);
682 if (!current.has_value()) {
683 return make_error<std::monostate>(-1,
"MPPS not found",
"storage");
686 if (current->status ==
"COMPLETED" || current->status ==
"DISCONTINUED") {
687 return make_error<std::monostate>(
689 kcenon::pacs::compat::format(
690 "Cannot update MPPS in final state '{}'. COMPLETED and "
691 "DISCONTINUED are final states.",
696 const char* sql = R
"(
699 end_datetime = CASE WHEN ? != '' THEN ? ELSE end_datetime END,
700 performed_series = CASE WHEN ? != '' THEN ? ELSE performed_series END,
701 updated_at = datetime('now')
705 sqlite3_stmt* stmt = nullptr;
706 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt,
nullptr);
707 if (rc != SQLITE_OK) {
708 return make_error<std::monostate>(
710 kcenon::pacs::compat::format(
"Failed to prepare statement: {}",
711 sqlite3_errmsg(db_)),
715 sqlite3_bind_text(stmt, 1, new_status.data(),
716 static_cast<int>(new_status.size()), SQLITE_TRANSIENT);
717 sqlite3_bind_text(stmt, 2, end_datetime.data(),
718 static_cast<int>(end_datetime.size()), SQLITE_TRANSIENT);
719 sqlite3_bind_text(stmt, 3, end_datetime.data(),
720 static_cast<int>(end_datetime.size()), SQLITE_TRANSIENT);
721 sqlite3_bind_text(stmt, 4, performed_series.data(),
722 static_cast<int>(performed_series.size()),
724 sqlite3_bind_text(stmt, 5, performed_series.data(),
725 static_cast<int>(performed_series.size()),
727 sqlite3_bind_text(stmt, 6, mpps_uid.data(),
728 static_cast<int>(mpps_uid.size()), SQLITE_TRANSIENT);
730 rc = sqlite3_step(stmt);
731 sqlite3_finalize(stmt);
733 if (rc != SQLITE_DONE) {
734 return make_error<std::monostate>(
736 kcenon::pacs::compat::format(
"Failed to update MPPS: {}",
737 sqlite3_errmsg(db_)),
745 if (record.mpps_uid.empty()) {
746 return make_error<std::monostate>(-1,
"MPPS UID is required for update",
750 return update_mpps(record.mpps_uid, record.status, record.end_datetime,
751 record.performed_series);
755 -> std::optional<mpps_record> {
756 const char* sql = R
"(
757 SELECT mpps_pk, mpps_uid, status, start_datetime, end_datetime,
758 station_ae, station_name, modality, study_uid, accession_no,
759 scheduled_step_id, requested_proc_id, performed_series,
760 created_at, updated_at
765 sqlite3_stmt* stmt = nullptr;
766 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt,
nullptr);
767 if (rc != SQLITE_OK) {
771 sqlite3_bind_text(stmt, 1, mpps_uid.data(),
772 static_cast<int>(mpps_uid.size()), SQLITE_TRANSIENT);
774 rc = sqlite3_step(stmt);
775 if (rc != SQLITE_ROW) {
776 sqlite3_finalize(stmt);
780 auto record = parse_mpps_row(stmt);
781 sqlite3_finalize(stmt);
786 -> std::optional<mpps_record> {
787 const char* sql = R
"(
788 SELECT mpps_pk, mpps_uid, status, start_datetime, end_datetime,
789 station_ae, station_name, modality, study_uid, accession_no,
790 scheduled_step_id, requested_proc_id, performed_series,
791 created_at, updated_at
796 sqlite3_stmt* stmt = nullptr;
797 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt,
nullptr);
798 if (rc != SQLITE_OK) {
802 sqlite3_bind_int64(stmt, 1, pk);
804 rc = sqlite3_step(stmt);
805 if (rc != SQLITE_ROW) {
806 sqlite3_finalize(stmt);
810 auto record = parse_mpps_row(stmt);
811 sqlite3_finalize(stmt);
817 std::vector<mpps_record> results;
819 const char* sql = R
"(
820 SELECT mpps_pk, mpps_uid, status, start_datetime, end_datetime,
821 station_ae, station_name, modality, study_uid, accession_no,
822 scheduled_step_id, requested_proc_id, performed_series,
823 created_at, updated_at
825 WHERE status = 'IN PROGRESS' AND station_ae = ?
826 ORDER BY start_datetime DESC;
829 sqlite3_stmt* stmt = nullptr;
830 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt,
nullptr);
831 if (rc != SQLITE_OK) {
832 return make_error<std::vector<mpps_record>>(
834 kcenon::pacs::compat::format(
"Failed to prepare query: {}",
835 sqlite3_errmsg(db_)),
839 sqlite3_bind_text(stmt, 1, station_ae.data(),
840 static_cast<int>(station_ae.size()), SQLITE_TRANSIENT);
842 while (sqlite3_step(stmt) == SQLITE_ROW) {
843 results.push_back(parse_mpps_row(stmt));
846 sqlite3_finalize(stmt);
847 return ok(std::move(results));
852 std::vector<mpps_record> results;
854 const char* sql = R
"(
855 SELECT mpps_pk, mpps_uid, status, start_datetime, end_datetime,
856 station_ae, station_name, modality, study_uid, accession_no,
857 scheduled_step_id, requested_proc_id, performed_series,
858 created_at, updated_at
861 ORDER BY start_datetime DESC;
864 sqlite3_stmt* stmt = nullptr;
865 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt,
nullptr);
866 if (rc != SQLITE_OK) {
867 return make_error<std::vector<mpps_record>>(
869 kcenon::pacs::compat::format(
"Failed to prepare query: {}",
870 sqlite3_errmsg(db_)),
874 sqlite3_bind_text(stmt, 1, study_uid.data(),
875 static_cast<int>(study_uid.size()), SQLITE_TRANSIENT);
877 while (sqlite3_step(stmt) == SQLITE_ROW) {
878 results.push_back(parse_mpps_row(stmt));
881 sqlite3_finalize(stmt);
882 return ok(std::move(results));
887 std::vector<mpps_record> results;
888 std::string sql = R
"(
889 SELECT mpps_pk, mpps_uid, status, start_datetime, end_datetime,
890 station_ae, station_name, modality, study_uid, accession_no,
891 scheduled_step_id, requested_proc_id, performed_series,
892 created_at, updated_at
896 std::vector<std::string> params;
898 if (query.mpps_uid.has_value()) {
899 sql +=
" AND mpps_uid = ?";
900 params.push_back(*query.mpps_uid);
902 if (query.status.has_value()) {
903 sql +=
" AND status = ?";
904 params.push_back(*query.status);
906 if (query.station_ae.has_value()) {
907 sql +=
" AND station_ae = ?";
908 params.push_back(*query.station_ae);
910 if (query.modality.has_value()) {
911 sql +=
" AND modality = ?";
912 params.push_back(*query.modality);
914 if (query.study_uid.has_value()) {
915 sql +=
" AND study_uid = ?";
916 params.push_back(*query.study_uid);
918 if (query.accession_no.has_value()) {
919 sql +=
" AND accession_no = ?";
920 params.push_back(*query.accession_no);
922 if (query.start_date_from.has_value()) {
923 sql +=
" AND substr(start_datetime, 1, 8) >= ?";
924 params.push_back(*query.start_date_from);
926 if (query.start_date_to.has_value()) {
927 sql +=
" AND substr(start_datetime, 1, 8) <= ?";
928 params.push_back(*query.start_date_to);
931 sql +=
" ORDER BY start_datetime DESC";
933 if (query.limit > 0) {
934 sql += kcenon::pacs::compat::format(
" LIMIT {}", query.limit);
936 if (query.offset > 0) {
937 sql += kcenon::pacs::compat::format(
" OFFSET {}", query.offset);
940 sqlite3_stmt* stmt =
nullptr;
941 auto rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt,
nullptr);
942 if (rc != SQLITE_OK) {
943 return make_error<std::vector<mpps_record>>(
945 kcenon::pacs::compat::format(
"Failed to prepare query: {}",
946 sqlite3_errmsg(db_)),
950 for (
size_t i = 0; i < params.size(); ++i) {
951 sqlite3_bind_text(stmt,
static_cast<int>(i + 1), params[i].c_str(), -1,
955 while (sqlite3_step(stmt) == SQLITE_ROW) {
956 results.push_back(parse_mpps_row(stmt));
959 sqlite3_finalize(stmt);
960 return ok(std::move(results));
964 const char* sql =
"DELETE FROM mpps WHERE mpps_uid = ?;";
966 sqlite3_stmt* stmt =
nullptr;
967 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt,
nullptr);
968 if (rc != SQLITE_OK) {
969 return make_error<std::monostate>(
971 kcenon::pacs::compat::format(
"Failed to prepare delete: {}",
972 sqlite3_errmsg(db_)),
976 sqlite3_bind_text(stmt, 1, mpps_uid.data(),
977 static_cast<int>(mpps_uid.size()), SQLITE_TRANSIENT);
979 rc = sqlite3_step(stmt);
980 sqlite3_finalize(stmt);
982 if (rc != SQLITE_DONE) {
983 return make_error<std::monostate>(
985 kcenon::pacs::compat::format(
"Failed to delete MPPS: {}",
986 sqlite3_errmsg(db_)),
994 const char* sql =
"SELECT COUNT(*) FROM mpps;";
995 sqlite3_stmt* stmt =
nullptr;
996 auto rc = sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr);
997 if (rc != SQLITE_OK) {
998 return make_error<size_t>(
1000 kcenon::pacs::compat::format(
"Failed to prepare query: {}",
1001 sqlite3_errmsg(
db_)),
1006 if (sqlite3_step(stmt) == SQLITE_ROW) {
1007 count =
static_cast<size_t>(sqlite3_column_int64(stmt, 0));
1009 sqlite3_finalize(stmt);
1015 const char* sql =
"SELECT COUNT(*) FROM mpps WHERE status = ?;";
1016 sqlite3_stmt* stmt =
nullptr;
1017 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt,
nullptr);
1018 if (rc != SQLITE_OK) {
1019 return make_error<size_t>(
1021 kcenon::pacs::compat::format(
"Failed to prepare query: {}",
1022 sqlite3_errmsg(db_)),
1026 sqlite3_bind_text(stmt, 1, status.data(),
static_cast<int>(status.size()),
1030 if (sqlite3_step(stmt) == SQLITE_ROW) {
1031 count =
static_cast<size_t>(sqlite3_column_int64(stmt, 0));
1033 sqlite3_finalize(stmt);
static auto parse_timestamp(const std::string &str) -> std::chrono::system_clock::time_point
auto find_mpps(std::string_view mpps_uid) const -> std::optional< mpps_record >
auto operator=(const mpps_repository &) -> mpps_repository &=delete
auto parse_mpps_row(void *stmt) const -> mpps_record
auto find_mpps_by_pk(int64_t pk) const -> std::optional< mpps_record >
auto update_mpps(std::string_view mpps_uid, std::string_view new_status, std::string_view end_datetime="", std::string_view performed_series="") -> VoidResult
auto create_mpps(std::string_view mpps_uid, std::string_view station_ae="", std::string_view modality="", std::string_view study_uid="", std::string_view accession_no="", std::string_view start_datetime="") -> Result< int64_t >
auto search_mpps(const mpps_query &query) const -> Result< std::vector< mpps_record > >
auto mpps_count() const -> Result< size_t >
auto delete_mpps(std::string_view mpps_uid) -> VoidResult
mpps_repository(sqlite3 *db)
auto find_mpps_by_study(std::string_view study_uid) const -> Result< std::vector< mpps_record > >
auto list_active_mpps(std::string_view station_ae) const -> Result< std::vector< mpps_record > >
Repository for MPPS lifecycle persistence using base_repository pattern.
@ move
C-MOVE move request/response.
const atna_coded_value query
Query (110112)
@ record
RECORD - Treatment record dose.
MPPS record from the database.