penambahan All case Satu sehat

This commit is contained in:
meninjar
2026-05-04 03:48:43 +00:00
parent 135c631021
commit af32d9cfdd
127 changed files with 617690 additions and 1160 deletions
+19 -12
View File
@@ -1,19 +1,26 @@
package procedure
import "time"
import (
"time"
"service/internal/satusehat/common"
)
type ProcedureRequest struct {
PatientID string `json:"patient_id" binding:"required"`
PatientName string `json:"patient_name" binding:"required"`
EncounterID string `json:"encounter_id" binding:"required"`
Status string `json:"status" binding:"required,oneof=preparation in-progress not-done on-hold stopped completed entered-in-error unknown"` // preparation, in-progress, not-done, on-hold, stopped, completed, entered-in-error, unknown
CategoryCode string `json:"category_code" binding:"required"`
CategoryDisplay string `json:"category_display" binding:"required"`
Code string `json:"code" binding:"required"` // SNOMED-CT or ICD9CM code
Display string `json:"display" binding:"required"` // Procedure name
PractitionerID string `json:"practitioner_id" binding:"required"`
PractitionerName string `json:"practitioner_name" binding:"required"`
PerformedDateTime time.Time `json:"performed_date_time" binding:"required"`
OrganizationID string `json:"organization_id,omitempty"`
ProcedureID string `json:"procedure_id,omitempty"`
Status string `json:"status" binding:"required,oneof=preparation in-progress not-done on-hold stopped completed entered-in-error unknown"`
Category []*common.CodeableConceptDTO `json:"category,omitempty"`
Code *common.CodeableConceptDTO `json:"code" binding:"required"`
Subject *common.ReferenceDTO `json:"subject" binding:"required"`
Encounter *common.ReferenceDTO `json:"encounter,omitempty"`
PerformedDateTime *time.Time `json:"performed_date_time,omitempty"`
PerformedStart *time.Time `json:"performed_start,omitempty"`
PerformedEnd *time.Time `json:"performed_end,omitempty"`
Performer []common.ReferenceDTO `json:"performer,omitempty"`
ReasonCode []*common.CodeableConceptDTO `json:"reason_code,omitempty"`
BodySite []*common.CodeableConceptDTO `json:"body_site,omitempty"`
Note []string `json:"note,omitempty"`
}
type ProcedurePatchRequest []map[string]interface{}
+130 -33
View File
@@ -1,49 +1,146 @@
package procedure
import (
"os"
"time"
"service/internal/interfaces/satusehat"
)
func MapRequestToFHIR(req ProcedureRequest) satusehat.FHIRPayload {
payload := satusehat.NewFHIRPayload("Procedure").
Set("status", req.Status).
Set("category", map[string]interface{}{
"coding": []map[string]interface{}{
{
"system": "http://snomed.info/sct",
"code": req.CategoryCode,
"display": req.CategoryDisplay,
},
},
}).
Set("code", map[string]interface{}{
"coding": []map[string]interface{}{
{
"system": "http://snomed.info/sct",
"code": req.Code,
"display": req.Display,
},
},
}).
Set("subject", map[string]interface{}{
"reference": "Patient/" + req.PatientID,
"display": req.PatientName,
}).
Set("encounter", map[string]interface{}{
"reference": "Encounter/" + req.EncounterID,
}).
Set("performedDateTime", req.PerformedDateTime.Format(time.RFC3339))
payload := satusehat.NewFHIRPayload("Procedure")
if req.PractitionerID != "" {
payload.Append("performer", map[string]interface{}{
"actor": map[string]interface{}{
"reference": "Practitioner/" + req.PractitionerID,
"display": req.PractitionerName,
orgID := os.Getenv("SATUSEHAT_ORG_ID")
if orgID == "" {
orgID = req.OrganizationID
}
if orgID != "" && req.ProcedureID != "" {
payload.Set("identifier", []map[string]interface{}{
{
"system": "http://sys-ids.kemkes.go.id/procedure/" + orgID,
"value": req.ProcedureID,
},
})
}
if req.Status != "" {
payload.Set("status", req.Status)
}
if len(req.Category) > 0 && req.Category[0] != nil {
sys := req.Category[0].System
if sys == "" {
sys = "http://snomed.info/sct"
}
coding := map[string]interface{}{"system": sys, "code": req.Category[0].Code}
if req.Category[0].Display != "" {
coding["display"] = req.Category[0].Display
}
catObj := map[string]interface{}{"coding": []map[string]interface{}{coding}}
if req.Category[0].Text != "" {
catObj["text"] = req.Category[0].Text
}
payload.Set("category", catObj)
}
if req.Code != nil {
sys := req.Code.System
if sys == "" {
sys = "http://snomed.info/sct"
}
coding := map[string]interface{}{"system": sys, "code": req.Code.Code}
if req.Code.Display != "" {
coding["display"] = req.Code.Display
}
codeObj := map[string]interface{}{"coding": []map[string]interface{}{coding}}
if req.Code.Text != "" {
codeObj["text"] = req.Code.Text
}
payload.Set("code", codeObj)
}
if req.Subject != nil {
subj := map[string]interface{}{"reference": req.Subject.Reference}
if req.Subject.Display != "" {
subj["display"] = req.Subject.Display
}
payload.Set("subject", subj)
}
if req.Encounter != nil {
enc := map[string]interface{}{"reference": req.Encounter.Reference}
if req.Encounter.Display != "" {
enc["display"] = req.Encounter.Display
}
payload.Set("encounter", enc)
}
if req.PerformedStart != nil && !req.PerformedStart.IsZero() {
period := map[string]interface{}{"start": req.PerformedStart.Format(time.RFC3339)}
if req.PerformedEnd != nil && !req.PerformedEnd.IsZero() {
period["end"] = req.PerformedEnd.Format(time.RFC3339)
}
payload.Set("performedPeriod", period)
} else if req.PerformedDateTime != nil && !req.PerformedDateTime.IsZero() {
payload.Set("performedDateTime", req.PerformedDateTime.Format(time.RFC3339))
}
if len(req.Performer) > 0 {
var performers []map[string]interface{}
for _, p := range req.Performer {
actor := map[string]interface{}{"reference": p.Reference}
if p.Display != "" {
actor["display"] = p.Display
}
performers = append(performers, map[string]interface{}{"actor": actor})
}
payload.Set("performer", performers)
}
if len(req.ReasonCode) > 0 {
var reasons []map[string]interface{}
for _, r := range req.ReasonCode {
if r != nil {
sys := r.System
if sys == "" {
sys = "http://hl7.org/fhir/sid/icd-10"
}
coding := map[string]interface{}{"system": sys, "code": r.Code}
if r.Display != "" {
coding["display"] = r.Display
}
reasons = append(reasons, map[string]interface{}{"coding": []map[string]interface{}{coding}})
}
}
payload.Set("reasonCode", reasons)
}
if len(req.BodySite) > 0 {
var bodySites []map[string]interface{}
for _, b := range req.BodySite {
if b != nil {
sys := b.System
if sys == "" {
sys = "http://snomed.info/sct"
}
coding := map[string]interface{}{"system": sys, "code": b.Code}
if b.Display != "" {
coding["display"] = b.Display
}
bodySites = append(bodySites, map[string]interface{}{"coding": []map[string]interface{}{coding}})
}
}
payload.Set("bodySite", bodySites)
}
if len(req.Note) > 0 {
var notes []map[string]interface{}
for _, text := range req.Note {
notes = append(notes, map[string]interface{}{"text": text})
}
payload.Set("note", notes)
}
return payload
}
@@ -6,6 +6,7 @@ import (
"fmt"
"net/url"
"service/internal/interfaces/satusehat"
"service/pkg/errors"
)
type Repository interface {
@@ -24,15 +25,23 @@ func NewRepository(client satusehat.SatuSehatClient) Repository {
return &repository{client: client}
}
type hiddenCtx struct {
context.Context
}
func (r *repository) executeRequest(ctx context.Context, method, endpoint string, req interface{}) (*satusehat.FHIRResponse, error) {
resp, err := r.client.DoRequest(ctx, method, endpoint, req)
resp, err := r.client.DoRequest(hiddenCtx{ctx}, method, endpoint, req)
if err != nil {
return nil, err
return nil, errors.InternalError().Message("Failed to execute request to SatuSehat").Cause(err).Build()
}
var result map[string]interface{}
if err := json.Unmarshal(resp, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
return nil, errors.InternalError().Message("Failed to parse SatuSehat response").Cause(err).Metadata("raw_response", string(resp)).Build()
}
if resourceType, ok := result["resourceType"].(string); ok && resourceType == "OperationOutcome" {
return nil, errors.ParseSatuSehatError(result)
}
var resourceID string
@@ -40,11 +49,7 @@ func (r *repository) executeRequest(ctx context.Context, method, endpoint string
resourceID = id
}
return &satusehat.FHIRResponse{
ID: resourceID,
FullResponse: result,
RawResponse: resp,
}, nil
return &satusehat.FHIRResponse{ID: resourceID, FullResponse: result, RawResponse: resp}, nil
}
func (r *repository) Create(ctx context.Context, payload interface{}) (*satusehat.FHIRResponse, error) {
@@ -52,21 +57,17 @@ func (r *repository) Create(ctx context.Context, payload interface{}) (*satuseha
}
func (r *repository) Update(ctx context.Context, id string, payload interface{}) (*satusehat.FHIRResponse, error) {
endpoint := fmt.Sprintf("/Procedure/%s", id)
return r.executeRequest(ctx, "PUT", endpoint, payload)
return r.executeRequest(ctx, "PUT", fmt.Sprintf("/Procedure/%s", id), payload)
}
func (r *repository) Patch(ctx context.Context, id string, payload ProcedurePatchRequest) (*satusehat.FHIRResponse, error) {
endpoint := fmt.Sprintf("/Procedure/%s", id)
return r.executeRequest(ctx, "PATCH", endpoint, payload)
return r.executeRequest(ctx, "PATCH", fmt.Sprintf("/Procedure/%s", id), payload)
}
func (r *repository) GetByID(ctx context.Context, id string) (*satusehat.FHIRResponse, error) {
endpoint := fmt.Sprintf("/Procedure/%s", id)
return r.executeRequest(ctx, "GET", endpoint, nil)
return r.executeRequest(ctx, "GET", fmt.Sprintf("/Procedure/%s", id), nil)
}
func (r *repository) Search(ctx context.Context, queryParams url.Values) (*satusehat.FHIRResponse, error) {
endpoint := fmt.Sprintf("/Procedure?%s", queryParams.Encode())
return r.executeRequest(ctx, "GET", endpoint, nil)
return r.executeRequest(ctx, "GET", fmt.Sprintf("/Procedure?%s", queryParams.Encode()), nil)
}
@@ -24,19 +24,16 @@ func NewService(repo Repository) Service {
}
func (s *service) Create(ctx context.Context, req ProcedureRequest) (*satusehat.FHIRResponse, error) {
fhirPayload := MapRequestToFHIR(req)
return s.repo.Create(ctx, fhirPayload)
return s.repo.Create(ctx, MapRequestToFHIR(req))
}
func (s *service) Update(ctx context.Context, id string, req ProcedureRequest) (*satusehat.FHIRResponse, error) {
if id == "" {
return nil, errors.NewValidationError().Message("Procedure ID is required").Build()
}
fhirPayload := MapRequestToFHIR(req)
fhirPayload.Set("id", id)
return s.repo.Update(ctx, id, fhirPayload)
payload := MapRequestToFHIR(req)
payload.Set("id", id)
return s.repo.Update(ctx, id, payload)
}
func (s *service) Patch(ctx context.Context, id string, req ProcedurePatchRequest) (*satusehat.FHIRResponse, error) {