diff --git a/cmd/main-migration/Makefile b/cmd/main-migration/Makefile index f445a815..a93bb244 100644 --- a/cmd/main-migration/Makefile +++ b/cmd/main-migration/Makefile @@ -16,3 +16,7 @@ apply: ## Calculate the schema hash hash: atlas migrate hash + +## Apply non-linear +apply-non-linear: + atlas migrate apply --env $(ENV) --exec-order non-linear \ No newline at end of file diff --git a/cmd/main-migration/migrations/20251113101344.sql b/cmd/main-migration/migrations/20251113101344.sql new file mode 100644 index 00000000..cda3abed --- /dev/null +++ b/cmd/main-migration/migrations/20251113101344.sql @@ -0,0 +1,11 @@ +-- Create "MedicineForm" table +CREATE TABLE "public"."MedicineForm" ( + "Id" serial NOT NULL, + "CreatedAt" timestamptz NULL, + "UpdatedAt" timestamptz NULL, + "DeletedAt" timestamptz NULL, + "Code" character varying(20) NULL, + "Name" character varying(50) NULL, + PRIMARY KEY ("Id"), + CONSTRAINT "uni_MedicineForm_Code" UNIQUE ("Code") +); diff --git a/cmd/main-migration/migrations/20251113120533.sql b/cmd/main-migration/migrations/20251113120533.sql new file mode 100644 index 00000000..b8d56fe8 --- /dev/null +++ b/cmd/main-migration/migrations/20251113120533.sql @@ -0,0 +1,2 @@ +-- Modify "Medicine" table +ALTER TABLE "public"."Medicine" ADD COLUMN "MedicineForm_Code" character varying(20) NULL, ADD CONSTRAINT "fk_Medicine_MedicineForm" FOREIGN KEY ("MedicineForm_Code") REFERENCES "public"."MedicineForm" ("Code") ON UPDATE NO ACTION ON DELETE NO ACTION; diff --git a/cmd/main-migration/migrations/20251114062746.sql b/cmd/main-migration/migrations/20251114062746.sql new file mode 100644 index 00000000..cdcfb350 --- /dev/null +++ b/cmd/main-migration/migrations/20251114062746.sql @@ -0,0 +1,2 @@ +-- Modify "TherapyProtocol" table +ALTER TABLE "public"."TherapyProtocol" ADD COLUMN "Status_Code" character varying(10) NULL; diff --git a/cmd/main-migration/migrations/atlas.sum b/cmd/main-migration/migrations/atlas.sum index f59b4131..d2484ac6 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,5 +1,4 @@ -h1:3V3a/T/te8iQqsolgRAJKr99GePHgSN9miJHUNngJ74= -h1:IAvX8SEd9TnVqJ7EYac7IGQ2CGS+5RhltXZ10G5+w7s= +h1:ZxTxK8LgNDXKTGBTF9olozXM0T7DDjK1+OJiwUwomeY= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -109,9 +108,13 @@ h1:IAvX8SEd9TnVqJ7EYac7IGQ2CGS+5RhltXZ10G5+w7s= 20251110093522.sql h1:nsz8jCxGjEdr/bz9g+4ozfZzIP803xONjVmucad1GMc= 20251110100258.sql h1:IBqt1VZj5WjQ+l9aAFGHOCCBtzb03KlLLihFLut7itg= 20251110100545.sql h1:6/LV7751iyKxE2xI6vO1zly+aHUwxXD/IBwLcVpKxqM= -20251111072601.sql h1:dEhwrkT0hJ06/YcvQd5alvdskimcHcYT27QKAzVY5+8= -20251111073546.sql h1:JnJZ4SdOObSe6Jf8v/i/KiRxoCz5KMeXCYytQMZgkZM= -20251111074148.sql h1:95Ui1eo1P68itOz5kZDNFi2ha0ZhUF4gMYiYVcip6fo= -20251111074652.sql h1:vUZbN0qgktRQ2GAlCpdYrbld2grPiSbvcMePEQMfxPs= -20251111082257.sql h1:Zr3Xg5n+p4C8F6Evqm5PVC0pqUvPTBcq692PiUEJlT8= -20251111111017.sql h1:8Kr6FFtkSeaenB/n/aKlOaz/K33SqazxMuH26oI0JI4= +20251110155448.sql h1:kFPobJB+cpflsXBAWUwy3lohuWvrb/VRlXnhJWl7i3Y= +20251111072601.sql h1:ch8F+yVhsSM5xY+TwMLY3PxdLa4Wuhtj76oyw79R7Js= +20251111073546.sql h1:cCv0NPscADAOBahRVqtDWFs6G2t7n+4a+RwlF8vk/c4= +20251111074148.sql h1:70TsV83u1gQ5TktI13K7NQiyCCa35Td2aR6CNtKUa4U= +20251111074652.sql h1:ddfQ/sRKMezPM75xBFTGytUQX5AwZ3znrJVpg73gKPA= +20251111082257.sql h1:ZsdLY1ROouos0l3oS0lkeSiuKLEUGbVvBhpcM2AVhkw= +20251111111017.sql h1:qrJ93dNtQwcuAvpsP/lAK/H63C4cinXrsVaPmWsTqkU= +20251113101344.sql h1:xaOZvAUP1fFfnO+syEFOzJUIg5lTfBe5AWHPbBWuCLA= +20251113120533.sql h1:f3/U1Ve2yF2zSMhkt+xtwF8wUYfUKYwgbNeGfE37EW4= +20251114062746.sql h1:4ypWL0cP2AUJLVHhPKo3GAQ7uqolL6F8o4nUW32KnCI= diff --git a/cmd/simgos-sync-migration/Makefile b/cmd/simgos-sync-migration/Makefile new file mode 100644 index 00000000..e83356e5 --- /dev/null +++ b/cmd/simgos-sync-migration/Makefile @@ -0,0 +1,18 @@ +# Makefile for Atlas migrations + +# Default environment +ENV ?= gorm + +.PHONY: diff apply hash + +## Generate a new migration diff +diff: + atlas migrate diff --env $(ENV) + +## Apply migrations to the database +apply: + atlas migrate apply --env $(ENV) + +## Calculate the schema hash +hash: + atlas migrate hash diff --git a/cmd/simgos-sync-migration/README-ATLAS.MD b/cmd/simgos-sync-migration/README-ATLAS.MD new file mode 100644 index 00000000..da249823 --- /dev/null +++ b/cmd/simgos-sync-migration/README-ATLAS.MD @@ -0,0 +1,59 @@ +# Database Migration with Atlas + +This project uses [Atlas](https://atlasgo.io/) for database schema management and migrations. + +## 📋 Prerequisites + +1. **Download and Install Atlas CLI** + Run the following command in PowerShell or Git Bash: + + ```sh + curl -sSf https://atlasgo.sh | sh + ``` + Verify installation: + + ```sh + atlas version + ``` + +2. Install GORM Provider + Run inside your Go project: + + ```sh + go get -u ariga.io/atlas-provider-gorm + ``` + +3. Create atlas.hcl configuration file + Just create an atlas.hcl file in your project root as example given at atlas.hcl.example +4. Create migrations folder + ```sh + mkdir migrations + ``` +5. Usage +You can use the provided Makefile for common commands: + + Generate a migration diff + ```sh + make diff + ``` + + Apply migrations + ```sh + make apply + ``` + + Compute schema hash + ```sh + make hash + ``` + + If you don’t have make installed, you can run the Atlas commands directly: + ```sh + atlas migrate diff --env gorm + ``` + ```sh + atlas migrate apply --env gorm + ``` + ```sh + atlas migrate hash + ``` \ No newline at end of file diff --git a/cmd/simgos-sync-migration/atlas.hcl.example b/cmd/simgos-sync-migration/atlas.hcl.example new file mode 100644 index 00000000..857d1352 --- /dev/null +++ b/cmd/simgos-sync-migration/atlas.hcl.example @@ -0,0 +1,22 @@ +data "external_schema" "gorm" { + program = [ + "go", + "run", + "-mod=mod", + ".", + ] +} + +env "gorm" { + src = data.external_schema.gorm.url + dev = "" // dsn db to check the diff + migration { + dir = "file://migrations" + } + url = "" // dsn db to apply + format { + migrate { + diff = "{{ sql . \" \" }}" + } + } +} \ No newline at end of file diff --git a/cmd/simgos-sync-migration/migration.go b/cmd/simgos-sync-migration/migration.go new file mode 100644 index 00000000..fdd2c995 --- /dev/null +++ b/cmd/simgos-sync-migration/migration.go @@ -0,0 +1,9 @@ +package main + +import ( + m "simrs-vx/internal/interface/migration" +) + +func main() { + m.Migrate(m.SimgosSync) +} diff --git a/cmd/simgos-sync-migration/migrations/20251113035508.sql b/cmd/simgos-sync-migration/migrations/20251113035508.sql new file mode 100644 index 00000000..5dddf417 --- /dev/null +++ b/cmd/simgos-sync-migration/migrations/20251113035508.sql @@ -0,0 +1,36 @@ +-- Create "InstallationLink" table +CREATE TABLE "public"."InstallationLink" ( + "Id" bigserial NOT NULL, + "CreatedAt" timestamptz NULL, + "UpdatedAt" timestamptz NULL, + "DeletedAt" timestamptz NULL, + "Simx_Id" bigint NULL, + "Simgos_Id" bigint NULL, + PRIMARY KEY ("Id"), + CONSTRAINT "uni_InstallationLink_Simgos_Id" UNIQUE ("Simgos_Id"), + CONSTRAINT "uni_InstallationLink_Simx_Id" UNIQUE ("Simx_Id") +); +-- Create "InstallationSimgosLog" table +CREATE TABLE "public"."InstallationSimgosLog" ( + "Id" bigserial NOT NULL, + "CreatedAt" timestamptz NULL, + "UpdatedAt" timestamptz NULL, + "DeletedAt" timestamptz NULL, + "Value" text NULL, + "Date" timestamptz NULL, + "Status" text NULL, + "ErrMessage" text NULL, + PRIMARY KEY ("Id") +); +-- Create "InstallationSimxLog" table +CREATE TABLE "public"."InstallationSimxLog" ( + "Id" bigserial NOT NULL, + "CreatedAt" timestamptz NULL, + "UpdatedAt" timestamptz NULL, + "DeletedAt" timestamptz NULL, + "Value" text NULL, + "Date" timestamptz NULL, + "Status" text NULL, + "ErrMessage" text NULL, + PRIMARY KEY ("Id") +); diff --git a/cmd/simgos-sync-migration/migrations/atlas.sum b/cmd/simgos-sync-migration/migrations/atlas.sum new file mode 100644 index 00000000..f1405dd2 --- /dev/null +++ b/cmd/simgos-sync-migration/migrations/atlas.sum @@ -0,0 +1,2 @@ +h1:8jMmMBxSEls9jaOUrpQQV0wUPlORRwJWd5g9742Z2fQ= +20251113035508.sql h1:rjDlu6yDdy5xv6nrCOr7NialrLSLT23pzduYNq29Hf0= diff --git a/internal/domain/main-entities/medicine-form/dto.go b/internal/domain/main-entities/medicine-form/dto.go new file mode 100644 index 00000000..a7145a2f --- /dev/null +++ b/internal/domain/main-entities/medicine-form/dto.go @@ -0,0 +1,67 @@ +package medicineform + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" +) + +type CreateDto struct { + Code *string `json:"code" validate:"maxLength=20"` + Name string `json:"name" validate:"maxLength=50"` +} + +type ReadListDto struct { + FilterDto + Includes string `json:"includes"` + Sort string `json:"sort"` + Pagination ecore.Pagination +} + +type FilterDto struct { + Code string `json:"code"` + Name string `json:"name"` + Search string `json:"search" gormhelper:"searchColumns=Code,Name"` +} + +type ReadDetailDto struct { + Id *uint16 `json:"id"` + Code *string `json:"code"` +} + +type UpdateDto struct { + Id *uint `json:"id"` + CreateDto +} + +type DeleteDto struct { + Id *uint `json:"id"` + Code *string `json:"code"` +} + +type MetaDto struct { + PageNumber int `json:"page_number"` + PageSize int `json:"page_size"` + Count int `json:"count"` +} + +type ResponseDto struct { + ecore.SmallMain + Code string `json:"code"` + Name string `json:"name"` +} + +func (d MedicineForm) ToResponse() ResponseDto { + resp := ResponseDto{ + Code: d.Code, + Name: d.Name, + } + resp.SmallMain = d.SmallMain + return resp +} + +func ToResponseList(data []MedicineForm) []ResponseDto { + resp := make([]ResponseDto, len(data)) + for i, u := range data { + resp[i] = u.ToResponse() + } + return resp +} diff --git a/internal/domain/main-entities/medicine-form/entity.go b/internal/domain/main-entities/medicine-form/entity.go new file mode 100644 index 00000000..75b1b104 --- /dev/null +++ b/internal/domain/main-entities/medicine-form/entity.go @@ -0,0 +1,11 @@ +package medicineform + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" +) + +type MedicineForm struct { + ecore.SmallMain // adjust this according to the needs + Code string `json:"code" gorm:"unique;size:20"` + Name string `json:"name" gorm:"size:50"` +} diff --git a/internal/domain/main-entities/medicine/dto.go b/internal/domain/main-entities/medicine/dto.go index 945db1e5..71d41d42 100644 --- a/internal/domain/main-entities/medicine/dto.go +++ b/internal/domain/main-entities/medicine/dto.go @@ -4,6 +4,7 @@ import ( ecore "simrs-vx/internal/domain/base-entities/core" ein "simrs-vx/internal/domain/main-entities/infra" eit "simrs-vx/internal/domain/main-entities/item" + emf "simrs-vx/internal/domain/main-entities/medicine-form" emg "simrs-vx/internal/domain/main-entities/medicine-group" emm "simrs-vx/internal/domain/main-entities/medicine-method" eu "simrs-vx/internal/domain/main-entities/uom" @@ -71,6 +72,8 @@ type ResponseDto struct { MedicineGroup *emg.MedicineGroup `json:"medicineGroup"` MedicineMethod_Code *string `json:"medicineMethod_code"` MedicineMethod *emm.MedicineMethod `json:"medicineMethod"` + MedicineForm_Code *string `json:"medicineForm_code"` + MedicineForm *emf.MedicineForm `json:"medicineForm"` Uom_Code *string `json:"uom_code"` Uom *eu.Uom `json:"uom"` Dose uint8 `json:"dose"` @@ -89,6 +92,8 @@ func (d Medicine) ToResponse() ResponseDto { MedicineGroup: d.MedicineGroup, MedicineMethod_Code: d.MedicineMethod_Code, MedicineMethod: d.MedicineMethod, + MedicineForm_Code: d.MedicineForm_Code, + MedicineForm: d.MedicineForm, Uom_Code: d.Uom_Code, Uom: d.Uom, Dose: d.Dose, diff --git a/internal/domain/main-entities/medicine/entity.go b/internal/domain/main-entities/medicine/entity.go index dda220bc..0a79e7e5 100644 --- a/internal/domain/main-entities/medicine/entity.go +++ b/internal/domain/main-entities/medicine/entity.go @@ -4,6 +4,7 @@ import ( ecore "simrs-vx/internal/domain/base-entities/core" ein "simrs-vx/internal/domain/main-entities/infra" eit "simrs-vx/internal/domain/main-entities/item" + emf "simrs-vx/internal/domain/main-entities/medicine-form" emg "simrs-vx/internal/domain/main-entities/medicine-group" emm "simrs-vx/internal/domain/main-entities/medicine-method" eu "simrs-vx/internal/domain/main-entities/uom" @@ -17,6 +18,8 @@ type Medicine struct { MedicineGroup *emg.MedicineGroup `json:"medicineGroup,omitempty" gorm:"foreignKey:MedicineGroup_Code;references:Code"` MedicineMethod_Code *string `json:"medicineMethod_code" gorm:"size:10"` MedicineMethod *emm.MedicineMethod `json:"medicineMethod,omitempty" gorm:"foreignKey:MedicineMethod_Code;references:Code"` + MedicineForm_Code *string `json:"medicineForm_code" gorm:"size:20"` + MedicineForm *emf.MedicineForm `json:"medicineForm,omitempty" gorm:"foreignKey:MedicineForm_Code;references:Code"` Uom_Code *string `json:"uom_code" gorm:"size:10"` Uom *eu.Uom `json:"uom" gorm:"foreignKey:Uom_Code;references:Code"` Dose uint8 `json:"dose"` diff --git a/internal/domain/main-entities/prescription-item/dto.go b/internal/domain/main-entities/prescription-item/dto.go index 33e63ed2..6e871428 100644 --- a/internal/domain/main-entities/prescription-item/dto.go +++ b/internal/domain/main-entities/prescription-item/dto.go @@ -19,7 +19,7 @@ type CreateDto struct { Usage string `json:"usage"` Interval uint8 `json:"interval"` IntervalUnit_Code erc.TimeUnitCode `json:"intervalUnit_code"` - IntervalMultiplier *uint16 `json:"intervalMultiplier" validate:"required"` + IntervalMultiplier *uint16 `json:"intervalMultiplier"` Quantity float64 `json:"quantity"` } diff --git a/internal/domain/main-entities/prescription/entity.go b/internal/domain/main-entities/prescription/entity.go index 849e8083..8c879b29 100644 --- a/internal/domain/main-entities/prescription/entity.go +++ b/internal/domain/main-entities/prescription/entity.go @@ -20,6 +20,10 @@ type Prescription struct { Status_Code erc.DataStatusCode `json:"status_code"` } +func (d Prescription) IsNotNew() bool { + return d.Status_Code != erc.DSCNew +} + func (d Prescription) IsApproved() bool { return d.Status_Code == erc.DSCDone } diff --git a/internal/domain/main-entities/therapy-protocol/entity.go b/internal/domain/main-entities/therapy-protocol/entity.go index 7caaf0a6..1378eb25 100644 --- a/internal/domain/main-entities/therapy-protocol/entity.go +++ b/internal/domain/main-entities/therapy-protocol/entity.go @@ -16,16 +16,17 @@ type TherapyProtocol struct { Doctor_Code *string `json:"doctor_code"` Doctor *ed.Doctor `json:"doctor,omitempty" gorm:"foreignKey:Doctor_Code;references:Code"` - Anamnesis *string `json:"anamnesis" gorm:"size:2048"` - MedicalDiagnoses *string `json:"medicalDiagnoses"` - FunctionDiagnoses *string `json:"functionDiagnoses"` - Procedures *string `json:"procedures"` - SupportingExams *string `json:"supportingExams" gorm:"size:2048"` - Instruction *string `json:"instruction" gorm:"size:2048"` - Evaluation *string `json:"evaluation" gorm:"size:2048"` - WorkCauseStatus *string `json:"workCauseStatus" gorm:"size:2048"` - Frequency *uint `json:"frequency"` - IntervalUnit_Code *common.TimeUnitCode `json:"intervalUnit_code" gorm:"size:10"` - Duration *uint `json:"duration"` - DurationUnit_Code *common.TimeUnitCode `json:"durationUnit_code" gorm:"size:10"` + Anamnesis *string `json:"anamnesis" gorm:"size:2048"` + MedicalDiagnoses *string `json:"medicalDiagnoses"` + FunctionDiagnoses *string `json:"functionDiagnoses"` + Procedures *string `json:"procedures"` + SupportingExams *string `json:"supportingExams" gorm:"size:2048"` + Instruction *string `json:"instruction" gorm:"size:2048"` + Evaluation *string `json:"evaluation" gorm:"size:2048"` + WorkCauseStatus *string `json:"workCauseStatus" gorm:"size:2048"` + Frequency *uint `json:"frequency"` + IntervalUnit_Code *common.TimeUnitCode `json:"intervalUnit_code" gorm:"size:10"` + Duration *uint `json:"duration"` + DurationUnit_Code *common.TimeUnitCode `json:"durationUnit_code" gorm:"size:10"` + Status_Code *common.DataVerifiedCode `json:"status_code" gorm:"size:10"` } diff --git a/internal/domain/references/common/common.go b/internal/domain/references/common/common.go index 2941049c..5b3f675b 100644 --- a/internal/domain/references/common/common.go +++ b/internal/domain/references/common/common.go @@ -16,6 +16,7 @@ type ( DataVerifiedCode string CrudCode string DataApprovalCode string + ProcessStatusCode string ) const ( @@ -58,6 +59,7 @@ const ( SCInactive ActiveStatusCode = "inactive" // Tidak aktif DSCNew DataStatusCode = "new" // Baru + DSCSubmited DataStatusCode = "submited" // Submited DSCReview DataStatusCode = "review" // Review DSCProcess DataStatusCode = "process" // Proses DSCDone DataStatusCode = "done" // Selesai @@ -101,6 +103,9 @@ const ( DACNew DataApprovalCode = "new" DACApproved DataApprovalCode = "approved" DACRejected DataApprovalCode = "rejected" + + PSCSuccess ProcessStatusCode = "success" + PSCFailed ProcessStatusCode = "failed" ) func GetDayCodes() map[DayCode]string { diff --git a/internal/domain/references/upload/upload.go b/internal/domain/references/upload/upload.go index 603be6c8..b22960f4 100644 --- a/internal/domain/references/upload/upload.go +++ b/internal/domain/references/upload/upload.go @@ -8,13 +8,16 @@ type ( ) const ( - UCPRN UploadCode = "person-resident-number" // Person Resident Number - UCPDL UploadCode = "person-driver-license" // Person Driver License - UCPP UploadCode = "person-passport" // Person Passport - UCPFC UploadCode = "person-family-card" // Person Family Card - UCMIR UploadCode = "mcu-item-result" // Mcu Item Result - UCSEP UploadCode = "vclaim-sep" // SEP - UCSIPP UploadCode = "vclaim-sipp" // SIPP + UCPRN UploadCode = "person-resident-number" // Person Resident Number + UCPDL UploadCode = "person-driver-license" // Person Driver License + UCPP UploadCode = "person-passport" // Person Passport + UCPFC UploadCode = "person-family-card" // Person Family Card + UCMIR UploadCode = "mcu-item-result" // Mcu Item Result + UCEnPatient UploadCode = "encounter-patient" + UCEnSupport UploadCode = "encounter-support" + UcEnOther UploadCode = "encounter-other" + UCSEP UploadCode = "vclaim-sep" // SEP + UCSIPP UploadCode = "vclaim-sipp" // SIPP ETCPerson EntityTypeCode = "person" ETCEncounter EntityTypeCode = "encounter" @@ -26,7 +29,7 @@ var validUploadCodesByEntity = map[EntityTypeCode][]UploadCode{ UCPRN, UCPDL, UCPP, UCPFC, }, ETCEncounter: { - UCSEP, UCSIPP, + UCSEP, UCSIPP, UCEnPatient, UCEnSupport, UcEnOther, }, ETCMCU: { UCMIR, diff --git a/internal/domain/simgos-entities/installation/entity.go b/internal/domain/simgos-entities/installation/entity.go new file mode 100644 index 00000000..f2b291f5 --- /dev/null +++ b/internal/domain/simgos-entities/installation/entity.go @@ -0,0 +1,12 @@ +package installation + +type MInstalasi struct { + No_Instalasi uint `json:"no_instalasi" gorm:"primaryKey;autoIncrement;column:no_instalasi"` + Nama_Instalasi string `json:"nama_instalasi" gorm:"column:nama_instalasi"` + Status_Rawat_Inap uint `json:"status_rawat_inap" gorm:"column:status_rawat_inap"` + St_Aktif uint `json:"st_aktif" gorm:"column:st_aktif"` +} + +func (MInstalasi) TableName() string { + return "m_instalasi" +} diff --git a/internal/domain/simgos-sync-entities/.keep b/internal/domain/simgos-sync-entities/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/internal/domain/sync-entities/installation/entity.go b/internal/domain/sync-entities/installation/entity.go new file mode 100644 index 00000000..a7818f3f --- /dev/null +++ b/internal/domain/sync-entities/installation/entity.go @@ -0,0 +1,29 @@ +package installation + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" + erc "simrs-vx/internal/domain/references/common" + "time" +) + +type InstallationLink struct { + ecore.Main + Simx_Id uint `json:"simx_id" gorm:"unique"` + Simgos_Id uint `json:"simgos_id" gorm:"unique"` +} + +type InstallationSimxLog struct { + ecore.Main + Value *string `json:"value"` + Date *time.Time `json:"date"` + Status erc.ProcessStatusCode `json:"status"` + ErrMessage *string `json:"errMessage"` +} + +type InstallationSimgosLog struct { + ecore.Main + Value *string `json:"value"` + Date *time.Time `json:"date"` + Status erc.ProcessStatusCode `json:"status"` + ErrMessage *string `json:"errMessage"` +} diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index 04bef359..346436d0 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -91,6 +91,7 @@ import ( medicalactionsrc "simrs-vx/internal/interface/main-handler/medical-action-src" medicalactionsrcitem "simrs-vx/internal/interface/main-handler/medical-action-src-item" medicine "simrs-vx/internal/interface/main-handler/medicine" + medicineform "simrs-vx/internal/interface/main-handler/medicine-form" medicinegroup "simrs-vx/internal/interface/main-handler/medicine-group" medicinemethod "simrs-vx/internal/interface/main-handler/medicine-method" pharmacycompany "simrs-vx/internal/interface/main-handler/pharmacy-company" @@ -140,6 +141,7 @@ func SetRoutes() http.Handler { hc.RegCrud(r, "/v1/auth-partner", authpartner.O) hc.RegCrud(r, "/v1/practice-schedule", practiceschedule.O) hc.RegCrud(r, "/v1/counter", counter.O) + hc.RegCrudByCode(r, "/v1/medicine-form", medicineform.O) hc.RegCrud(r, "/v1/medicine-mix", medicicinemix.O) hc.RegCrud(r, "/v1/medicine-mix-item", medicicinemixitem.O) hc.RegCrud(r, "/v1/soapi", auth.GuardMW, soapi.O) @@ -188,6 +190,7 @@ func SetRoutes() http.Handler { "POST /": prescription.O.Create, "PATCH /{id}": prescription.O.Update, "DELETE /{id}": prescription.O.Delete, + "PATCH /{id}/submit": prescription.O.Submit, "PATCH /{id}/approve": prescription.O.Approve, }) hk.GroupRoutes("/v1/mcu-order-sub-item", r, auth.GuardMW, hk.MapHandlerFunc{ diff --git a/internal/interface/main-handler/medicine-form/handler.go b/internal/interface/main-handler/medicine-form/handler.go new file mode 100644 index 00000000..a363fb30 --- /dev/null +++ b/internal/interface/main-handler/medicine-form/handler.go @@ -0,0 +1,71 @@ +package medicineform + +import ( + "net/http" + + rw "github.com/karincake/risoles" + sf "github.com/karincake/semprit" + + // ua "github.com/karincake/tumpeng/auth/svc" + + e "simrs-vx/internal/domain/main-entities/medicine-form" + u "simrs-vx/internal/use-case/main-use-case/medicine-form" +) + +type myBase struct{} + +var O myBase + +func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { + dto := e.CreateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + res, err := u.Create(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetList(w http.ResponseWriter, r *http.Request) { + dto := e.ReadListDto{} + sf.UrlQueryParam(&dto, *r.URL) + res, err := u.ReadList(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) { + code := rw.ValidateString(w, "code", r.PathValue("code")) + if code == "" { + return + } + dto := e.ReadDetailDto{} + dto.Code = &code + res, err := u.ReadDetail(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Update(w http.ResponseWriter, r *http.Request) { + code := rw.ValidateString(w, "code", r.PathValue("code")) + if code == "" { + return + } + + dto := e.UpdateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + dto.Code = &code + res, err := u.Update(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { + code := rw.ValidateString(w, "code", r.PathValue("code")) + if code == "" { + return + } + + dto := e.DeleteDto{} + dto.Code = &code + res, err := u.Delete(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/interface/main-handler/prescription/handler.go b/internal/interface/main-handler/prescription/handler.go index 24d73372..30d5998e 100644 --- a/internal/interface/main-handler/prescription/handler.go +++ b/internal/interface/main-handler/prescription/handler.go @@ -78,6 +78,18 @@ func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { rw.DataResponse(w, res, err) } +func (obj myBase) Submit(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + dto := e.ReadDetailDto{} + dto.Id = uint(id) + res, err := u.Submit(dto) + rw.DataResponse(w, res, err) +} + func (obj myBase) Approve(w http.ResponseWriter, r *http.Request) { id := rw.ValidateInt(w, "id", r.PathValue("id")) if id <= 0 { diff --git a/internal/interface/migration/main-entities.go b/internal/interface/migration/main-entities.go index 272cd4ce..94b5d545 100644 --- a/internal/interface/migration/main-entities.go +++ b/internal/interface/migration/main-entities.go @@ -57,6 +57,7 @@ import ( medicationitem "simrs-vx/internal/domain/main-entities/medication-item" medicationitemdist "simrs-vx/internal/domain/main-entities/medication-item-dist" medicine "simrs-vx/internal/domain/main-entities/medicine" + medicineform "simrs-vx/internal/domain/main-entities/medicine-form" medicinegroup "simrs-vx/internal/domain/main-entities/medicine-group" medicinemethod "simrs-vx/internal/domain/main-entities/medicine-method" medicinemix "simrs-vx/internal/domain/main-entities/medicine-mix" @@ -145,6 +146,7 @@ func getMainEntities() []any { ðnic.Ethnic{}, &insurancecompany.InsuranceCompany{}, &medicine.Medicine{}, + &medicineform.MedicineForm{}, &medicinemix.MedicineMix{}, &medicinemixitem.MedicineMixItem{}, &medicalactionsrc.MedicalActionSrc{}, diff --git a/internal/interface/migration/migration.go b/internal/interface/migration/migration.go index 6cec68dc..31ec1840 100644 --- a/internal/interface/migration/migration.go +++ b/internal/interface/migration/migration.go @@ -31,6 +31,8 @@ func getEntities(input string) []any { return getMainEntities() case "satusehat": return getSatuSehatEntities() + case "simgossync": + return getSimgosSyncEntities() } return nil } diff --git a/internal/interface/migration/simgossync-entities.go b/internal/interface/migration/simgossync-entities.go new file mode 100644 index 00000000..3b67265f --- /dev/null +++ b/internal/interface/migration/simgossync-entities.go @@ -0,0 +1,14 @@ +package migration + +import ( + /************** Source ***************/ + installation "simrs-vx/internal/domain/sync-entities/installation" +) + +func getSimgosSyncEntities() []any { + return []any{ + &installation.InstallationLink{}, + &installation.InstallationSimxLog{}, + &installation.InstallationSimgosLog{}, + } +} diff --git a/internal/interface/migration/tycovar.go b/internal/interface/migration/tycovar.go index f86f855d..f6c57b4b 100644 --- a/internal/interface/migration/tycovar.go +++ b/internal/interface/migration/tycovar.go @@ -2,6 +2,7 @@ package migration const ( - Main = "main" - SatuSehat = "satusehat" + Main = "main" + SatuSehat = "satusehat" + SimgosSync = "simgossync" ) diff --git a/internal/use-case/main-use-case/authentication/case.go.bu b/internal/use-case/main-use-case/authentication/case.go.bu new file mode 100644 index 00000000..1743407a --- /dev/null +++ b/internal/use-case/main-use-case/authentication/case.go.bu @@ -0,0 +1,463 @@ +package authentication + +import ( + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/golang-jwt/jwt" + "github.com/google/uuid" + a "github.com/karincake/apem" + dg "github.com/karincake/apem/db-gorm-pg" + ms "github.com/karincake/apem/ms-redis" + d "github.com/karincake/dodol" + l "github.com/karincake/lepet" + + pa "simrs-vx/internal/lib/auth" + pl "simrs-vx/pkg/logger" + p "simrs-vx/pkg/password" + +<<<<<<< HEAD + ed "simrs-vx/internal/domain/main-entities/doctor" + ee "simrs-vx/internal/domain/main-entities/employee" + "simrs-vx/internal/domain/main-entities/intern" + em "simrs-vx/internal/domain/main-entities/midwife" + en "simrs-vx/internal/domain/main-entities/nurse" + ep "simrs-vx/internal/domain/main-entities/pharmacist" + eu "simrs-vx/internal/domain/main-entities/user" + +======= + eu "simrs-vx/internal/domain/main-entities/user" + euf "simrs-vx/internal/domain/main-entities/user-fes" +>>>>>>> fc7e74b (feat/sso-auth: wip) + erc "simrs-vx/internal/domain/references/common" +) + +const source = "authentication" + +var authCfg AuthCfg + +func init() { + a.RegisterExtCall(GetConfig) +} + +// Generates token and store in redis at one place +// just return the error code +func GenToken(input eu.LoginDto) (*d.Data, error) { + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Get User + user := &eu.User{Name: input.Name} + // if input.Position_Code != "" { + // user.Position_Code = input.Position_Code + // } + if errCode := getAndCheck(user, user); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + + if user.LoginAttemptCount > 5 { + if user.LastSuccessLogin != nil { + now := time.Now() + lastAllowdLogin := user.LastAllowdLogin + if lastAllowdLogin.After(now.Add(-time.Hour * 1)) { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-tooMany", Message: pl.GenMessage("auth-login-tooMany")}} + } else { + tn := time.Now() + user.LastAllowdLogin = &tn + user.LoginAttemptCount = 0 + dg.I.Save(&user) + } + } else { + tn := time.Now() + user.LastAllowdLogin = &tn + dg.I.Save(&user) + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-tooMany", Message: pl.GenMessage("auth-login-tooMany")}} + } + } + + if !p.Check(input.Password, user.Password) { + user.LoginAttemptCount++ + dg.I.Save(&user) + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-incorrect", Message: pl.GenMessage("auth-login-incorrect")}} + } else if user.Status_Code == erc.USCBlocked { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-blocked", Message: pl.GenMessage("auth-login-blocked")}} + } else if user.Status_Code == erc.USCNew { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-unverified", Message: pl.GenMessage("auth-login-unverified")}} + } + + // Access token prep + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("uuid-gen-fail"), err)) + } + if input.Duration == 0 { + input.Duration = 24 * 60 + } + duration := time.Minute * time.Duration(input.Duration) + aUuid := id.String() + atExpires := time.Now().Add(duration).Unix() + atSecretKey := authCfg.AtSecretKey + + // Create Claim + atClaims := jwt.MapClaims{} + atClaims["user_id"] = user.Id + atClaims["user_name"] = user.Name + atClaims["user_contractPosition_code"] = user.ContractPosition_Code + atClaims["uuid"] = aUuid + atClaims["exp"] = atExpires + + // Create output + outputData := d.II{ + "user_id": strconv.Itoa(int(user.Id)), + "user_name": user.Name, + "user_contractPosition_code": user.ContractPosition_Code, + } + + // extra + // role := []string{} + // switch user.ContractPosition_Code { + // case erg.CSCEmp: + // // employee + // employee := ee.Employee{} + // dg.I.Where("\"User_Id\" = ?", user.Id).First(&employee) + // if employee.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noEmployee", Message: pl.GenMessage("auth-noEmployee")}} + // } + // atClaims["employee_id"] = employee.Id + // outputData["employee_id"] = employee.Id + // role = append(role, "emp-"+string(*employee.Position_Code)) + + // //if employee.Division_Code != nil { + // // atClaims["employee_division_code"] = employee.Division_Code + // // outputData["employee_division_code"] = employee.Division_Code + // //} + + // // employee position + // if employee.Id > 0 && employee.Position_Code != nil { + // atClaims["employee_position_code"] = *employee.Position_Code + // switch *employee.Position_Code { + // case erg.EPCDoc: + // doctor := ed.Doctor{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&doctor) + // if doctor.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noDoctor", Message: pl.GenMessage("auth-noDoctor")}} + // } + // atClaims["doctor_code"] = doctor.Code + // outputData["doctor_code"] = doctor.Code + +<<<<<<< HEAD + // specialist + if doctor.Specialist_Code != nil { + atClaims["specialist_code"] = doctor.Specialist_Code + outputData["specialist_code"] = doctor.Specialist_Code + } + if doctor.Subspecialist_Code != nil { + atClaims["subspecialist_code"] = doctor.Subspecialist_Code + outputData["subspecialist_code"] = doctor.Subspecialist_Code + } + case erg.EPCNur: + empData := en.Nurse{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + } + atClaims["nurse_code"] = empData.Code + outputData["nurse_code"] = empData.Code + case erg.EPCMwi: + empData := em.Midwife{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + } + atClaims["midwife_code"] = empData.Code + outputData["midwife_code"] = empData.Code + case erg.EPCPha: + empData := ep.Pharmacist{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noPharmacist", Message: pl.GenMessage("auth-noPharmacist")}} + } + atClaims["pharmacist_code"] = empData.Code + outputData["pharmacist_code"] = empData.Code + } +======= + // // specialist + // if doctor.Specialist_Code != nil { + // atClaims["specialist_code"] = doctor.Specialist_Code + // outputData["specialist_code"] = doctor.Specialist_Code + // } + // if doctor.Subspecialist_Code != nil { + // atClaims["subspecialist_code"] = doctor.Subspecialist_Code + // outputData["subspecialist_code"] = doctor.Subspecialist_Code + // } + // case erg.EPCNur: + // empData := en.Nurse{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + // } + // atClaims["nurse_code"] = empData.Code + // outputData["nurse_code"] = empData.Code + // case erg.EPCMwi: + // empData := em.Midwife{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + // } + // atClaims["midwife_code"] = empData.Code + // outputData["midwife_code"] = empData.Code + // } +>>>>>>> fc7e74b (feat/sso-auth: wip) + + // errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} + + // // division position + // divisionPositions, err := getDivisionPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } + + // // installation position + // installationPositions, err := getInstallationPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } + + // // unit position + // unitPositions, err := getUnitPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } + + // // specialist position + // specialistPositions, err := getSpecialistPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } + + // // subspecialist position + // subspecialistPositions, err := getSubspecialistPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } + + // role = append(role, divisionPositions...) + // role = append(role, installationPositions...) + // role = append(role, unitPositions...) + // role = append(role, specialistPositions...) + // role = append(role, subspecialistPositions...) + // // atClaims["division_positions"] = divsionPositions + // // outputData["division_positions"] = divsionPositions + // } + // case erg.CSCInt: + // intern := intern.Intern{} + // dg.I.Where("\"User_Id\" = ?", user.Id).First(&intern) + // role = append(role, "int-"+string(*intern.Position_Code)) + // case erg.CSCSys: + // role = append(role, "system") + // } + // atClaims["roles"] = role + // outputData["roles"] = role + if err := populateRoles(user, atClaims, outputData, event); err != nil { + return nil, err + } + + // Generate jwt + at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) + ats, err := at.SignedString([]byte(atSecretKey)) + if err != nil { + return nil, d.FieldErrors{"user": d.FieldError{Code: "token-sign-err", Message: pl.GenMessage("token-sign-err")}} + } + outputData["accessToken"] = ats + + // Save to redis + now := time.Now() + atx := time.Unix(atExpires, 0) //converting Unix to UTC(to Time object) + err = ms.I.Set(aUuid, strconv.Itoa(int(user.Id)), atx.Sub(now)).Err() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("redis-store-fail"), err.Error())) + } + + tn := time.Now() + user.LoginAttemptCount = 0 + user.LastSuccessLogin = &tn + user.LastAllowdLogin = &tn + dg.I.Save(&user) + + // Current data + return &d.Data{ + Meta: d.IS{ + "source": "authentication", + "structure": "single-data", + "status": "verified", + }, + Data: outputData, + }, nil +} + +func RevokeToken(uuid string) { + ms.I.Del(uuid) +} + +func VerifyToken(r *http.Request, tokenType TokenType) (data *jwt.Token, errCode, errDetail string) { + auth := r.Header.Get("Authorization") + if auth == "" { + return nil, "auth-missingHeader", "" + } + authArr := strings.Split(auth, " ") + if len(authArr) == 2 { + auth = authArr[1] + } + + token, err := jwt.Parse(auth, func(token *jwt.Token) (any, error) { + //Make sure that the token method conform to "SigningMethodHMAC" + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf(l.I.Msg("token-sign-unexcpeted"), token.Header["alg"]) + } + if tokenType == AccessToken { + return []byte(authCfg.AtSecretKey), nil + } else { + return []byte(authCfg.RtSecretKey), nil + } + }) + if err != nil { + return nil, "token-parse-fail", err.Error() + } + return token, "", "" +} + +func ExtractToken(r *http.Request, tokenType TokenType) (data *pa.AuthInfo, err error) { + token, errCode, errDetail := VerifyToken(r, tokenType) + if errCode != "" { + return nil, d.FieldError{Code: errCode, Message: pl.GenMessage(errCode, errDetail)} + } + claims, ok := token.Claims.(jwt.MapClaims) + if ok && token.Valid { + accessUuid, ok := claims["uuid"].(string) + if !ok { + return nil, d.FieldError{Code: "token-invalid", Message: pl.GenMessage("token-invalid", "uuid not available")} + } + user_id, myErr := strconv.ParseInt(fmt.Sprintf("%.f", claims["user_id"]), 10, 64) + if myErr != nil { + return nil, d.FieldError{Code: "token-invalid", Message: pl.GenMessage("token-invalid", "uuid is not available")} + } + accessUuidRedis := ms.I.Get(accessUuid) + if accessUuidRedis.String() == "" { + return nil, d.FieldError{Code: "token-unidentified", Message: pl.GenMessage("token-unidentified")} + } + + data = &pa.AuthInfo{ + Uuid: accessUuid, + User_Id: uint(user_id), + User_Name: fmt.Sprintf("%v", claims["user_name"]), + } + + data.User_ContractPosition_code = checkStrClaims(claims, "contractPosition_code") + data.Employee_Position_Code = checkStrPtrClaims(claims, "employee_position_code") + data.Doctor_Code = checkStrPtrClaims(claims, "doctor_code") + data.Nurse_Code = checkStrPtrClaims(claims, "nurse_code") + data.Midwife_Code = checkStrPtrClaims(claims, "midwife_code") + data.Nutritionist_Code = checkStrPtrClaims(claims, "nutritionist_code") + data.Laborant_Code = checkStrPtrClaims(claims, "laborant_code") + data.Pharmachist_Code = checkStrPtrClaims(claims, "pharmachist_code") + data.Intern_Position_Code = checkStrPtrClaims(claims, "intern_position_code") + data.Employee_Id = checkUntPtrClaims(claims, "employee_id") + return + } + return nil, d.FieldError{Code: "token", Message: "token-invalid"} +} + +func GetConfig() { + a.ParseCfg(&authCfg) +} + +func GenTokenFes(input euf.LoginDto) (*d.Data, error) { + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Get User Fes + userFes := &euf.UserFes{Name: input.Name, AuthPartner_Code: input.AuthPartner_Code} + if errCode := getAndCheck(userFes, userFes); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + if userFes.AuthPartner.SecretKey != input.AuthPartner_SecretKey { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-secretKey-invalid", Message: pl.GenMessage("auth-secretKey-invalid")}} + } + + user := &eu.User{Name: userFes.User_Name} + if errCode := getAndCheck(user, user); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + + // Access token prep + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("uuid-gen-fail"), err)) + } + if input.Duration == 0 { + input.Duration = 24 * 60 + } + duration := time.Minute * time.Duration(input.Duration) + aUuid := id.String() + atExpires := time.Now().Add(duration).Unix() + atSecretKey := authCfg.AtSecretKey + + // Create Claim + atClaims := jwt.MapClaims{} + atClaims["user_id"] = user.Id + atClaims["user_name"] = user.Name + atClaims["user_contractPosition_code"] = user.ContractPosition_Code + atClaims["uuid"] = aUuid + atClaims["exp"] = atExpires + + // Create output + outputData := d.II{ + "user_id": strconv.Itoa(int(user.Id)), + "user_name": user.Name, + "user_contractPosition_code": user.ContractPosition_Code, + } + + // extra + if err := populateRoles(user, atClaims, outputData, event); err != nil { + return nil, err + } + + // Generate jwt + at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) + ats, err := at.SignedString([]byte(atSecretKey)) + if err != nil { + return nil, d.FieldErrors{"user": d.FieldError{Code: "token-sign-err", Message: pl.GenMessage("token-sign-err")}} + } + outputData["accessToken"] = ats + + // Save to redis + now := time.Now() + atx := time.Unix(atExpires, 0) //converting Unix to UTC(to Time object) + err = ms.I.Set(aUuid, strconv.Itoa(int(user.Id)), atx.Sub(now)).Err() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("redis-store-fail"), err.Error())) + } + + tn := time.Now() + user.LoginAttemptCount = 0 + user.LastSuccessLogin = &tn + user.LastAllowdLogin = &tn + dg.I.Save(&user) + + // Current data + return &d.Data{ + Meta: d.IS{ + "source": "authentication", + "structure": "single-data", + "status": "verified", + }, + Data: outputData, + }, nil +} diff --git a/internal/use-case/main-use-case/authentication/helper.go.bu b/internal/use-case/main-use-case/authentication/helper.go.bu new file mode 100644 index 00000000..b7ccc660 --- /dev/null +++ b/internal/use-case/main-use-case/authentication/helper.go.bu @@ -0,0 +1,323 @@ +package authentication + +import ( + "fmt" + "strconv" + "time" + + "github.com/golang-jwt/jwt" + "github.com/google/uuid" + dg "github.com/karincake/apem/db-gorm-pg" + ms "github.com/karincake/apem/ms-redis" + d "github.com/karincake/dodol" + l "github.com/karincake/lepet" + + pl "simrs-vx/pkg/logger" + + edp "simrs-vx/internal/domain/main-entities/division-position" + ed "simrs-vx/internal/domain/main-entities/doctor" + ee "simrs-vx/internal/domain/main-entities/employee" + eip "simrs-vx/internal/domain/main-entities/installation-position" + "simrs-vx/internal/domain/main-entities/intern" + em "simrs-vx/internal/domain/main-entities/midwife" + en "simrs-vx/internal/domain/main-entities/nurse" + esp "simrs-vx/internal/domain/main-entities/specialist-position" + essp "simrs-vx/internal/domain/main-entities/subspecialist-position" + eup "simrs-vx/internal/domain/main-entities/unit-position" + eu "simrs-vx/internal/domain/main-entities/user" + erg "simrs-vx/internal/domain/references/organization" + + udp "simrs-vx/internal/use-case/main-use-case/division-position" + uip "simrs-vx/internal/use-case/main-use-case/installation-position" + usp "simrs-vx/internal/use-case/main-use-case/specialist-position" + ussp "simrs-vx/internal/use-case/main-use-case/subspecialist-position" + uup "simrs-vx/internal/use-case/main-use-case/unit-position" +) + +// just return the error code +func getAndCheck(input, condition any, includes any) (eCode string) { + qry := dg.I.Where(condition) + + // WARNING THIS PRELOAD FAILS + if includes != nil { + if val := includes.(string); val != "" { + qry = qry.Preload(val) + } else if vals := includes.([]string); len(vals) > 0 { + for _, val := range vals { + qry = qry.Preload(val) + } + } + } + + result := qry.First(&input) + if result.Error != nil { + return "fetch-fail" + } else if result.RowsAffected == 0 { + return "auth-login-incorrect" + } + + return "" +} + +func getDivisionPosition(employee_id uint, event *pl.Event) ([]string, error) { + var result []string + + // get data division_position based on employee_id + data, _, err := udp.ReadListData(edp.ReadListDto{FilterDto: edp.FilterDto{Employee_Id: &employee_id}}, event) + if err != nil { + return nil, err + } + + if len(data) > 0 { + for _, dp := range data { + result = append(result, "div-"+*dp.Division_Code) + } + } + + return result, nil +} + +func getInstallationPosition(employeeId uint, event *pl.Event) ([]string, error) { + var result []string + + // get data unit_position based on employee_id + data, _, err := uip.ReadListData(eip.ReadListDto{ + FilterDto: eip.FilterDto{Employee_Id: &employeeId}, + Includes: "installation"}, event) + if err != nil { + return nil, err + } + + if len(data) > 0 { + for _, dp := range data { + result = append(result, "inst-"+*dp.Installation_Code+"-"+dp.Code) + } + } + + return result, nil +} + +func getUnitPosition(employeeId uint, event *pl.Event) ([]string, error) { + var result []string + + // get data unit_position based on employee_id + data, _, err := uup.ReadListData(eup.ReadListDto{FilterDto: eup.FilterDto{Employee_Id: &employeeId}}, event) + if err != nil { + return nil, err + } + + if len(data) > 0 { + for _, dp := range data { + result = append(result, "unit-"+*dp.Unit_Code+"-"+dp.Code) + } + } + + return result, nil +} + +func getSpecialistPosition(employeeId uint, event *pl.Event) ([]string, error) { + var result []string + + // get data unit_position based on employee_id + data, _, err := usp.ReadListData(esp.ReadListDto{FilterDto: esp.FilterDto{Employee_Id: &employeeId}}, event) + if err != nil { + return nil, err + } + + if len(data) > 0 { + for _, dp := range data { + result = append(result, "spec-"+*dp.Specialist_Code+"-"+dp.Code) + } + } + + return result, nil +} + +func getSubspecialistPosition(employeeId uint, event *pl.Event) ([]string, error) { + var result []string + + // get data unit_position based on employee_id + data, _, err := ussp.ReadListData(essp.ReadListDto{ + FilterDto: essp.FilterDto{Employee_Id: &employeeId}, + Includes: "subspecialist"}, event) + if err != nil { + return nil, err + } + + if len(data) > 0 { + for _, dp := range data { + result = append(result, "subspec-"+dp.Subspecialist.Code+"-"+dp.Code) + } + } + + return result, nil +} + +func checkStrClaims(claim map[string]interface{}, key string) string { + if v, exist := claim[key]; exist && v != nil { + return v.(string) + } + return "" +} + +func checkStrPtrClaims(claim map[string]interface{}, key string) *string { + if v, exist := claim[key]; exist && v != nil { + val := v.(string) + return &val + } + return nil +} + +func checkUntPtrClaims(claim map[string]interface{}, key string) *uint { + if v, exist := claim[key]; exist && v != nil { + val := uint(v.(float64)) + return &val + } + return nil +} + +func populateRoles(user *eu.User, input eu.LoginDto, atClaims jwt.MapClaims, outputData d.II, event pl.Event) error { + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("uuid-gen-fail"), err)) + } + if input.Duration == 0 { + input.Duration = 24 * 60 + } + duration := time.Minute * time.Duration(input.Duration) + aUuid := id.String() + atExpires := time.Now().Add(duration).Unix() + + atClaims["uuid"] = aUuid + atClaims["exp"] = atExpires + atClaims["user_id"] = user.Id + atClaims["user_name"] = user.Name + atClaims["user_contractPosition_code"] = user.ContractPosition_Code + + outputData["user_id"] = user.Id + outputData["user_name"] = user.Name + outputData["user_contractPosition_code"] = user.ContractPosition_Code + + roles := []string{} + switch user.ContractPosition_Code { + case erg.CSCEmp: + // employee + employee := ee.Employee{} + dg.I.Where("\"User_Id\" = ?", user.Id).First(&employee) + if employee.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noEmployee", Message: pl.GenMessage("auth-noEmployee")}} + } + atClaims["employee_id"] = employee.Id + outputData["employee_id"] = employee.Id + roles = append(roles, "emp-"+string(*employee.Position_Code)) + + // employee position + if employee.Id > 0 && employee.Position_Code != nil { + atClaims["employee_position_code"] = *employee.Position_Code + switch *employee.Position_Code { + case erg.EPCDoc: + doctor := ed.Doctor{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&doctor) + if doctor.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noDoctor", Message: pl.GenMessage("auth-noDoctor")}} + } + atClaims["doctor_code"] = doctor.Code + outputData["doctor_code"] = doctor.Code + + // specialist + if doctor.Specialist_Code != nil { + atClaims["specialist_code"] = doctor.Specialist_Code + outputData["specialist_code"] = doctor.Specialist_Code + } + if doctor.Subspecialist_Code != nil { + atClaims["subspecialist_code"] = doctor.Subspecialist_Code + outputData["subspecialist_code"] = doctor.Subspecialist_Code + } + case erg.EPCNur: + empData := en.Nurse{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + } + atClaims["nurse_code"] = empData.Code + outputData["nurse_code"] = empData.Code + case erg.EPCMwi: + empData := em.Midwife{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + } + atClaims["midwife_code"] = empData.Code + outputData["midwife_code"] = empData.Code + } + + errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} + + // division position + divisionPositions, err := getDivisionPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // installation position + installationPositions, err := getInstallationPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // unit position + unitPositions, err := getUnitPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // specialist position + specialistPositions, err := getSpecialistPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // subspecialist position + subspecialistPositions, err := getSubspecialistPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + roles = append(roles, divisionPositions...) + roles = append(roles, installationPositions...) + roles = append(roles, unitPositions...) + roles = append(roles, specialistPositions...) + roles = append(roles, subspecialistPositions...) + // atClaims["division_positions"] = divsionPositions + // outputData["division_positions"] = divsionPositions + } + case erg.CSCInt: + intern := intern.Intern{} + dg.I.Where("\"User_Id\" = ?", user.Id).First(&intern) + roles = append(roles, "int-"+string(*intern.Position_Code)) + case erg.CSCSys: + roles = append(roles, "system") + } + atClaims["roles"] = roles + outputData["roles"] = roles + + // Generate jwt + atSecretKey := authCfg.AtSecretKey + at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) + ats, err := at.SignedString([]byte(atSecretKey)) + if err != nil { + return d.FieldErrors{"user": d.FieldError{Code: "token-sign-err", Message: pl.GenMessage("token-sign-err")}} + } + outputData["accessToken"] = ats + + // Save to redis + exp, _ := atClaims["exp"].(int64) + now := time.Now() + atx := time.Unix(exp, 0) //converting Unix to UTC(to Time object) + err = ms.I.Set(atClaims["uuid"].(string), strconv.Itoa(int(user.Id)), atx.Sub(now)).Err() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("redis-store-fail"), err.Error())) + } + + return nil +} diff --git a/internal/use-case/main-use-case/medicine-form/case.go b/internal/use-case/main-use-case/medicine-form/case.go new file mode 100644 index 00000000..6eb0fb75 --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/case.go @@ -0,0 +1,276 @@ +package medicineform + +import ( + e "simrs-vx/internal/domain/main-entities/medicine-form" + "strconv" + + dg "github.com/karincake/apem/db-gorm-pg" + d "github.com/karincake/dodol" + + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" +) + +const source = "medicine-form" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.MedicineForm{} + + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + err := dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCreateMiddleware(createPreMw, &input, &data); err != nil { + return err + } + + if resData, err := CreateData(input, &event, tx); err != nil { + return err + } else { + data = *resData + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunCreateMiddleware(createPostMw, &input, &data); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + Data: data.ToResponse(), + }, nil +} + +func ReadList(input e.ReadListDto) (*d.Data, error) { + var data *e.MedicineForm + var dataList []e.MedicineForm + var metaList *e.MetaDto + var err error + + event := pl.Event{ + Feature: "ReadList", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readList") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadListMiddleware(readListPreMw, &input, data); err != nil { + return err + } + + if dataList, metaList, err = ReadListData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadListMiddleware(readListPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "list-data", + "status": "fetched", + "page_number": strconv.Itoa(metaList.PageNumber), + "page_size": strconv.Itoa(metaList.PageSize), + "record_totalCount": strconv.Itoa(metaList.Count), + "record_currentCount": strconv.Itoa(len(dataList)), + }, + Data: e.ToResponseList(dataList), + }, nil +} + +func ReadDetail(input e.ReadDetailDto) (*d.Data, error) { + var data *e.MedicineForm + var err error + + event := pl.Event{ + Feature: "ReadDetail", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readDetail") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPreMw, &input, data); err != nil { + return err + } + + if data, err = ReadDetailData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "fetched", + }, + Data: data.ToResponse(), + }, nil +} + +func Update(input e.UpdateDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Code: input.Code} + var data *e.MedicineForm + var err error + + event := pl.Event{ + Feature: "Update", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "update") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { + return err + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := UpdateData(input, data, &event, tx); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "updated", + }, + Data: data.ToResponse(), + }, nil + +} + +func Delete(input e.DeleteDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Code: input.Code} + var data *e.MedicineForm + var err error + + event := pl.Event{ + Feature: "Delete", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "delete") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { + return err + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := DeleteData(data, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "deleted", + }, + Data: data.ToResponse(), + }, nil + +} diff --git a/internal/use-case/main-use-case/medicine-form/helper.go b/internal/use-case/main-use-case/medicine-form/helper.go new file mode 100644 index 00000000..8e80369c --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/helper.go @@ -0,0 +1,22 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package medicineform + +import ( + e "simrs-vx/internal/domain/main-entities/medicine-form" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.MedicineForm) { + var inputSrc *e.CreateDto + if inputT, ok := any(input).(*e.CreateDto); ok { + inputSrc = inputT + } else { + inputTemp := any(input).(*e.UpdateDto) + inputSrc = &inputTemp.CreateDto + } + + data.Code = *inputSrc.Code + data.Name = inputSrc.Name +} diff --git a/internal/use-case/main-use-case/medicine-form/lib.go b/internal/use-case/main-use-case/medicine-form/lib.go new file mode 100644 index 00000000..01d570a2 --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/lib.go @@ -0,0 +1,147 @@ +package medicineform + +import ( + e "simrs-vx/internal/domain/main-entities/medicine-form" + + plh "simrs-vx/pkg/lib-helper" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + dg "github.com/karincake/apem/db-gorm-pg" + gh "github.com/karincake/getuk" + "gorm.io/gorm" +) + +func CreateData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*e.MedicineForm, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.MedicineForm{} + setData(&input, &data) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Create(&data).Error; err != nil { + return nil, plh.HandleCreateError(input, event, err) + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.MedicineForm, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.MedicineForm{} + pagination := gh.Pagination{} + count := int64(0) + meta := e.MetaDto{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + tx = tx. + Model(&e.MedicineForm{}). + Scopes(gh.Preload(input.Includes)). + Scopes(gh.Filter(input.FilterDto)). + Count(&count). + Scopes(gh.Paginate(input, &pagination)). + Scopes(gh.Sort(input.Sort)) + + if err := tx.Find(&data).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, &meta, nil + } + return nil, nil, plh.HandleListError(input, event, err) + } + + meta.Count = int(count) + meta.PageNumber = pagination.PageNumber + meta.PageSize = pagination.PageSize + + pl.SetLogInfo(event, nil, "complete") + return data, &meta, nil +} + +func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e.MedicineForm, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.MedicineForm{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if input.Code != nil { + tx = tx.Where("\"Code\" = ?", *input.Code) + } + if input.Id != nil { + tx = tx.Where("\"Id\" = ?", input.Id) + } + + if err := tx.First(&data).Error; err != nil { + if processedErr := pu.HandleReadError(err, event, source, input.Id, data); processedErr != nil { + return nil, processedErr + } + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func UpdateData(input e.UpdateDto, data *e.MedicineForm, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBUpdate") + setData(&input, data) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Save(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-update-fail", + Detail: "Database update failed", + Raw: err, + } + return pl.SetLogError(event, input) + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} + +func DeleteData(data *e.MedicineForm, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBDelete") + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Delete(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-delete-fail", + Detail: "Database delete failed", + Raw: err, + } + return pl.SetLogError(event, data) + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} diff --git a/internal/use-case/main-use-case/medicine-form/middleware-runner.go b/internal/use-case/main-use-case/medicine-form/middleware-runner.go new file mode 100644 index 00000000..9b22189b --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/middleware-runner.go @@ -0,0 +1,103 @@ +package medicineform + +import ( + e "simrs-vx/internal/domain/main-entities/medicine-form" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" +) + +type middlewareRunner struct { + Event *pl.Event + Tx *gorm.DB + MwType pu.MWType +} + +// NewMiddlewareExecutor creates a new middleware executor +func newMiddlewareRunner(event *pl.Event, tx *gorm.DB) *middlewareRunner { + return &middlewareRunner{ + Event: event, + Tx: tx, + } +} + +// ExecuteCreateMiddleware executes create middleware +func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e.CreateDto, data *e.MedicineForm) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, input *e.ReadListDto, data *e.MedicineForm) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.MedicineForm) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.MedicineForm) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunDeleteMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.MedicineForm) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) setMwType(mwType pu.MWType) { + me.MwType = mwType +} diff --git a/internal/use-case/main-use-case/medicine-form/middleware.go b/internal/use-case/main-use-case/medicine-form/middleware.go new file mode 100644 index 00000000..b4388010 --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/middleware.go @@ -0,0 +1,9 @@ +package medicineform + +// example of middleware +// func init() { +// createPreMw = append(createPreMw, +// CreateMw{Name: "modif-input", Func: pm.ModifInput}, +// CreateMw{Name: "check-data", Func: pm.CheckData}, +// ) +// } diff --git a/internal/use-case/main-use-case/medicine-form/tycovar.go b/internal/use-case/main-use-case/medicine-form/tycovar.go new file mode 100644 index 00000000..8dbd3509 --- /dev/null +++ b/internal/use-case/main-use-case/medicine-form/tycovar.go @@ -0,0 +1,44 @@ +/* +DESCRIPTION: +A sample, part of the package that contains type, constants, and/or variables. + +In this sample it also provides type and variable regarding the needs of the +middleware to separate from main use-case which has the basic CRUD +functionality. The purpose of this is to make the code more maintainable. +*/ +package medicineform + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/medicine-form" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.MedicineForm, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.MedicineForm, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.MedicineForm, tx *gorm.DB) error +} + +type UpdateMw = readDetailMw +type DeleteMw = readDetailMw + +var createPreMw []createMw // preprocess middleware +var createPostMw []createMw // postprocess middleware +var readListPreMw []readListMw // .. +var readListPostMw []readListMw // .. +var readDetailPreMw []readDetailMw +var readDetailPostMw []readDetailMw +var updatePreMw []readDetailMw +var updatePostMw []readDetailMw +var deletePreMw []readDetailMw +var deletePostMw []readDetailMw diff --git a/internal/use-case/main-use-case/medicine/lib.go b/internal/use-case/main-use-case/medicine/lib.go index f9fccb2e..8dfc4457 100644 --- a/internal/use-case/main-use-case/medicine/lib.go +++ b/internal/use-case/main-use-case/medicine/lib.go @@ -1,6 +1,7 @@ package medicine import ( + "fmt" e "simrs-vx/internal/domain/main-entities/medicine" plh "simrs-vx/pkg/lib-helper" @@ -47,6 +48,7 @@ func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.Me tx = dg.I } + fmt.Println(input.Includes) tx = tx. Model(&e.Medicine{}). Scopes(gh.Preload(input.Includes)). diff --git a/internal/use-case/main-use-case/prescription/case.go b/internal/use-case/main-use-case/prescription/case.go index fa68ec44..e67ec03d 100644 --- a/internal/use-case/main-use-case/prescription/case.go +++ b/internal/use-case/main-use-case/prescription/case.go @@ -279,6 +279,69 @@ func Delete(input e.DeleteDto) (*d.Data, error) { } +func Submit(input e.ReadDetailDto) (*d.Data, error) { + var data *e.Prescription + var err error + + event := pl.Event{ + Feature: "Process", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "process") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + data, err = ReadDetailData(input, &event, tx) + if err != nil { + return err + } + + if data.IsNotNew() { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-state-mismatch", + Detail: "prescription is not in new state", + Raw: errors.New("prescription is not in new state"), + } + return pl.SetLogError(&event, input) + } + + data.Status_Code = erc.DSCSubmited + if err := tx.Save(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-update-fail", + Detail: "Database update failed", + Raw: err, + } + return pl.SetLogError(&event, input) + } + + if err := createMedication(input.Id, &event, tx); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "submited", + }, + Data: data.ToResponse(), + }, nil + +} + func Approve(input e.ReadDetailDto) (*d.Data, error) { var data *e.Prescription var err error