diff --git a/docs/docs.go b/docs/docs.go index 4394e53e..e5f2c67a 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -215,67 +215,6 @@ const docTemplate = `{ } } }, - "/api/v1/bpjs/Peserta/nik/{nik}/tglSEP/{tglSEP}": { - "get": { - "description": "Search participant data based on Population NIK and service date", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "BPJS" - ], - "summary": "Get participant data by NIK", - "parameters": [ - { - "type": "string", - "description": "NIK KTP", - "name": "nik", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Service date/SEP date (format: yyyy-MM-dd)", - "name": "tglSEP", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Participant data", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "400": { - "description": "Bad request", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "404": { - "description": "Participant not found", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - } - }, "/api/v1/retribusi/{id}": { "get": { "description": "Returns a single retribusi by ID", @@ -761,9 +700,14 @@ const docTemplate = `{ } } }, - "/sep": { - "put": { - "description": "Update Surat Eligibilitas Peserta", + "/peserta/:nokartu": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get participant eligibility information", "consumes": [ "application/json" ], @@ -771,89 +715,49 @@ const docTemplate = `{ "application/json" ], "tags": [ - "SEP" + "vclaim", + "peserta" ], - "summary": "Update SEP", + "summary": "Get Peserta data", "parameters": [ { - "description": "SEP update request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepPutRequest" - } + "type": "string", + "description": "Nokartu", + "name": "nokartu", + "in": "path", + "required": true } ], "responses": { "200": { - "description": "SEP updated successfully", + "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepResponse" + "$ref": "#/definitions/reference.PesertaResponse" } }, "400": { - "description": "Invalid request", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" } }, "500": { - "description": "Internal server error", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/gin.H" - } - } - } - }, - "post": { - "description": "Create a new Surat Eligibilitas Peserta", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "SEP" - ], - "summary": "Create a new SEP", - "parameters": [ - { - "description": "SEP creation request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepPostRequest" - } - } - ], - "responses": { - "200": { - "description": "SEP created successfully", - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepResponse" - } - }, - "400": { - "description": "Invalid request", - "schema": { - "$ref": "#/definitions/gin.H" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" } } } } }, - "/sep/{noSep}": { + "/rujukan/:norujukan": { "get": { - "description": "Retrieve a Surat Eligibilitas Peserta by noSep", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get referral information", "consumes": [ "application/json" ], @@ -861,41 +765,206 @@ const docTemplate = `{ "application/json" ], "tags": [ - "SEP" + "vclaim", + "rujukan" ], - "summary": "Get SEP", + "summary": "Get Rujukan data", "parameters": [ { "type": "string", - "description": "No SEP", - "name": "noSep", + "description": "Norujukan", + "name": "norujukan", "in": "path", "required": true } ], "responses": { "200": { - "description": "Data SEP retrieved successfully", + "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepResponse" + "$ref": "#/definitions/reference.RujukanResponse" } }, "400": { - "description": "Invalid request", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" } }, "500": { - "description": "Internal server error", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" + } + } + } + } + }, + "/sep": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Manage SEP (Surat Eligibilitas Peserta)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "vclaim", + "sep" + ], + "summary": "Create Sep", + "parameters": [ + { + "description": "Sep data", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/reference.SEPRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/reference.SEPResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" + } + } + } + } + }, + "/sep/:nosep": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Manage SEP (Surat Eligibilitas Peserta)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "vclaim", + "sep" + ], + "summary": "Get Sep data", + "parameters": [ + { + "type": "string", + "description": "Nosep", + "name": "nosep", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/reference.SEPResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Manage SEP (Surat Eligibilitas Peserta)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "vclaim", + "sep" + ], + "summary": "Update Sep", + "parameters": [ + { + "type": "string", + "description": "Nosep", + "name": "nosep", + "in": "path", + "required": true + }, + { + "description": "Sep data", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/reference.SEPRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/reference.SEPResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" } } } }, "delete": { - "description": "Delete a Surat Eligibilitas Peserta by noSep", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Manage SEP (Surat Eligibilitas Peserta)", "consumes": [ "application/json" ], @@ -903,42 +972,36 @@ const docTemplate = `{ "application/json" ], "tags": [ - "SEP" + "vclaim", + "sep" ], - "summary": "Delete SEP", + "summary": "Delete Sep", "parameters": [ { "type": "string", - "description": "No SEP", - "name": "noSep", + "description": "Nosep", + "name": "nosep", "in": "path", "required": true - }, - { - "type": "string", - "description": "User", - "name": "user", - "in": "query", - "required": true } ], "responses": { "200": { - "description": "SEP deleted successfully", + "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepResponse" + "$ref": "#/definitions/reference.BaseResponse" } }, "400": { - "description": "Invalid request", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" } }, "500": { - "description": "Internal server error", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" } } } @@ -1371,27 +1434,403 @@ const docTemplate = `{ } } }, - "api-service_internal_models_vclaim.SepPostRequest": { - "type": "object" - }, - "api-service_internal_models_vclaim.SepPutRequest": { - "type": "object" - }, - "api-service_internal_models_vclaim.SepResponse": { + "reference.BaseResponse": { "type": "object", "properties": { - "data": { + "message": { + "type": "string" + }, + "request_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + }, + "reference.ErrorResponse": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "errors": { "type": "object", "additionalProperties": true }, "message": { "type": "string" + }, + "request_id": { + "type": "string" + }, + "status": { + "type": "string" } } }, - "gin.H": { + "reference.PesertaData": { "type": "object", - "additionalProperties": {} + "properties": { + "aktif": { + "type": "string" + }, + "asuransi": { + "type": "string" + }, + "cob": { + "type": "string" + }, + "kdCabang": { + "type": "string" + }, + "kdJnsPst": { + "type": "string" + }, + "ketAktif": { + "type": "string" + }, + "klsRawat": { + "type": "string" + }, + "mr": { + "type": "object", + "properties": { + "nmMR": { + "type": "string" + }, + "noMR": { + "type": "string" + }, + "sex": { + "type": "string" + }, + "tglLahir": { + "type": "string" + }, + "tglMeninggal": { + "type": "string" + } + } + }, + "nama": { + "type": "string" + }, + "nik": { + "type": "string" + }, + "nmCabang": { + "type": "string" + }, + "nmJnsPst": { + "type": "string" + }, + "noKartu": { + "type": "string" + }, + "noKtp": { + "type": "string" + }, + "noSKTM": { + "type": "string" + }, + "pisa": { + "type": "string" + }, + "sex": { + "type": "string" + }, + "statusPeserta": { + "type": "string" + }, + "tglLahir": { + "type": "string" + }, + "tglTAT": { + "type": "string" + }, + "tglTMT": { + "type": "string" + }, + "tglTunggak": { + "type": "string" + } + } + }, + "reference.PesertaResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/reference.PesertaData" + }, + "message": { + "type": "string" + }, + "metaData": {}, + "request_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + }, + "reference.RujukanData": { + "type": "object", + "properties": { + "diagnosa": { + "type": "object", + "properties": { + "kdDiagnosa": { + "type": "string" + }, + "nmDiagnosa": { + "type": "string" + } + } + }, + "kelasRawat": { + "type": "string" + }, + "nama": { + "type": "string" + }, + "noKartu": { + "type": "string" + }, + "noRujukan": { + "type": "string" + }, + "pelayanan": { + "type": "string" + }, + "poliRujukan": { + "type": "object", + "properties": { + "kdPoli": { + "type": "string" + }, + "nmPoli": { + "type": "string" + } + } + }, + "provPerujuk": { + "type": "object", + "properties": { + "kdProvider": { + "type": "string" + }, + "nmProvider": { + "type": "string" + } + } + }, + "statusRujukan": { + "type": "string" + }, + "tglRujukan": { + "type": "string" + } + } + }, + "reference.RujukanResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/reference.RujukanData" + }, + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/reference.RujukanData" + } + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + }, + "reference.SEPData": { + "type": "object", + "properties": { + "catatan": { + "type": "string" + }, + "diagnosa": { + "type": "string" + }, + "informasi": { + "type": "object", + "properties": { + "dpjpLayan": { + "type": "string" + }, + "noSKDP": { + "type": "string" + }, + "noTelp": { + "type": "string" + }, + "subSpesialis": { + "type": "string" + } + } + }, + "jnsPelayanan": { + "type": "string" + }, + "klsRawat": { + "type": "string" + }, + "noMR": { + "type": "string" + }, + "noSep": { + "type": "string" + }, + "peserta": { + "$ref": "#/definitions/reference.PesertaData" + }, + "poli": { + "type": "string" + }, + "rujukan": { + "$ref": "#/definitions/reference.SEPRujukan" + }, + "tglSep": { + "type": "string" + } + } + }, + "reference.SEPRequest": { + "type": "object", + "required": [ + "diagnosa", + "jnsPelayanan", + "klsRawat", + "noKartu", + "noMR", + "poli", + "ppkPelayanan", + "tglSep", + "user" + ], + "properties": { + "catatan": { + "type": "string" + }, + "diagnosa": { + "type": "string" + }, + "jnsPelayanan": { + "type": "string", + "enum": [ + "1", + "2" + ] + }, + "klsRawat": { + "type": "string", + "enum": [ + "1", + "2", + "3" + ] + }, + "noKartu": { + "type": "string" + }, + "noMR": { + "type": "string" + }, + "noTelp": { + "type": "string" + }, + "poli": { + "type": "string" + }, + "ppkPelayanan": { + "type": "string" + }, + "request_id": { + "type": "string" + }, + "rujukan": { + "$ref": "#/definitions/reference.SEPRujukan" + }, + "tglSep": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, + "reference.SEPResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/reference.SEPData" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + }, + "reference.SEPRujukan": { + "type": "object", + "required": [ + "asalRujukan", + "noRujukan", + "ppkRujukan", + "tglRujukan" + ], + "properties": { + "asalRujukan": { + "type": "string", + "enum": [ + "1", + "2" + ] + }, + "noRujukan": { + "type": "string" + }, + "ppkRujukan": { + "type": "string" + }, + "tglRujukan": { + "type": "string" + } + } }, "sql.NullString": { "type": "object", diff --git a/docs/swagger.json b/docs/swagger.json index 8b222e66..9dcb12d9 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -212,67 +212,6 @@ } } }, - "/api/v1/bpjs/Peserta/nik/{nik}/tglSEP/{tglSEP}": { - "get": { - "description": "Search participant data based on Population NIK and service date", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "BPJS" - ], - "summary": "Get participant data by NIK", - "parameters": [ - { - "type": "string", - "description": "NIK KTP", - "name": "nik", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Service date/SEP date (format: yyyy-MM-dd)", - "name": "tglSEP", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Participant data", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "400": { - "description": "Bad request", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "404": { - "description": "Participant not found", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - } - }, "/api/v1/retribusi/{id}": { "get": { "description": "Returns a single retribusi by ID", @@ -758,9 +697,14 @@ } } }, - "/sep": { - "put": { - "description": "Update Surat Eligibilitas Peserta", + "/peserta/:nokartu": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get participant eligibility information", "consumes": [ "application/json" ], @@ -768,89 +712,49 @@ "application/json" ], "tags": [ - "SEP" + "vclaim", + "peserta" ], - "summary": "Update SEP", + "summary": "Get Peserta data", "parameters": [ { - "description": "SEP update request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepPutRequest" - } + "type": "string", + "description": "Nokartu", + "name": "nokartu", + "in": "path", + "required": true } ], "responses": { "200": { - "description": "SEP updated successfully", + "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepResponse" + "$ref": "#/definitions/reference.PesertaResponse" } }, "400": { - "description": "Invalid request", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" } }, "500": { - "description": "Internal server error", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/gin.H" - } - } - } - }, - "post": { - "description": "Create a new Surat Eligibilitas Peserta", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "SEP" - ], - "summary": "Create a new SEP", - "parameters": [ - { - "description": "SEP creation request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepPostRequest" - } - } - ], - "responses": { - "200": { - "description": "SEP created successfully", - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepResponse" - } - }, - "400": { - "description": "Invalid request", - "schema": { - "$ref": "#/definitions/gin.H" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" } } } } }, - "/sep/{noSep}": { + "/rujukan/:norujukan": { "get": { - "description": "Retrieve a Surat Eligibilitas Peserta by noSep", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get referral information", "consumes": [ "application/json" ], @@ -858,41 +762,206 @@ "application/json" ], "tags": [ - "SEP" + "vclaim", + "rujukan" ], - "summary": "Get SEP", + "summary": "Get Rujukan data", "parameters": [ { "type": "string", - "description": "No SEP", - "name": "noSep", + "description": "Norujukan", + "name": "norujukan", "in": "path", "required": true } ], "responses": { "200": { - "description": "Data SEP retrieved successfully", + "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepResponse" + "$ref": "#/definitions/reference.RujukanResponse" } }, "400": { - "description": "Invalid request", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" } }, "500": { - "description": "Internal server error", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" + } + } + } + } + }, + "/sep": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Manage SEP (Surat Eligibilitas Peserta)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "vclaim", + "sep" + ], + "summary": "Create Sep", + "parameters": [ + { + "description": "Sep data", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/reference.SEPRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/reference.SEPResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" + } + } + } + } + }, + "/sep/:nosep": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Manage SEP (Surat Eligibilitas Peserta)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "vclaim", + "sep" + ], + "summary": "Get Sep data", + "parameters": [ + { + "type": "string", + "description": "Nosep", + "name": "nosep", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/reference.SEPResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Manage SEP (Surat Eligibilitas Peserta)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "vclaim", + "sep" + ], + "summary": "Update Sep", + "parameters": [ + { + "type": "string", + "description": "Nosep", + "name": "nosep", + "in": "path", + "required": true + }, + { + "description": "Sep data", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/reference.SEPRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/reference.SEPResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/reference.ErrorResponse" } } } }, "delete": { - "description": "Delete a Surat Eligibilitas Peserta by noSep", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Manage SEP (Surat Eligibilitas Peserta)", "consumes": [ "application/json" ], @@ -900,42 +969,36 @@ "application/json" ], "tags": [ - "SEP" + "vclaim", + "sep" ], - "summary": "Delete SEP", + "summary": "Delete Sep", "parameters": [ { "type": "string", - "description": "No SEP", - "name": "noSep", + "description": "Nosep", + "name": "nosep", "in": "path", "required": true - }, - { - "type": "string", - "description": "User", - "name": "user", - "in": "query", - "required": true } ], "responses": { "200": { - "description": "SEP deleted successfully", + "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim.SepResponse" + "$ref": "#/definitions/reference.BaseResponse" } }, "400": { - "description": "Invalid request", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" } }, "500": { - "description": "Internal server error", + "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/gin.H" + "$ref": "#/definitions/reference.ErrorResponse" } } } @@ -1368,27 +1431,403 @@ } } }, - "api-service_internal_models_vclaim.SepPostRequest": { - "type": "object" - }, - "api-service_internal_models_vclaim.SepPutRequest": { - "type": "object" - }, - "api-service_internal_models_vclaim.SepResponse": { + "reference.BaseResponse": { "type": "object", "properties": { - "data": { + "message": { + "type": "string" + }, + "request_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + }, + "reference.ErrorResponse": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "errors": { "type": "object", "additionalProperties": true }, "message": { "type": "string" + }, + "request_id": { + "type": "string" + }, + "status": { + "type": "string" } } }, - "gin.H": { + "reference.PesertaData": { "type": "object", - "additionalProperties": {} + "properties": { + "aktif": { + "type": "string" + }, + "asuransi": { + "type": "string" + }, + "cob": { + "type": "string" + }, + "kdCabang": { + "type": "string" + }, + "kdJnsPst": { + "type": "string" + }, + "ketAktif": { + "type": "string" + }, + "klsRawat": { + "type": "string" + }, + "mr": { + "type": "object", + "properties": { + "nmMR": { + "type": "string" + }, + "noMR": { + "type": "string" + }, + "sex": { + "type": "string" + }, + "tglLahir": { + "type": "string" + }, + "tglMeninggal": { + "type": "string" + } + } + }, + "nama": { + "type": "string" + }, + "nik": { + "type": "string" + }, + "nmCabang": { + "type": "string" + }, + "nmJnsPst": { + "type": "string" + }, + "noKartu": { + "type": "string" + }, + "noKtp": { + "type": "string" + }, + "noSKTM": { + "type": "string" + }, + "pisa": { + "type": "string" + }, + "sex": { + "type": "string" + }, + "statusPeserta": { + "type": "string" + }, + "tglLahir": { + "type": "string" + }, + "tglTAT": { + "type": "string" + }, + "tglTMT": { + "type": "string" + }, + "tglTunggak": { + "type": "string" + } + } + }, + "reference.PesertaResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/reference.PesertaData" + }, + "message": { + "type": "string" + }, + "metaData": {}, + "request_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + }, + "reference.RujukanData": { + "type": "object", + "properties": { + "diagnosa": { + "type": "object", + "properties": { + "kdDiagnosa": { + "type": "string" + }, + "nmDiagnosa": { + "type": "string" + } + } + }, + "kelasRawat": { + "type": "string" + }, + "nama": { + "type": "string" + }, + "noKartu": { + "type": "string" + }, + "noRujukan": { + "type": "string" + }, + "pelayanan": { + "type": "string" + }, + "poliRujukan": { + "type": "object", + "properties": { + "kdPoli": { + "type": "string" + }, + "nmPoli": { + "type": "string" + } + } + }, + "provPerujuk": { + "type": "object", + "properties": { + "kdProvider": { + "type": "string" + }, + "nmProvider": { + "type": "string" + } + } + }, + "statusRujukan": { + "type": "string" + }, + "tglRujukan": { + "type": "string" + } + } + }, + "reference.RujukanResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/reference.RujukanData" + }, + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/reference.RujukanData" + } + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + }, + "reference.SEPData": { + "type": "object", + "properties": { + "catatan": { + "type": "string" + }, + "diagnosa": { + "type": "string" + }, + "informasi": { + "type": "object", + "properties": { + "dpjpLayan": { + "type": "string" + }, + "noSKDP": { + "type": "string" + }, + "noTelp": { + "type": "string" + }, + "subSpesialis": { + "type": "string" + } + } + }, + "jnsPelayanan": { + "type": "string" + }, + "klsRawat": { + "type": "string" + }, + "noMR": { + "type": "string" + }, + "noSep": { + "type": "string" + }, + "peserta": { + "$ref": "#/definitions/reference.PesertaData" + }, + "poli": { + "type": "string" + }, + "rujukan": { + "$ref": "#/definitions/reference.SEPRujukan" + }, + "tglSep": { + "type": "string" + } + } + }, + "reference.SEPRequest": { + "type": "object", + "required": [ + "diagnosa", + "jnsPelayanan", + "klsRawat", + "noKartu", + "noMR", + "poli", + "ppkPelayanan", + "tglSep", + "user" + ], + "properties": { + "catatan": { + "type": "string" + }, + "diagnosa": { + "type": "string" + }, + "jnsPelayanan": { + "type": "string", + "enum": [ + "1", + "2" + ] + }, + "klsRawat": { + "type": "string", + "enum": [ + "1", + "2", + "3" + ] + }, + "noKartu": { + "type": "string" + }, + "noMR": { + "type": "string" + }, + "noTelp": { + "type": "string" + }, + "poli": { + "type": "string" + }, + "ppkPelayanan": { + "type": "string" + }, + "request_id": { + "type": "string" + }, + "rujukan": { + "$ref": "#/definitions/reference.SEPRujukan" + }, + "tglSep": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, + "reference.SEPResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/reference.SEPData" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + }, + "reference.SEPRujukan": { + "type": "object", + "required": [ + "asalRujukan", + "noRujukan", + "ppkRujukan", + "tglRujukan" + ], + "properties": { + "asalRujukan": { + "type": "string", + "enum": [ + "1", + "2" + ] + }, + "noRujukan": { + "type": "string" + }, + "ppkRujukan": { + "type": "string" + }, + "tglRujukan": { + "type": "string" + } + } }, "sql.NullString": { "type": "object", diff --git a/docs/swagger.yaml b/docs/swagger.yaml index cc1ecd49..6aec9559 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -292,20 +292,271 @@ definitions: message: type: string type: object - api-service_internal_models_vclaim.SepPostRequest: - type: object - api-service_internal_models_vclaim.SepPutRequest: - type: object - api-service_internal_models_vclaim.SepResponse: + reference.BaseResponse: properties: - data: + message: + type: string + request_id: + type: string + status: + type: string + timestamp: + type: string + type: object + reference.ErrorResponse: + properties: + code: + type: string + errors: additionalProperties: true type: object message: type: string + request_id: + type: string + status: + type: string type: object - gin.H: - additionalProperties: {} + reference.PesertaData: + properties: + aktif: + type: string + asuransi: + type: string + cob: + type: string + kdCabang: + type: string + kdJnsPst: + type: string + ketAktif: + type: string + klsRawat: + type: string + mr: + properties: + nmMR: + type: string + noMR: + type: string + sex: + type: string + tglLahir: + type: string + tglMeninggal: + type: string + type: object + nama: + type: string + nik: + type: string + nmCabang: + type: string + nmJnsPst: + type: string + noKartu: + type: string + noKtp: + type: string + noSKTM: + type: string + pisa: + type: string + sex: + type: string + statusPeserta: + type: string + tglLahir: + type: string + tglTAT: + type: string + tglTMT: + type: string + tglTunggak: + type: string + type: object + reference.PesertaResponse: + properties: + data: + $ref: '#/definitions/reference.PesertaData' + message: + type: string + metaData: {} + request_id: + type: string + status: + type: string + timestamp: + type: string + type: object + reference.RujukanData: + properties: + diagnosa: + properties: + kdDiagnosa: + type: string + nmDiagnosa: + type: string + type: object + kelasRawat: + type: string + nama: + type: string + noKartu: + type: string + noRujukan: + type: string + pelayanan: + type: string + poliRujukan: + properties: + kdPoli: + type: string + nmPoli: + type: string + type: object + provPerujuk: + properties: + kdProvider: + type: string + nmProvider: + type: string + type: object + statusRujukan: + type: string + tglRujukan: + type: string + type: object + reference.RujukanResponse: + properties: + data: + $ref: '#/definitions/reference.RujukanData' + list: + items: + $ref: '#/definitions/reference.RujukanData' + type: array + message: + type: string + request_id: + type: string + status: + type: string + timestamp: + type: string + type: object + reference.SEPData: + properties: + catatan: + type: string + diagnosa: + type: string + informasi: + properties: + dpjpLayan: + type: string + noSKDP: + type: string + noTelp: + type: string + subSpesialis: + type: string + type: object + jnsPelayanan: + type: string + klsRawat: + type: string + noMR: + type: string + noSep: + type: string + peserta: + $ref: '#/definitions/reference.PesertaData' + poli: + type: string + rujukan: + $ref: '#/definitions/reference.SEPRujukan' + tglSep: + type: string + type: object + reference.SEPRequest: + properties: + catatan: + type: string + diagnosa: + type: string + jnsPelayanan: + enum: + - "1" + - "2" + type: string + klsRawat: + enum: + - "1" + - "2" + - "3" + type: string + noKartu: + type: string + noMR: + type: string + noTelp: + type: string + poli: + type: string + ppkPelayanan: + type: string + request_id: + type: string + rujukan: + $ref: '#/definitions/reference.SEPRujukan' + tglSep: + type: string + timestamp: + type: string + user: + type: string + required: + - diagnosa + - jnsPelayanan + - klsRawat + - noKartu + - noMR + - poli + - ppkPelayanan + - tglSep + - user + type: object + reference.SEPResponse: + properties: + data: + $ref: '#/definitions/reference.SEPData' + message: + type: string + request_id: + type: string + status: + type: string + timestamp: + type: string + type: object + reference.SEPRujukan: + properties: + asalRujukan: + enum: + - "1" + - "2" + type: string + noRujukan: + type: string + ppkRujukan: + type: string + tglRujukan: + type: string + required: + - asalRujukan + - noRujukan + - ppkRujukan + - tglRujukan type: object sql.NullString: properties: @@ -460,48 +711,6 @@ paths: summary: Register new user tags: - Authentication - /api/v1/bpjs/Peserta/nik/{nik}/tglSEP/{tglSEP}: - get: - consumes: - - application/json - description: Search participant data based on Population NIK and service date - parameters: - - description: NIK KTP - in: path - name: nik - required: true - type: string - - description: 'Service date/SEP date (format: yyyy-MM-dd)' - in: path - name: tglSEP - required: true - type: string - produces: - - application/json - responses: - "200": - description: Participant data - schema: - additionalProperties: true - type: object - "400": - description: Bad request - schema: - additionalProperties: true - type: object - "404": - description: Participant not found - schema: - additionalProperties: true - type: object - "500": - description: Internal server error - schema: - additionalProperties: true - type: object - summary: Get participant data by NIK - tags: - - BPJS /api/v1/retribusi/{id}: delete: consumes: @@ -824,127 +1033,203 @@ paths: summary: Generate token directly tags: - Token + /peserta/:nokartu: + get: + consumes: + - application/json + description: Get participant eligibility information + parameters: + - description: Nokartu + in: path + name: nokartu + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/reference.PesertaResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/reference.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/reference.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get Peserta data + tags: + - vclaim + - peserta + /rujukan/:norujukan: + get: + consumes: + - application/json + description: Get referral information + parameters: + - description: Norujukan + in: path + name: norujukan + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/reference.RujukanResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/reference.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/reference.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get Rujukan data + tags: + - vclaim + - rujukan /sep: post: consumes: - application/json - description: Create a new Surat Eligibilitas Peserta + description: Manage SEP (Surat Eligibilitas Peserta) parameters: - - description: SEP creation request + - description: Sep data in: body name: request required: true schema: - $ref: '#/definitions/api-service_internal_models_vclaim.SepPostRequest' + $ref: '#/definitions/reference.SEPRequest' produces: - application/json responses: - "200": - description: SEP created successfully + "201": + description: Created schema: - $ref: '#/definitions/api-service_internal_models_vclaim.SepResponse' + $ref: '#/definitions/reference.SEPResponse' "400": - description: Invalid request + description: Bad Request schema: - $ref: '#/definitions/gin.H' + $ref: '#/definitions/reference.ErrorResponse' "500": - description: Internal server error + description: Internal Server Error schema: - $ref: '#/definitions/gin.H' - summary: Create a new SEP + $ref: '#/definitions/reference.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Create Sep tags: - - SEP - put: - consumes: - - application/json - description: Update Surat Eligibilitas Peserta - parameters: - - description: SEP update request - in: body - name: request - required: true - schema: - $ref: '#/definitions/api-service_internal_models_vclaim.SepPutRequest' - produces: - - application/json - responses: - "200": - description: SEP updated successfully - schema: - $ref: '#/definitions/api-service_internal_models_vclaim.SepResponse' - "400": - description: Invalid request - schema: - $ref: '#/definitions/gin.H' - "500": - description: Internal server error - schema: - $ref: '#/definitions/gin.H' - summary: Update SEP - tags: - - SEP - /sep/{noSep}: + - vclaim + - sep + /sep/:nosep: delete: consumes: - application/json - description: Delete a Surat Eligibilitas Peserta by noSep + description: Manage SEP (Surat Eligibilitas Peserta) parameters: - - description: No SEP + - description: Nosep in: path - name: noSep - required: true - type: string - - description: User - in: query - name: user + name: nosep required: true type: string produces: - application/json responses: "200": - description: SEP deleted successfully + description: OK schema: - $ref: '#/definitions/api-service_internal_models_vclaim.SepResponse' + $ref: '#/definitions/reference.BaseResponse' "400": - description: Invalid request + description: Bad Request schema: - $ref: '#/definitions/gin.H' + $ref: '#/definitions/reference.ErrorResponse' "500": - description: Internal server error + description: Internal Server Error schema: - $ref: '#/definitions/gin.H' - summary: Delete SEP + $ref: '#/definitions/reference.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Delete Sep tags: - - SEP + - vclaim + - sep get: consumes: - application/json - description: Retrieve a Surat Eligibilitas Peserta by noSep + description: Manage SEP (Surat Eligibilitas Peserta) parameters: - - description: No SEP + - description: Nosep in: path - name: noSep + name: nosep required: true type: string produces: - application/json responses: "200": - description: Data SEP retrieved successfully + description: OK schema: - $ref: '#/definitions/api-service_internal_models_vclaim.SepResponse' + $ref: '#/definitions/reference.SEPResponse' "400": - description: Invalid request + description: Bad Request schema: - $ref: '#/definitions/gin.H' + $ref: '#/definitions/reference.ErrorResponse' "500": - description: Internal server error + description: Internal Server Error schema: - $ref: '#/definitions/gin.H' - summary: Get SEP + $ref: '#/definitions/reference.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get Sep data tags: - - SEP + - vclaim + - sep + put: + consumes: + - application/json + description: Manage SEP (Surat Eligibilitas Peserta) + parameters: + - description: Nosep + in: path + name: nosep + required: true + type: string + - description: Sep data + in: body + name: request + required: true + schema: + $ref: '#/definitions/reference.SEPRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/reference.SEPResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/reference.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/reference.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Update Sep + tags: + - vclaim + - sep schemes: - http - https diff --git a/internal/handlers/reference/peserta.go b/internal/handlers/reference/peserta.go deleted file mode 100644 index 1fc491f6..00000000 --- a/internal/handlers/reference/peserta.go +++ /dev/null @@ -1,92 +0,0 @@ -package handlers - -import ( - "context" - "fmt" - "net/http" - "time" - - "api-service/internal/config" - services "api-service/internal/services/bpjs" - - "github.com/gin-gonic/gin" -) - -// PesertaHandler handles BPJS participant operations -type PesertaHandler struct { - bpjsService services.VClaimService -} - -// NewPesertaHandler creates a new PesertaHandler instance -func NewPesertaHandler(cfg config.BpjsConfig) *PesertaHandler { - return &PesertaHandler{ - bpjsService: services.NewService(cfg), - } -} - -// GetPesertaByNIK godoc -// @Summary Get participant data by NIK -// @Description Search participant data based on Population NIK and service date -// @Tags BPJS -// @Accept json -// @Produce json -// @Param nik path string true "NIK KTP" -// @Param tglSEP path string true "Service date/SEP date (format: yyyy-MM-dd)" -// @Success 200 {object} map[string]interface{} "Participant data" -// @Failure 400 {object} map[string]interface{} "Bad request" -// @Failure 404 {object} map[string]interface{} "Participant not found" -// @Failure 500 {object} map[string]interface{} "Internal server error" -// @Router /api/v1/bpjs/Peserta/nik/{nik}/tglSEP/{tglSEP} [get] -func (h *PesertaHandler) GetPesertaByNIK(c *gin.Context) { - nik := c.Param("nik") - tglSEP := c.Param("tglSEP") - - // Validate parameters - if nik == "" { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "NIK parameter is required", - "message": "NIK KTP tidak boleh kosong", - }) - return - } - - if tglSEP == "" { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "tglSEP parameter is required", - "message": "Tanggal SEP tidak boleh kosong", - }) - return - } - - // Validate date format - if _, err := time.Parse("2006-01-02", tglSEP); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid date format", - "message": "Format tanggal harus yyyy-MM-dd", - }) - return - } - - // Create context with timeout - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Build endpoint URL - endpoint := fmt.Sprintf("/Peserta/nik/%s/tglSEP/%s", nik, tglSEP) - - // Call BPJS service - var result map[string]interface{} - if err := h.bpjsService.Get(ctx, endpoint, &result); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to fetch participant data", - "message": err.Error(), - }) - return - } - - // Return successful response - c.JSON(http.StatusOK, gin.H{ - "message": "Data peserta berhasil diambil", - "data": result, - }) -} diff --git a/internal/handlers/swagger/swagger.go b/internal/handlers/swagger/swagger.go deleted file mode 100644 index a8f2a952..00000000 --- a/internal/handlers/swagger/swagger.go +++ /dev/null @@ -1,100 +0,0 @@ -package swagger - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" - swaggerFiles "github.com/swaggo/files" - ginSwagger "github.com/swaggo/gin-swagger" - - "api-service/internal/config" -) - -// Handler handles Swagger documentation -type Handler struct { - config *config.Config -} - -// NewHandler creates a new Swagger handler -func NewHandler(cfg *config.Config) *Handler { - return &Handler{ - config: cfg, - } -} - -// RegisterRoutes registers Swagger routes -func (h *Handler) RegisterRoutes(router *gin.Engine) { - // Serve Swagger UI - router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) - - // Serve OpenAPI spec - router.GET("/openapi.json", h.serveOpenAPISpec) - router.GET("/openapi.yaml", h.serveOpenAPISpecYAML) -} - -// serveOpenAPISpec serves the OpenAPI JSON specification -func (h *Handler) serveOpenAPISpec(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "openapi": "3.0.0", - "info": map[string]interface{}{ - "title": h.config.Swagger.Title, - "description": h.config.Swagger.Description, - "version": h.config.Swagger.Version, - "termsOfService": h.config.Swagger.TermsOfService, - "contact": map[string]interface{}{ - "name": h.config.Swagger.ContactName, - "url": h.config.Swagger.ContactURL, - "email": h.config.Swagger.ContactEmail, - }, - "license": map[string]interface{}{ - "name": h.config.Swagger.LicenseName, - "url": h.config.Swagger.LicenseURL, - }, - }, - "servers": []map[string]interface{}{ - { - "url": strings.Join([]string{strings.ToLower(h.config.Swagger.Schemes[0]), "://", h.config.Swagger.Host, h.config.Swagger.BasePath}, ""), - "description": "API Server", - }, - }, - "paths": map[string]interface{}{}, - "components": map[string]interface{}{ - "schemas": map[string]interface{}{}, - "securitySchemes": map[string]interface{}{}, - }, - }) -} - -// serveOpenAPISpecYAML serves the OpenAPI YAML specification -func (h *Handler) serveOpenAPISpecYAML(c *gin.Context) { - c.YAML(http.StatusOK, map[string]interface{}{ - "openapi": "3.0.0", - "info": map[string]interface{}{ - "title": h.config.Swagger.Title, - "description": h.config.Swagger.Description, - "version": h.config.Swagger.Version, - "termsOfService": h.config.Swagger.TermsOfService, - "contact": map[string]interface{}{ - "name": h.config.Swagger.ContactName, - "url": h.config.Swagger.ContactURL, - "email": h.config.Swagger.ContactEmail, - }, - "license": map[string]interface{}{ - "name": h.config.Swagger.LicenseName, - "url": h.config.Swagger.LicenseURL, - }, - }, - "servers": []map[string]interface{}{ - { - "url": strings.Join([]string{strings.ToLower(h.config.Swagger.Schemes[0]), "://", h.config.Swagger.Host, h.config.Swagger.BasePath}, ""), - "description": "API Server", - }, - }, - "paths": map[string]interface{}{}, - "components": map[string]interface{}{ - "schemas": map[string]interface{}{}, - "securitySchemes": map[string]interface{}{}, - }, - }) -} diff --git a/internal/handlers/vclaim/sep.go b/internal/handlers/vclaim/sep.go deleted file mode 100644 index a1a4c8a8..00000000 --- a/internal/handlers/vclaim/sep.go +++ /dev/null @@ -1,190 +0,0 @@ -package handlers - -import ( - "context" - "fmt" - "net/http" - "time" - - "api-service/internal/config" - vclaimModels "api-service/internal/models/vclaim" - services "api-service/internal/services/bpjs" - - "github.com/gin-gonic/gin" -) - -type SepHandler struct { - service services.VClaimService -} - -func NewSepHandler(cfg config.BpjsConfig) *SepHandler { - return &SepHandler{ - service: services.NewService(cfg), - } -} - -// CreateSEP godoc -// @Summary Create a new SEP -// @Description Create a new Surat Eligibilitas Peserta -// @Tags SEP -// @Accept json -// @Produce json -// @Param request body vclaimModels.SepPostRequest true "SEP creation request" -// @Success 200 {object} vclaimModels.SepResponse "SEP created successfully" -// @Failure 400 {object} gin.H "Invalid request" -// @Failure 500 {object} gin.H "Internal server error" -// @Router /sep [post] -func (h *SepHandler) CreateSEP(c *gin.Context) { - var req vclaimModels.SepPostRequest - - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "invalid body", - "message": err.Error(), - }) - return - } - - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() - - var result map[string]interface{} - if err := h.service.Post(ctx, "SEP/2.0/insert", req, &result); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "create failed", - "message": err.Error(), - }) - return - } - - c.JSON(http.StatusOK, vclaimModels.SepResponse{ - Message: "SEP berhasil dibuat", - Data: result, - }) -} - -// UpdateSEP godoc -// @Summary Update SEP -// @Description Update Surat Eligibilitas Peserta -// @Tags SEP -// @Accept json -// @Produce json -// @Param request body vclaimModels.SepPutRequest true "SEP update request" -// @Success 200 {object} vclaimModels.SepResponse "SEP updated successfully" -// @Failure 400 {object} gin.H "Invalid request" -// @Failure 500 {object} gin.H "Internal server error" -// @Router /sep [put] -func (h *SepHandler) UpdateSEP(c *gin.Context) { - var req vclaimModels.SepPutRequest - - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "invalid body", - "message": err.Error(), - }) - return - } - - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() - - var result map[string]interface{} - if err := h.service.Put(ctx, "SEP/2.0/update", req, &result); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "update failed", - "message": err.Error(), - }) - return - } - - c.JSON(http.StatusOK, vclaimModels.SepResponse{ - Message: "SEP berhasil diperbarui", - Data: result, - }) -} - -// DeleteSEP godoc -// @Summary Delete SEP -// @Description Delete a Surat Eligibilitas Peserta by noSep -// @Tags SEP -// @Accept json -// @Produce json -// @Param noSep path string true "No SEP" -// @Param user query string true "User" -// @Success 200 {object} vclaimModels.SepResponse "SEP deleted successfully" -// @Failure 400 {object} gin.H "Invalid request" -// @Failure 500 {object} gin.H "Internal server error" -// @Router /sep/{noSep} [delete] -func (h *SepHandler) DeleteSEP(c *gin.Context) { - noSep := c.Param("noSep") - user := c.Query("user") - - if noSep == "" || user == "" { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "noSep and user required", - }) - return - } - - body := vclaimModels.SepDeleteRequest{} - body.TSep.NoSep = noSep - body.TSep.User = user - - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() - - if err := h.service.Delete(ctx, "SEP/2.0/delete", body); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "delete failed", - "message": err.Error(), - }) - return - } - - var result map[string]interface{} - c.JSON(http.StatusOK, vclaimModels.SepResponse{ - Message: "SEP berhasil dihapus", - Data: result, - }) -} - -// GetSEP godoc -// @Summary Get SEP -// @Description Retrieve a Surat Eligibilitas Peserta by noSep -// @Tags SEP -// @Accept json -// @Produce json -// @Param noSep path string true "No SEP" -// @Success 200 {object} vclaimModels.SepResponse "Data SEP retrieved successfully" -// @Failure 400 {object} gin.H "Invalid request" -// @Failure 500 {object} gin.H "Internal server error" -// @Router /sep/{noSep} [get] -func (h *SepHandler) GetSEP(c *gin.Context) { - noSep := c.Param("noSep") - - if noSep == "" { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "noSep required", - }) - return - } - - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("SEP/%s", noSep) - var result map[string]interface{} - - if err := h.service.Get(ctx, endpoint, &result); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "fetch failed", - "message": err.Error(), - }) - return - } - - c.JSON(http.StatusOK, vclaimModels.SepResponse{ - Message: "Data SEP berhasil diambil", - Data: result, - }) -} diff --git a/internal/handlers/vclaim_handler.go b/internal/handlers/vclaim_handler.go new file mode 100644 index 00000000..0110a12b --- /dev/null +++ b/internal/handlers/vclaim_handler.go @@ -0,0 +1,401 @@ + +// Code generated by generate-dynamic-handler.go; DO NOT EDIT. +// Generated at: 2025-08-28 17:47:19 +// Service: VClaim (vclaim) +// Description: BPJS VClaim service for eligibility and SEP management + +package handlers + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "api-service/internal/config" + "api-service/internal/models/reference" + "api-service/internal/services/bpjs" + "api-service/pkg/logger" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "github.com/google/uuid" +) + +// VClaimHandler handles VClaim BPJS services +type VClaimHandler struct { + service services.VClaimService + validator *validator.Validate + logger logger.Logger + config config.BpjsConfig +} + +// VClaimHandlerConfig contains configuration for VClaimHandler +type VClaimHandlerConfig struct { + BpjsConfig config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// NewVClaimHandler creates a new VClaimHandler +func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler { + return &VClaimHandler{ + service: services.NewService(cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +} + + + +// GetPESERTA retrieves Peserta data + +// @Summary Get Peserta data +// @Description Get participant eligibility information +// @Tags vclaim,peserta +// @Accept json +// @Produce json + +// @Security ApiKeyAuth + + +// @Param nokartu path string true "nokartu" + +// @Success 200 {object} reference.PesertaResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /Peserta/:nokartu [get] + +func (h *VClaimHandler) GetPESERTA(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + + h.logger.Info("Processing GetPeserta request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "/Peserta/:nokartu", + + "nokartu": c.Param("nokartu"), + + }) + + + // Extract path parameters + + nokartu := c.Param("nokartu") + if nokartu == "" { + + h.logger.Error("Missing required parameter nokartu", map[string]interface{}{ + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter nokartu", + RequestID: requestID, + }) + return + } + + + // Call service method + var response reference.PesertaResponse + + result, err := h.service.GetPESERTA(ctx, nokartu) + + if err != nil { + + h.logger.Error("Failed to get Peserta", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusOK, response) +} + + + + + +// GetSEP retrieves Sep data + +// @Summary Get Sep data +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json + +// @Security ApiKeyAuth + + +// @Param nosep path string true "nosep" + +// @Success 200 {object} reference.SEPResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /SEP/:nosep [get] + +func (h *VClaimHandler) GetSEP(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + + h.logger.Info("Processing GetSep request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "/SEP/:nosep", + + "nosep": c.Param("nosep"), + + }) + + + // Extract path parameters + + nosep := c.Param("nosep") + if nosep == "" { + + h.logger.Error("Missing required parameter nosep", map[string]interface{}{ + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter nosep", + RequestID: requestID, + }) + return + } + + + // Call service method + var response reference.SEPResponse + + result, err := h.service.GetSEP(ctx, nosep) + + if err != nil { + + h.logger.Error("Failed to get Sep", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusOK, response) +} + + + +// CreateSEP creates new Sep + +// @Summary Create Sep +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json + +// @Security ApiKeyAuth + +// @Param request body reference.SEPRequest true "Sep data" +// @Success 201 {object} reference.SEPResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /sep [post] + +func (h *VClaimHandler) CreateSEP(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + + h.logger.Info("Processing CreateSep request", map[string]interface{}{ + "request_id": requestID, + }) + + + var req reference.SEPRequest + if err := c.ShouldBindJSON(&req); err != nil { + + h.logger.Error("Invalid request body", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Invalid request body: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Validate request + if err := h.validator.Struct(&req); err != nil { + + h.logger.Error("Validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Validation failed: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.SEPResponse + result, err := h.service.CreateSEP(ctx, &req) + if err != nil { + + h.logger.Error("Failed to create Sep", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusCreated, response) +} + + + +// GetRUJUKAN retrieves Rujukan data + +// @Summary Get Rujukan data +// @Description Get referral information +// @Tags vclaim,rujukan +// @Accept json +// @Produce json + +// @Security ApiKeyAuth + + +// @Param norujukan path string true "norujukan" + +// @Success 200 {object} reference.RujukanResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /rujukan/:norujukan [get] + +func (h *VClaimHandler) GetRUJUKAN(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + + h.logger.Info("Processing GetRujukan request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "/rujukan/:norujukan", + + "norujukan": c.Param("norujukan"), + + }) + + + // Extract path parameters + + norujukan := c.Param("norujukan") + if norujukan == "" { + + h.logger.Error("Missing required parameter norujukan", map[string]interface{}{ + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter norujukan", + RequestID: requestID, + }) + return + } + + + // Call service method + var response reference.RujukanResponse + + result, err := h.service.GetRUJUKAN(ctx, norujukan) + + if err != nil { + + h.logger.Error("Failed to get Rujukan", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusOK, response) +} + + + + diff --git a/internal/models/reference/aplicare.go b/internal/models/reference/aplicare.go new file mode 100644 index 00000000..b1ea6cf3 --- /dev/null +++ b/internal/models/reference/aplicare.go @@ -0,0 +1,70 @@ +package reference + +// === REFERENSI MODELS === + +// ReferensiRequest represents referensi lookup request +type ReferensiRequest struct { + BaseRequest + JenisReferensi string `json:"jenis_referensi" validate:"required,oneof=diagnosa procedure obat alkes faskes dokter poli"` + Keyword string `json:"keyword,omitempty"` + KodeReferensi string `json:"kode_referensi,omitempty"` + PaginationRequest +} + +// ReferensiData represents referensi information +type ReferensiData struct { + Kode string `json:"kode"` + Nama string `json:"nama"` + Kategori string `json:"kategori,omitempty"` + Status string `json:"status"` + TglBerlaku string `json:"tgl_berlaku,omitempty"` + TglBerakhir string `json:"tgl_berakhir,omitempty"` + Keterangan string `json:"keterangan,omitempty"` +} + +// ReferensiResponse represents referensi API response +type ReferensiResponse struct { + BaseResponse + Data []ReferensiData `json:"data,omitempty"` + Pagination *PaginationResponse `json:"pagination,omitempty"` +} + +// === MONITORING MODELS === + +// MonitoringRequest represents monitoring data request +type MonitoringRequest struct { + BaseRequest + TanggalAwal string `json:"tanggal_awal" validate:"required"` + TanggalAkhir string `json:"tanggal_akhir" validate:"required"` + JenisLaporan string `json:"jenis_laporan" validate:"required,oneof=kunjungan klaim rujukan sep"` + PPK string `json:"ppk,omitempty"` + StatusData string `json:"status_data,omitempty"` + PaginationRequest +} + +// MonitoringData represents monitoring information +type MonitoringData struct { + Tanggal string `json:"tanggal"` + PPK string `json:"ppk"` + NamaPPK string `json:"nama_ppk"` + JumlahKasus int `json:"jumlah_kasus"` + TotalTarif float64 `json:"total_tarif"` + StatusData string `json:"status_data"` + Keterangan string `json:"keterangan,omitempty"` +} + +// MonitoringResponse represents monitoring API response +type MonitoringResponse struct { + BaseResponse + Data []MonitoringData `json:"data,omitempty"` + Summary *MonitoringSummary `json:"summary,omitempty"` + Pagination *PaginationResponse `json:"pagination,omitempty"` +} + +// MonitoringSummary represents monitoring summary +type MonitoringSummary struct { + TotalKasus int `json:"total_kasus"` + TotalTarif float64 `json:"total_tarif"` + RataRataTarif float64 `json:"rata_rata_tarif"` + PeriodeLaporan string `json:"periode_laporan"` +} diff --git a/internal/models/reference/base.go b/internal/models/reference/base.go new file mode 100644 index 00000000..5af43e32 --- /dev/null +++ b/internal/models/reference/base.go @@ -0,0 +1,53 @@ +package reference + +import ( + "time" +) + +// BaseRequest contains common fields for all BPJS requests +type BaseRequest struct { + RequestID string `json:"request_id,omitempty"` + Timestamp time.Time `json:"timestamp,omitempty"` +} + +// BaseResponse contains common response fields +type BaseResponse struct { + Status string `json:"status"` + Message string `json:"message,omitempty"` + RequestID string `json:"request_id,omitempty"` + Timestamp string `json:"timestamp,omitempty"` +} + +// ErrorResponse represents error response structure +type ErrorResponse struct { + Status string `json:"status"` + Message string `json:"message"` + RequestID string `json:"request_id,omitempty"` + Errors map[string]interface{} `json:"errors,omitempty"` + Code string `json:"code,omitempty"` +} + +// PaginationRequest contains pagination parameters +type PaginationRequest struct { + Page int `json:"page" validate:"min=1"` + Limit int `json:"limit" validate:"min=1,max=100"` + SortBy string `json:"sort_by,omitempty"` + SortDir string `json:"sort_dir,omitempty" validate:"omitempty,oneof=asc desc"` +} + +// PaginationResponse contains pagination metadata +type PaginationResponse struct { + CurrentPage int `json:"current_page"` + TotalPages int `json:"total_pages"` + TotalItems int64 `json:"total_items"` + ItemsPerPage int `json:"items_per_page"` + HasNext bool `json:"has_next"` + HasPrev bool `json:"has_previous"` +} + +// MetaInfo contains additional metadata +type MetaInfo struct { + Version string `json:"version"` + Environment string `json:"environment"` + ServerTime string `json:"server_time"` +} diff --git a/internal/models/reference/eclaim.go b/internal/models/reference/eclaim.go new file mode 100644 index 00000000..12631973 --- /dev/null +++ b/internal/models/reference/eclaim.go @@ -0,0 +1,148 @@ +package reference + +// === KLAIM MODELS === + +// KlaimRequest represents klaim submission request +type KlaimRequest struct { + BaseRequest + NoSep string `json:"nomor_sep" validate:"required"` + NoKartu string `json:"nomor_kartu" validate:"required"` + NoMR string `json:"nomor_mr" validate:"required"` + TglPulang string `json:"tgl_pulang" validate:"required"` + TglMasuk string `json:"tgl_masuk" validate:"required"` + JnsPelayanan string `json:"jenis_pelayanan" validate:"required,oneof=1 2"` + CaraPulang string `json:"cara_pulang" validate:"required"` + Data KlaimData `json:"data" validate:"required"` +} + +// KlaimData represents detailed klaim information +type KlaimData struct { + Diagnosa []DiagnosaKlaim `json:"diagnosa" validate:"required,dive"` + Procedure []ProcedureKlaim `json:"procedure,omitempty"` + Investigasi []InvestigasiKlaim `json:"investigasi,omitempty"` + ObatAlkes []ObatKlaim `json:"obat_alkes,omitempty"` + TarifRS []TarifKlaim `json:"tarif_rs,omitempty"` + DRG *DRGInfo `json:"drg,omitempty"` + SpecialCMG *SpecialCMGInfo `json:"special_cmg,omitempty"` +} + +// DiagnosaKlaim represents diagnosis in klaim +type DiagnosaKlaim struct { + KodeDiagnosa string `json:"kode_diagnosa" validate:"required"` + NamaDiagnosa string `json:"nama_diagnosa"` + TipeDiagnosa string `json:"tipe_diagnosa" validate:"required,oneof=1 2"` +} + +// ProcedureKlaim represents procedure in klaim +type ProcedureKlaim struct { + KodeTindakan string `json:"kode_tindakan" validate:"required"` + NamaTindakan string `json:"nama_tindakan"` + TanggalTindakan string `json:"tanggal_tindakan" validate:"required"` + Keterangan string `json:"keterangan,omitempty"` +} + +// InvestigasiKlaim represents investigation/lab results +type InvestigasiKlaim struct { + KodeInvestigasi string `json:"kode_investigasi" validate:"required"` + NamaInvestigasi string `json:"nama_investigasi"` + Hasil string `json:"hasil,omitempty"` + Satuan string `json:"satuan,omitempty"` + NilaiNormal string `json:"nilai_normal,omitempty"` +} + +// ObatKlaim represents medication in klaim +type ObatKlaim struct { + KodeObat string `json:"kode_obat" validate:"required"` + NamaObat string `json:"nama_obat"` + Dosis string `json:"dosis,omitempty"` + Frekuensi string `json:"frekuensi,omitempty"` + Jumlah float64 `json:"jumlah" validate:"min=0"` + Harga float64 `json:"harga" validate:"min=0"` +} + +// TarifKlaim represents hospital tariff +type TarifKlaim struct { + KodeTarif string `json:"kode_tarif" validate:"required"` + NamaTarif string `json:"nama_tarif"` + Jumlah int `json:"jumlah" validate:"min=0"` + Tarif float64 `json:"tarif" validate:"min=0"` + Total float64 `json:"total"` +} + +// DRGInfo represents DRG information +type DRGInfo struct { + KodeDRG string `json:"kode_drg"` + NamaDRG string `json:"nama_drg"` + TarifDRG float64 `json:"tarif_drg"` + Severity string `json:"severity,omitempty"` +} + +// SpecialCMGInfo represents Special CMG information +type SpecialCMGInfo struct { + KodeCMG string `json:"kode_cmg"` + NamaCMG string `json:"nama_cmg"` + TarifCMG float64 `json:"tarif_cmg"` + SubAcute string `json:"sub_acute,omitempty"` +} + +// KlaimResponse represents klaim API response +type KlaimResponse struct { + BaseResponse + Data *KlaimResponseData `json:"data,omitempty"` +} + +// KlaimResponseData represents processed klaim data +type KlaimResponseData struct { + NoKlaim string `json:"nomor_klaim"` + NoSep string `json:"nomor_sep"` + StatusKlaim string `json:"status_klaim"` + TarifAktual float64 `json:"tarif_aktual"` + TarifRS float64 `json:"tarif_rs"` + TarifApproved float64 `json:"tarif_approved"` + Grouper *GrouperResult `json:"grouper,omitempty"` +} + +// === GROUPER MODELS === + +// GrouperRequest represents grouper processing request +type GrouperRequest struct { + BaseRequest + NoSep string `json:"nomor_sep" validate:"required"` + NoKartu string `json:"nomor_kartu" validate:"required"` + TglMasuk string `json:"tgl_masuk" validate:"required"` + TglPulang string `json:"tgl_pulang" validate:"required"` + JnsPelayanan string `json:"jenis_pelayanan" validate:"required,oneof=1 2"` + CaraPulang string `json:"cara_pulang" validate:"required"` + DiagnosaPrimer string `json:"diagnosa_primer" validate:"required"` + DiagnosaSkunder []string `json:"diagnosa_skunder,omitempty"` + Procedure []string `json:"procedure,omitempty"` + AdlScore int `json:"adl_score,omitempty"` + AgeAtAdmission int `json:"age_at_admission" validate:"min=0"` +} + +// GrouperResult represents grouper processing result +type GrouperResult struct { + KodeDRG string `json:"kode_drg"` + NamaDRG string `json:"nama_drg"` + TarifDRG float64 `json:"tarif_drg"` + KodeCMG string `json:"kode_cmg,omitempty"` + NamaCMG string `json:"nama_cmg,omitempty"` + TarifCMG float64 `json:"tarif_cmg,omitempty"` + Severity string `json:"severity"` + SubAcute bool `json:"sub_acute"` + Chronic bool `json:"chronic"` + TopUp *TopUpInfo `json:"top_up,omitempty"` +} + +// TopUpInfo represents top-up information +type TopUpInfo struct { + Eligible bool `json:"eligible"` + Percentage float64 `json:"percentage"` + Amount float64 `json:"amount"` +} + +// GrouperResponse represents grouper API response +type GrouperResponse struct { + BaseResponse + Data *GrouperResult `json:"data,omitempty"` +} diff --git a/internal/models/reference/peserta.go b/internal/models/reference/peserta.go deleted file mode 100644 index bf6d7bad..00000000 --- a/internal/models/reference/peserta.go +++ /dev/null @@ -1,84 +0,0 @@ -package models - -// PesertaResponse represents the response structure for BPJS participant data -type PesertaResponse struct { - Message string `json:"message"` - Data map[string]interface{} `json:"data"` -} - -// PesertaRawResponse represents the raw response structure from BPJS API -type PesertaRawResponse struct { - MetaData struct { - Code string `json:"code"` - Message string `json:"message"` - } `json:"metaData"` - Response interface{} `json:"response"` -} - -// PesertaRequest represents the request structure for BPJS participant search -type PesertaRequest struct { - NIK string `json:"nik" binding:"required"` - TglSEP string `json:"tglSEP" binding:"required"` -} - -// PesertaData represents the participant data structure -type PesertaData struct { - NoKartu string `json:"noKartu"` - NIK string `json:"nik"` - Nama string `json:"nama"` - Pisa string `json:"pisa"` - Sex string `json:"sex"` - TglLahir string `json:"tglLahir"` - Pob string `json:"pob"` - KdProvider string `json:"kdProvider"` - NmProvider string `json:"nmProvider"` - KelasRawat string `json:"kelasRawat"` - Keterangan string `json:"keterangan"` - NoTelepon string `json:"noTelepon"` - Alamat string `json:"alamat"` - KdPos string `json:"kdPos"` - Pekerjaan string `json:"pekerjaan"` - StatusKawin string `json:"statusKawin"` - TglCetakKartu string `json:"tglCetakKartu"` - TglTAT string `json:"tglTAT"` - TglTMT string `json:"tglTMT"` - ProvUmum struct { - KdProvider string `json:"kdProvider"` - NmProvider string `json:"nmProvider"` - } `json:"provUmum"` - JenisPeserta struct { - KdJenisPeserta string `json:"kdJenisPeserta"` - NmJenisPeserta string `json:"nmJenisPeserta"` - } `json:"jenisPeserta"` - KelasTanggungan struct { - KdKelas string `json:"kdKelas"` - NmKelas string `json:"nmKelas"` - } `json:"kelasTanggungan"` - Informasi struct { - Dinsos string `json:"dinsos"` - NoSKTM string `json:"noSKTM"` - ProlanisPRB string `json:"prolanisPRB"` - } `json:"informasi"` - Cob struct { - NoAsuransi string `json:"noAsuransi"` - NmAsuransi string `json:"nmAsuransi"` - TglTAT string `json:"tglTAT"` - TglTMT string `json:"tglTMT"` - } `json:"cob"` - HakKelas struct { - Kode string `json:"kode"` - Nama string `json:"nama"` - } `json:"hakKelas"` - Mr struct { - NoMR string `json:"noMR"` - NoTelepon string `json:"noTelepon"` - } `json:"mr"` - ProvRujuk struct { - KdProvider string `json:"kdProvider"` - NmProvider string `json:"nmProvider"` - } `json:"provRujuk"` - StatusPeserta struct { - Kode string `json:"kode"` - Nama string `json:"nama"` - } `json:"statusPeserta"` -} diff --git a/internal/models/reference/services.go b/internal/models/reference/services.go new file mode 100644 index 00000000..abba955b --- /dev/null +++ b/internal/models/reference/services.go @@ -0,0 +1,29 @@ +// internal/models/reference/services.go +package reference + +import "context" + +// VClaimService defines VClaim service interface +type VClaimService interface { + GetPeserta(ctx context.Context, noKartu string) (*PesertaData, error) + CreateSEP(ctx context.Context, req *SEPRequest) (*SEPData, error) + GetSEP(ctx context.Context, noSep string) (*SEPData, error) + UpdateSEP(ctx context.Context, noSep string, req *SEPRequest) (*SEPData, error) + DeleteSEP(ctx context.Context, noSep string) error + GetRujukan(ctx context.Context, noRujukan string) (*RujukanData, error) +} + +// EClaimService defines EClaim service interface +type EClaimService interface { + CreateKlaim(ctx context.Context, req *KlaimRequest) (*KlaimResponseData, error) + GetKlaim(ctx context.Context, noKlaim string) (*KlaimResponseData, error) + UpdateKlaim(ctx context.Context, noKlaim string, req *KlaimRequest) (*KlaimResponseData, error) + ProcessGrouper(ctx context.Context, req *GrouperRequest) (*GrouperResult, error) +} + +// AplicareService defines Aplicare service interface +type AplicareService interface { + GetReferensi(ctx context.Context, req *ReferensiRequest) ([]ReferensiData, *PaginationResponse, error) + GetMonitoring(ctx context.Context, req *MonitoringRequest) ([]MonitoringData, *MonitoringSummary, *PaginationResponse, error) + CreateMonitoring(ctx context.Context, req *MonitoringRequest) error +} diff --git a/internal/models/reference/validation.go b/internal/models/reference/validation.go new file mode 100644 index 00000000..aeb3400e --- /dev/null +++ b/internal/models/reference/validation.go @@ -0,0 +1,106 @@ +package reference + +import ( + "regexp" + "strings" + "time" + + "github.com/go-playground/validator/v10" +) + +// CustomValidator wraps the validator +type CustomValidator struct { + Validator *validator.Validate +} + +// Validate validates struct +func (cv *CustomValidator) Validate(i interface{}) error { + return cv.Validator.Struct(i) +} + +// RegisterCustomValidations registers custom validation rules +func RegisterCustomValidations(v *validator.Validate) { + // Validate Indonesian phone number + v.RegisterValidation("indonesian_phone", validateIndonesianPhone) + + // Validate BPJS card number format + v.RegisterValidation("bpjs_card", validateBPJSCard) + + // Validate Indonesian NIK + v.RegisterValidation("indonesian_nik", validateIndonesianNIK) + + // Validate date format YYYY-MM-DD + v.RegisterValidation("date_format", validateDateFormat) + + // Validate ICD-10 code format + v.RegisterValidation("icd10", validateICD10) + + // Validate ICD-9-CM procedure code + v.RegisterValidation("icd9cm", validateICD9CM) +} + +func validateIndonesianPhone(fl validator.FieldLevel) bool { + phone := fl.Field().String() + if phone == "" { + return true // Optional field + } + + // Indonesian phone number pattern: +62, 62, 08, or 8 + pattern := `^(\+?62|0?8)[1-9][0-9]{7,11}$` + matched, _ := regexp.MatchString(pattern, phone) + return matched +} + +func validateBPJSCard(fl validator.FieldLevel) bool { + card := fl.Field().String() + if len(card) != 13 { + return false + } + + // BPJS card should be numeric + pattern := `^\d{13}$` + matched, _ := regexp.MatchString(pattern, card) + return matched +} + +func validateIndonesianNIK(fl validator.FieldLevel) bool { + nik := fl.Field().String() + if len(nik) != 16 { + return false + } + + // NIK should be numeric + pattern := `^\d{16}$` + matched, _ := regexp.MatchString(pattern, nik) + return matched +} + +func validateDateFormat(fl validator.FieldLevel) bool { + dateStr := fl.Field().String() + _, err := time.Parse("2006-01-02", dateStr) + return err == nil +} + +func validateICD10(fl validator.FieldLevel) bool { + code := fl.Field().String() + if code == "" { + return true + } + + // Basic ICD-10 pattern: Letter followed by 2 digits, optional dot and more digits + pattern := `^[A-Z]\d{2}(\.\d+)?$` + matched, _ := regexp.MatchString(pattern, strings.ToUpper(code)) + return matched +} + +func validateICD9CM(fl validator.FieldLevel) bool { + code := fl.Field().String() + if code == "" { + return true + } + + // Basic ICD-9-CM procedure pattern: 2-4 digits with optional decimal + pattern := `^\d{2,4}(\.\d+)?$` + matched, _ := regexp.MatchString(pattern, code) + return matched +} diff --git a/internal/models/reference/vclaim.go b/internal/models/reference/vclaim.go new file mode 100644 index 00000000..e2378a54 --- /dev/null +++ b/internal/models/reference/vclaim.go @@ -0,0 +1,144 @@ +// internal/models/reference/vclaim.go +package reference + +// === PESERTA MODELS === + +// PesertaRequest represents peserta lookup request +type PesertaRequest struct { + BaseRequest + NoKartu string `json:"nokartu" validate:"required,min=13,max=13"` + NIK string `json:"nik,omitempty" validate:"omitempty,min=16,max=16"` + TanggalSEP string `json:"tglsep" validate:"required" example:"2024-01-15"` + NoTelepon string `json:"notelp,omitempty" validate:"omitempty,max=15"` +} + +// PesertaData represents peserta information from BPJS +type PesertaData struct { + NoKartu string `json:"noKartu"` + NIK string `json:"nik"` + Nama string `json:"nama"` + Pisa string `json:"pisa"` + Sex string `json:"sex"` + TanggalLahir string `json:"tglLahir"` + TelephoneMsisdn string `json:"tglTAT"` + TelephoneAsat string `json:"tglTMT"` + KodeCabang string `json:"kdCabang"` + NamaCabang string `json:"nmCabang"` + KodeJenisPeserta string `json:"kdJnsPst"` + NamaJenisPeserta string `json:"nmJnsPst"` + KelasRawat string `json:"klsRawat"` + Status string `json:"statusPeserta"` + Aktif string `json:"aktif"` + KeteranganAktif string `json:"ketAktif"` + NoSKTM string `json:"noSKTM,omitempty"` + NoKTP string `json:"noKtp"` + Asuransi string `json:"asuransi,omitempty"` + CoB string `json:"cob,omitempty"` + TunggakanIuran string `json:"tglTunggak,omitempty"` + MR struct { + NoMR string `json:"noMR"` + NamaMR string `json:"nmMR"` + Sex string `json:"sex"` + TglLahir string `json:"tglLahir"` + TglMeninggal string `json:"tglMeninggal,omitempty"` + } `json:"mr,omitempty"` +} + +// PesertaResponse represents peserta API response +type PesertaResponse struct { + BaseResponse + Data *PesertaData `json:"data,omitempty"` + MetaData interface{} `json:"metaData,omitempty"` +} + +// === SEP (Surat Eligibilitas Peserta) MODELS === + +// SEPRequest represents SEP creation/update request +type SEPRequest struct { + BaseRequest + NoKartu string `json:"noKartu" validate:"required"` + TglSep string `json:"tglSep" validate:"required"` + PPKPelayanan string `json:"ppkPelayanan" validate:"required"` + JnsPelayanan string `json:"jnsPelayanan" validate:"required,oneof=1 2"` + KlsRawat string `json:"klsRawat" validate:"required,oneof=1 2 3"` + NoMR string `json:"noMR" validate:"required"` + Rujukan *SEPRujukan `json:"rujukan"` + Catatan string `json:"catatan,omitempty"` + Diagnosa string `json:"diagnosa" validate:"required"` + PoliTujuan string `json:"poli" validate:"required"` + ExternalUser string `json:"user" validate:"required"` + NoTelp string `json:"noTelp,omitempty"` +} + +// SEPRujukan represents rujukan information in SEP +type SEPRujukan struct { + AsalRujukan string `json:"asalRujukan" validate:"required,oneof=1 2"` + TglRujukan string `json:"tglRujukan" validate:"required"` + NoRujukan string `json:"noRujukan" validate:"required"` + PPKRujukan string `json:"ppkRujukan" validate:"required"` +} + +// SEPData represents SEP response data +type SEPData struct { + NoSep string `json:"noSep"` + TglSep string `json:"tglSep"` + JnsPelayanan string `json:"jnsPelayanan"` + PoliTujuan string `json:"poli"` + KlsRawat string `json:"klsRawat"` + NoMR string `json:"noMR"` + Rujukan SEPRujukan `json:"rujukan"` + Catatan string `json:"catatan"` + Diagnosa string `json:"diagnosa"` + Peserta PesertaData `json:"peserta"` + Informasi struct { + NoSKDP string `json:"noSKDP,omitempty"` + DPJPLayan string `json:"dpjpLayan"` + NoTelepon string `json:"noTelp"` + SubSpesialis string `json:"subSpesialis,omitempty"` + } `json:"informasi"` +} + +// SEPResponse represents SEP API response +type SEPResponse struct { + BaseResponse + Data *SEPData `json:"data,omitempty"` +} + +// === RUJUKAN MODELS === + +// RujukanRequest represents rujukan lookup request +type RujukanRequest struct { + BaseRequest + NoRujukan string `json:"noRujukan" validate:"required"` + NoKartu string `json:"noKartu,omitempty"` +} + +// RujukanData represents rujukan information +type RujukanData struct { + NoRujukan string `json:"noRujukan"` + TglRujukan string `json:"tglRujukan"` + NoKartu string `json:"noKartu"` + Nama string `json:"nama"` + KelasRawat string `json:"kelasRawat"` + Diagnosa struct { + KodeDiagnosa string `json:"kdDiagnosa"` + NamaDiagnosa string `json:"nmDiagnosa"` + } `json:"diagnosa"` + PoliRujukan struct { + KodePoli string `json:"kdPoli"` + NamaPoli string `json:"nmPoli"` + } `json:"poliRujukan"` + ProvPerujuk struct { + KodeProvider string `json:"kdProvider"` + NamaProvider string `json:"nmProvider"` + } `json:"provPerujuk"` + PelayananInfo string `json:"pelayanan"` + StatusRujukan string `json:"statusRujukan"` +} + +// RujukanResponse represents rujukan API response +type RujukanResponse struct { + BaseResponse + Data *RujukanData `json:"data,omitempty"` + List []RujukanData `json:"list,omitempty"` +} diff --git a/internal/models/vclaim/sep.go b/internal/models/vclaim/sep.go deleted file mode 100644 index 821d128a..00000000 --- a/internal/models/vclaim/sep.go +++ /dev/null @@ -1,144 +0,0 @@ -package models - -// SepPostRequest represents the request payload for creating a SEP -type SepPostRequest struct { - TSep TSepPost `json:"tsep" binding:"required"` -} - -// TSepPost contains the main SEP data for creation -type TSepPost struct { - NoKartu string `json:"noKartu" binding:"required"` - TglSep string `json:"tglSep" binding:"required"` // yyyy-MM-dd - PpkPelayanan string `json:"ppkPelayanan" binding:"required"` - JnsPelayanan string `json:"jnsPelayanan" binding:"required"` - KlsRawat KlsRawatPost `json:"klsRawat" binding:"required"` - NoMR string `json:"noMR" binding:"required"` - Rujukan Rujukan `json:"rujukan" binding:"required"` - Catatan string `json:"catatan"` - DiagAwal string `json:"diagAwal" binding:"required"` - Poli Poli `json:"poli" binding:"required"` - Cob Flag `json:"cob" binding:"required"` - Katarak Flag `json:"katarak" binding:"required"` - Jaminan Jaminan `json:"jaminan" binding:"required"` - TujuanKunj string `json:"tujuanKunj"` - FlagProcedure string `json:"flagProcedure"` - KdPenunjang string `json:"kdPenunjang"` - AssesmentPel string `json:"assesmentPel"` - Skdp Skdp `json:"skdp" binding:"required"` - DpjpLayan string `json:"dpjpLayan"` - NoTelp string `json:"noTelp"` - User string `json:"user" binding:"required"` -} - -// KlsRawatPost represents class of care data for POST requests -type KlsRawatPost struct { - KlsRawatHak string `json:"klsRawatHak" binding:"required"` - KlsRawatNaik string `json:"klsRawatNaik"` - Pembiayaan string `json:"pembiayaan"` - PenanggungJawab string `json:"penanggungJawab"` -} - -// Rujukan represents referral data -type Rujukan struct { - AsalRujukan string `json:"asalRujukan" binding:"required"` - TglRujukan string `json:"tglRujukan" binding:"required"` - NoRujukan string `json:"noRujukan" binding:"required"` - PpkRujukan string `json:"ppkRujukan" binding:"required"` -} - -// Poli represents poly/department data -type Poli struct { - Tujuan string `json:"tujuan" binding:"required"` - Eksekutif string `json:"eksekutif" binding:"required"` -} - -// Flag represents a generic flag structure -type Flag struct { - Flag string `json:"flag" binding:"required"` -} - -// Jaminan represents insurance guarantee data -type Jaminan struct { - LakaLantas string `json:"lakaLantas" binding:"required"` - NoLP string `json:"noLP"` - Penjamin Penjamin `json:"penjamin"` -} - -// Penjamin represents guarantor data -type Penjamin struct { - TglKejadian string `json:"tglKejadian"` - Keterangan string `json:"keterangan"` - Suplesi Suplesi `json:"suplesi"` -} - -// Suplesi represents supplementary data -type Suplesi struct { - Suplesi string `json:"suplesi"` - NoSepSuplesi string `json:"noSepSuplesi"` - LokasiLaka LokasiLaka `json:"lokasiLaka"` -} - -// LokasiLaka represents accident location data -type LokasiLaka struct { - KdPropinsi string `json:"kdPropinsi"` - KdKabupaten string `json:"kdKabupaten"` - KdKecamatan string `json:"kdKecamatan"` -} - -// Skdp represents SKDP data -type Skdp struct { - NoSurat string `json:"noSurat" binding:"required"` - KodeDPJP string `json:"kodeDPJP" binding:"required"` -} - -// SepPutRequest represents the request payload for updating a SEP -type SepPutRequest struct { - TSep TSepPut `json:"tsep" binding:"required"` -} - -// TSepPut contains the main SEP data for updates -type TSepPut struct { - NoSep string `json:"noSep" binding:"required"` - KlsRawat KlsRawatPut `json:"klsRawat"` - NoMR string `json:"noMR"` - Catatan string `json:"catatan"` - DiagAwal string `json:"diagAwal"` - Poli Poli `json:"poli"` - Cob Flag `json:"cob"` - Katarak Flag `json:"katarak"` - Jaminan Jaminan `json:"jaminan"` - DpjpLayan string `json:"dpjpLayan"` - NoTelp string `json:"noTelp"` - User string `json:"user" binding:"required"` -} - -// KlsRawatPut represents class of care data for PUT requests -type KlsRawatPut struct { - KlsRawatHak string `json:"klsRawatHak"` - KlsRawatNaik string `json:"klsRawatNaik"` - Pembiayaan string `json:"pembiayaan"` - PenanggungJawab string `json:"penanggungJawab"` -} - -// SepDeleteRequest represents the request payload for deleting a SEP -type SepDeleteRequest struct { - TSep struct { - NoSep string `json:"noSep" binding:"required"` - User string `json:"user" binding:"required"` - } `json:"tsep" binding:"required"` -} - -// SepResponse represents the standard response for SEP operations -type SepResponse struct { - Message string `json:"message"` - Data map[string]interface{} `json:"data,omitempty"` -} - -// SepRawResponse represents the raw response from BPJS API -type SepRawResponse struct { - MetaData struct { - Code string `json:"code"` - Message string `json:"message"` - } `json:"metaData"` - Response interface{} `json:"response"` -} diff --git a/internal/routes/v1/routes.go b/internal/routes/v1/routes.go index 9618d0ff..6f30f8e9 100644 --- a/internal/routes/v1/routes.go +++ b/internal/routes/v1/routes.go @@ -5,7 +5,6 @@ import ( "api-service/internal/database" authHandlers "api-service/internal/handlers/auth" healthcheckHandlers "api-service/internal/handlers/healthcheck" - bpjsPesertaHandlers "api-service/internal/handlers/reference" retribusiHandlers "api-service/internal/handlers/retribusi" "api-service/internal/middleware" services "api-service/internal/services/auth" @@ -65,10 +64,6 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { v1.POST("/token/generate", tokenHandler.GenerateToken) v1.POST("/token/generate-direct", tokenHandler.GenerateTokenDirect) - // BPJS endpoints - bpjsPesertaHandler := bpjsPesertaHandlers.NewPesertaHandler(cfg.Bpjs) - v1.GET("/bpjs/peserta/nik/:nik/tglSEP/:tglSEP", bpjsPesertaHandler.GetPesertaByNIK) - // ============= PUBLISHED ROUTES =============================================== // // Retribusi endpoints diff --git a/internal/services/bpjs/vclaimBridge.go b/internal/services/bpjs/vclaimBridge.go index b1027627..2face815 100644 --- a/internal/services/bpjs/vclaimBridge.go +++ b/internal/services/bpjs/vclaimBridge.go @@ -22,8 +22,8 @@ type VClaimService interface { Post(ctx context.Context, endpoint string, payload interface{}, result interface{}) error Put(ctx context.Context, endpoint string, payload interface{}, result interface{}) error Delete(ctx context.Context, endpoint string, result interface{}) error - GetRawResponse(ctx context.Context, endpoint string) (*ResponDTO, error) - PostRawResponse(ctx context.Context, endpoint string, payload interface{}) (*ResponDTO, error) + GetRawResponse(ctx context.Context, endpoint string) (*ResponDTOVclaim, error) + PostRawResponse(ctx context.Context, endpoint string, payload interface{}) (*ResponDTOVclaim, error) } // Service struct for VClaim service @@ -33,7 +33,7 @@ type Service struct { } // Response structures -type ResponMentahDTO struct { +type ResponMentahDTOVclaim struct { MetaData struct { Code string `json:"code"` Message string `json:"message"` @@ -41,7 +41,7 @@ type ResponMentahDTO struct { Response string `json:"response"` } -type ResponDTO struct { +type ResponDTOVclaim struct { MetaData struct { Code string `json:"code"` Message string `json:"message"` @@ -133,7 +133,7 @@ func (s *Service) prepareRequest(ctx context.Context, method, endpoint string, b } // processResponse processes response from VClaim API -func (s *Service) processResponse(res *http.Response) (*ResponDTO, error) { +func (s *Service) processResponse(res *http.Response) (*ResponDTOVclaim, error) { defer res.Body.Close() log.Info(). @@ -170,7 +170,7 @@ func (s *Service) processResponse(res *http.Response) (*ResponDTO, error) { } // Parse raw response - var respMentah ResponMentahDTO + var respMentah ResponMentahDTOVclaim if err := json.Unmarshal(body, &respMentah); err != nil { log.Error(). Err(err). @@ -186,7 +186,7 @@ func (s *Service) processResponse(res *http.Response) (*ResponDTO, error) { Msg("Response metadata") // Create final response - finalResp := &ResponDTO{ + finalResp := &ResponDTOVclaim{ MetaData: respMentah.MetaData, } @@ -312,7 +312,7 @@ func (s *Service) Delete(ctx context.Context, endpoint string, result interface{ } // GetRawResponse returns raw response without mapping -func (s *Service) GetRawResponse(ctx context.Context, endpoint string) (*ResponDTO, error) { +func (s *Service) GetRawResponse(ctx context.Context, endpoint string) (*ResponDTOVclaim, error) { req, err := s.prepareRequest(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, err @@ -327,7 +327,7 @@ func (s *Service) GetRawResponse(ctx context.Context, endpoint string) (*ResponD } // PostRawResponse returns raw response without mapping -func (s *Service) PostRawResponse(ctx context.Context, endpoint string, payload interface{}) (*ResponDTO, error) { +func (s *Service) PostRawResponse(ctx context.Context, endpoint string, payload interface{}) (*ResponDTOVclaim, error) { var buf bytes.Buffer if payload != nil { if err := json.NewEncoder(&buf).Encode(payload); err != nil { @@ -349,7 +349,7 @@ func (s *Service) PostRawResponse(ctx context.Context, endpoint string, payload } // mapToResult maps the final response to the result interface -func mapToResult(resp *ResponDTO, result interface{}) error { +func mapToResult(resp *ResponDTOVclaim, result interface{}) error { respBytes, err := json.Marshal(resp) if err != nil { return fmt.Errorf("failed to marshal final response: %w", err) diff --git a/services-config-bpjs.yaml b/services-config-bpjs.yaml new file mode 100644 index 00000000..ad0ee5ee --- /dev/null +++ b/services-config-bpjs.yaml @@ -0,0 +1,149 @@ +# services-config-complete.yaml +global: + module_name: "api-service" + output_dir: "internal/handlers" + package_prefix: "api-service" + enable_swagger: true + enable_logging: true + enable_metrics: true + +services: + vclaim: + name: "VClaim" + category: "vclaim" + package: "vclaim" + description: "BPJS VClaim service for eligibility and SEP management" + base_url: "https://apijkn.bpjs-kesehatan.go.id/vclaim-rest" + timeout: 30 + retry_count: 3 + middleware: + - "RequestLogger" + - "ResponseLogger" + - "RateLimiter" + dependencies: + - "database" + - "redis" + endpoints: + peserta: + methods: ["GET"] + get_path: "/Peserta/:nokartu" + model: "PesertaRequest" + response_model: "PesertaResponse" + description: "Get participant eligibility information" + summary: "Get Participant Info" + tags: ["vclaim", "peserta"] + require_auth: true + rate_limit: 100 + cache_enabled: true + cache_ttl: 300 + sep: + methods: ["GET", "POST", "PUT", "DELETE"] + get_path: "/SEP/:nosep" + post_path: "/sep" + put_path: "/sep/:nosep" + delete_path: "/sep/:nosep" + model: "SEPRequest" + response_model: "SEPResponse" + description: "Manage SEP (Surat Eligibilitas Peserta)" + summary: "SEP Management" + tags: ["vclaim", "sep"] + require_auth: true + rate_limit: 50 + cache_enabled: true + cache_ttl: 180 + custom_headers: + X-Service: "VClaim" + X-Version: "2.0" + rujukan: + methods: ["GET"] + get_path: "/rujukan/:norujukan" + model: "RujukanRequest" + response_model: "RujukanResponse" + description: "Get referral information" + summary: "Get Referral Info" + tags: ["vclaim", "rujukan"] + require_auth: true + rate_limit: 100 + cache_enabled: true + cache_ttl: 600 + + eclaim: + name: "EClaim" + category: "eclaim" + package: "eclaim" + description: "BPJS EClaim service for claim processing and grouper" + base_url: "https://apijkn.bpjs-kesehatan.go.id/new-eclaim-rest" + timeout: 60 + retry_count: 2 + middleware: + - "RequestLogger" + - "ResponseLogger" + - "ClaimValidator" + dependencies: + - "database" + - "grouper_service" + endpoints: + klaim: + methods: ["GET", "POST", "PUT"] + get_path: "/klaim/:noklaim" + post_path: "/klaim" + put_path: "/klaim/:noklaim" + model: "KlaimRequest" + response_model: "KlaimResponse" + description: "Manage insurance claims" + summary: "Claim Management" + tags: ["eclaim", "klaim"] + require_auth: true + rate_limit: 30 + cache_enabled: false + grouper: + methods: ["POST"] + post_path: "/grouper" + model: "GrouperRequest" + response_model: "GrouperResponse" + description: "Process claim grouping and pricing" + summary: "Claim Grouper" + tags: ["eclaim", "grouper"] + require_auth: true + rate_limit: 20 + cache_enabled: true + cache_ttl: 120 + + aplicare: + name: "Aplicare" + category: "aplicare" + package: "aplicare" + description: "BPJS Aplicare service for reference data and monitoring" + base_url: "https://apijkn.bpjs-kesehatan.go.id/aplicaresws" + timeout: 45 + retry_count: 3 + middleware: + - "RequestLogger" + - "ResponseLogger" + dependencies: + - "database" + endpoints: + referensi: + methods: ["GET"] + get_path: "/referensi/:jenis" + model: "ReferensiRequest" + response_model: "ReferensiResponse" + description: "Get reference data (diagnoses, procedures, etc.)" + summary: "Get Reference Data" + tags: ["aplicare", "referensi"] + require_auth: true + rate_limit: 200 + cache_enabled: true + cache_ttl: 3600 + monitoring: + methods: ["GET", "POST"] + get_path: "/monitoring/:tanggal" + post_path: "/monitoring" + model: "MonitoringRequest" + response_model: "MonitoringResponse" + description: "Healthcare monitoring and reporting" + summary: "Monitoring Data" + tags: ["aplicare", "monitoring"] + require_auth: true + rate_limit: 50 + cache_enabled: false diff --git a/tools/bpjs/generate-handler.go b/tools/bpjs/generate-handler.go index 10761ef8..ada9d50b 100644 --- a/tools/bpjs/generate-handler.go +++ b/tools/bpjs/generate-handler.go @@ -2,1131 +2,608 @@ package main import ( "fmt" + "io/ioutil" "os" "path/filepath" "strings" + "text/template" "time" + + "gopkg.in/yaml.v2" ) -// BpjsHandlerData contains template data for BPJS handler generation -type BpjsHandlerData struct { +// ServiceConfig represents the main configuration structure +type ServiceConfig struct { + Services map[string]Service `yaml:"services"` + Global GlobalConfig `yaml:"global,omitempty"` +} + +// GlobalConfig contains global configuration +type GlobalConfig struct { + ModuleName string `yaml:"module_name"` + OutputDir string `yaml:"output_dir"` + PackagePrefix string `yaml:"package_prefix"` + EnableSwagger bool `yaml:"enable_swagger"` + EnableLogging bool `yaml:"enable_logging"` + EnableMetrics bool `yaml:"enable_metrics"` +} + +// Service represents individual service configuration +type Service struct { + Name string `yaml:"name"` + Category string `yaml:"category"` + Package string `yaml:"package"` + Description string `yaml:"description"` + BaseURL string `yaml:"base_url"` + Timeout int `yaml:"timeout"` + RetryCount int `yaml:"retry_count"` + Endpoints map[string]Endpoint `yaml:"endpoints"` + Middleware []string `yaml:"middleware,omitempty"` // FIXED: Changed to []string + Dependencies []string `yaml:"dependencies,omitempty"` // FIXED: Changed to []string +} + +// Endpoint represents endpoint configuration +type Endpoint struct { + Methods []string `yaml:"methods"` + GetPath string `yaml:"get_path,omitempty"` + PostPath string `yaml:"post_path,omitempty"` + PutPath string `yaml:"put_path,omitempty"` + DeletePath string `yaml:"delete_path,omitempty"` + PatchPath string `yaml:"patch_path,omitempty"` + Model string `yaml:"model"` + ResponseModel string `yaml:"response_model"` + Description string `yaml:"description"` + Summary string `yaml:"summary"` + Tags []string `yaml:"tags"` + RequireAuth bool `yaml:"require_auth"` + RateLimit int `yaml:"rate_limit,omitempty"` + CacheEnabled bool `yaml:"cache_enabled"` + CacheTTL int `yaml:"cache_ttl,omitempty"` + CustomHeaders map[string]string `yaml:"custom_headers,omitempty"` // ADDED: Missing field +} + +// TemplateData holds data for generating handlers +type TemplateData struct { + ServiceName string + ServiceLower string + ServiceUpper string + Category string + Package string + Description string + BaseURL string + Timeout int + RetryCount int + Endpoints []EndpointData + Timestamp string + ModuleName string + HasValidator bool + HasLogger bool + HasMetrics bool + HasSwagger bool + HasAuth bool + HasCache bool + Dependencies []string // FIXED: Changed to []string + Middleware []string // FIXED: Changed to []string + GlobalConfig GlobalConfig +} + +// EndpointData represents processed endpoint data +type EndpointData struct { Name string NameLower string NameUpper string - Category string - CategoryPath string - ModuleName string + NameCamel string + Methods []string + GetPath string + PostPath string + PutPath string + DeletePath string + PatchPath string + Model string + ResponseModel string + Description string + Summary string + Tags []string HasGet bool HasPost bool HasPut bool HasDelete bool - GetEndpoint string - PostEndpoint string - PutEndpoint string - DeleteEndpoint string - Timestamp string + HasPatch bool + RequireAuth bool + RateLimit int + CacheEnabled bool + CacheTTL int + PathParams []string + QueryParams []string + RequiredFields []string + OptionalFields []string + CustomHeaders map[string]string } +// Template remains the same as before... +const handlerTemplate = ` +// Code generated by generate-dynamic-handler.go; DO NOT EDIT. +// Generated at: {{.Timestamp}} +// Service: {{.ServiceName}} ({{.Category}}) +// Description: {{.Description}} + +package handlers + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "{{.ModuleName}}/internal/config" + "{{.ModuleName}}/internal/models/reference" + "{{.ModuleName}}/internal/services/bpjs" + "{{.ModuleName}}/pkg/logger" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "github.com/google/uuid" +) + +// {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services +type {{.ServiceName}}Handler struct { + service services.{{.ServiceName}}Service + validator *validator.Validate + logger logger.Logger + config config.BpjsConfig +} + +// {{.ServiceName}}HandlerConfig contains configuration for {{.ServiceName}}Handler +type {{.ServiceName}}HandlerConfig struct { + BpjsConfig config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler +func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceName}}Handler { + return &{{.ServiceName}}Handler{ + service: services.NewService(cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +} + +{{range .Endpoints}} +{{if .HasGet}} +// Get{{.NameUpper}} retrieves {{.Name}} data +{{if $.HasSwagger}} +// @Summary Get {{.Name}} data +// @Description {{.Description}} +// @Tags {{join .Tags ","}} +// @Accept json +// @Produce json +{{if .RequireAuth}} +// @Security ApiKeyAuth +{{end}} +{{range .PathParams}} +// @Param {{.}} path string true "{{.}}" +{{end}} +// @Success 200 {object} reference.{{.ResponseModel}} +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router {{.GetPath}} [get] +{{end}} +func (h *{{$.ServiceName}}Handler) Get{{.NameUpper}}(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) + defer cancel() + + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + {{if $.HasLogger}} + h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "{{.GetPath}}", + {{range .PathParams}} + "{{.}}": c.Param("{{.}}"), + {{end}} + }) + {{end}} + + // Extract path parameters + {{range .PathParams}} + {{.}} := c.Param("{{.}}") + if {{.}} == "" { + {{if $.HasLogger}} + h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter {{.}}", + RequestID: requestID, + }) + return + } + {{end}} + + // Call service method + var response reference.{{.ResponseModel}} + {{if .PathParams}} + result, err := h.service.Get{{.NameUpper}}(ctx{{range .PathParams}}, {{.}}{{end}}) + {{else}} + result, err := h.service.Get{{.NameUpper}}(ctx) + {{end}} + if err != nil { + {{if $.HasLogger}} + h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusOK, response) +} +{{end}} + +{{if .HasPost}} +// Create{{.NameUpper}} creates new {{.Name}} +{{if $.HasSwagger}} +// @Summary Create {{.Name}} +// @Description {{.Description}} +// @Tags {{join .Tags ","}} +// @Accept json +// @Produce json +{{if .RequireAuth}} +// @Security ApiKeyAuth +{{end}} +// @Param request body reference.{{.Model}} true "{{.Name}} data" +// @Success 201 {object} reference.{{.ResponseModel}} +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router {{.PostPath}} [post] +{{end}} +func (h *{{$.ServiceName}}Handler) Create{{.NameUpper}}(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + {{if $.HasLogger}} + h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + }) + {{end}} + + var req reference.{{.Model}} + if err := c.ShouldBindJSON(&req); err != nil { + {{if $.HasLogger}} + h.logger.Error("Invalid request body", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Invalid request body: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Validate request + if err := h.validator.Struct(&req); err != nil { + {{if $.HasLogger}} + h.logger.Error("Validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Validation failed: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.{{.ResponseModel}} + result, err := h.service.Create{{.NameUpper}}(ctx, &req) + if err != nil { + {{if $.HasLogger}} + h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusCreated, response) +} +{{end}} +{{end}} +` + func main() { if len(os.Args) < 2 { - fmt.Println("Usage: go run generate-bpjs-handler.go [category/]entity [methods]") - fmt.Println("Examples:") - fmt.Println(" go run generate-bpjs-handler.go vclaim/sep get post put delete") - fmt.Println(" go run generate-bpjs-handler.go eclaim/klaim get post") - fmt.Println(" go run generate-bpjs-handler.go peserta get") + printUsage() os.Exit(1) } - // Parse entity path - entityPath := os.Args[1] - methods := []string{} + configFile := os.Args[1] + var targetService string if len(os.Args) > 2 { - methods = os.Args[2:] - } else { - methods = []string{"get", "post", "put", "delete"} + targetService = os.Args[2] } - // Parse category and entity - var category, entityName string - if strings.Contains(entityPath, "/") { - parts := strings.Split(entityPath, "/") - if len(parts) != 2 { - fmt.Println("❌ Error: Invalid path format. Use 'category/entity' or just 'entity'") - os.Exit(1) - } - category = parts[0] - entityName = parts[1] - } else { - category = "" - entityName = entityPath - } - - // Format names - entityName = strings.Title(entityName) - entityLower := strings.ToLower(entityName) - entityUpper := strings.ToUpper(entityName) - - data := BpjsHandlerData{ - Name: entityName, - NameLower: entityLower, - NameUpper: entityUpper, - Category: category, - CategoryPath: category, - ModuleName: "api-service", - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - } - - // Set methods and endpoints - for _, m := range methods { - switch strings.ToLower(m) { - case "get": - data.HasGet = true - data.GetEndpoint = fmt.Sprintf("%s/{id}", entityUpper) - case "post": - data.HasPost = true - data.PostEndpoint = fmt.Sprintf("%s/2.0/insert", entityUpper) - case "put": - data.HasPut = true - data.PutEndpoint = fmt.Sprintf("%s/2.0/update", entityUpper) - case "delete": - data.HasDelete = true - data.DeleteEndpoint = fmt.Sprintf("%s/2.0/delete", entityUpper) - } - } - - // Create directories - var handlerDir, modelDir string - if category != "" { - handlerDir = filepath.Join("internal", "handlers", category) - modelDir = filepath.Join("internal", "models", category) - } else { - handlerDir = filepath.Join("internal", "handlers") - modelDir = filepath.Join("internal", "models") - } - - for _, d := range []string{handlerDir, modelDir} { - if err := os.MkdirAll(d, 0755); err != nil { - panic(err) - } - } - - // Generate files - generateOptimizedBpjsHandlerFile(data, handlerDir) - generateOptimizedBpjsModelFile(data, modelDir) - updateOptimizedBpjsRoutesFile(data) - - fmt.Printf("✅ Successfully generated optimized BPJS handler: %s\n", entityName) - if category != "" { - fmt.Printf("📁 Category: %s\n", category) - } - fmt.Printf("📁 Handler: %s\n", filepath.Join(handlerDir, entityLower+".go")) - fmt.Printf("📁 Model: %s\n", filepath.Join(modelDir, entityLower+".go")) -} - -// ================= OPTIMIZED HANDLER GENERATION ===================== - -func generateOptimizedBpjsHandlerFile(data BpjsHandlerData, handlerDir string) { - var modelsImportPath string - if data.Category != "" { - modelsImportPath = data.ModuleName + "/internal/models/" + data.Category - } else { - modelsImportPath = data.ModuleName + "/internal/models" - } - - handlerContent := `package handlers - -import ( - "context" - "fmt" - "net/http" - "time" - - "` + data.ModuleName + `/internal/config" - "` + modelsImportPath + `" - services "` + data.ModuleName + `/internal/services/bpjs" - "` + data.ModuleName + `/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/go-playground/validator/v10" -) - -// ` + data.Name + `Handler handles ` + data.NameLower + ` BPJS services with optimized error handling and logging -type ` + data.Name + `Handler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config *config.BpjsConfig -} - -// HandlerConfig contains configuration for ` + data.Name + `Handler -type ` + data.Name + `HandlerConfig struct { - BpjsConfig *config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} - -// New` + data.Name + `Handler creates a new optimized ` + data.Name + `Handler -func New` + data.Name + `Handler(cfg *` + data.Name + `HandlerConfig) *` + data.Name + `Handler { - return &` + data.Name + `Handler{ - service: services.NewService(*cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -}` - - // Add optimized methods based on flags - if data.HasPost { - handlerContent += generateOptimizedBpjsCreateMethod(data) - } - - if data.HasPut { - handlerContent += generateOptimizedBpjsUpdateMethod(data) - } - - if data.HasDelete { - handlerContent += generateOptimizedBpjsDeleteMethod(data) - } - - if data.HasGet { - handlerContent += generateOptimizedBpjsGetMethod(data) - } - - // Add helper methods - handlerContent += generateHelperMethods(data) - - writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent) -} - -func generateOptimizedBpjsCreateMethod(data BpjsHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) - } else { - routePath = data.NameLower - tagName = strings.Title(data.NameLower) - } - - return ` - -// Create` + data.Name + ` creates a new ` + data.Name + ` with comprehensive error handling and validation -// @Summary Create a new ` + data.NameUpper + ` -// @Description Create a new ` + data.Name + ` in BPJS system with enhanced validation and logging -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param request body models.` + data.Name + `PostRequest true "` + data.Name + ` creation request" -// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` created successfully" -// @Failure 400 {object} models.` + data.Name + `Response "Bad request - validation error" -// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" -// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" -// @Router /api/v1/` + routePath + ` [post] -func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - - h.logger.Info("Creating ` + data.Name + `", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - }) - - var req models.` + data.Name + `PostRequest - req.RequestID = requestID - req.Timestamp = startTime - - // Bind and validate JSON - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Failed to bind JSON", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT", - "Format request tidak valid", err.Error(), requestID) - return - } - - // Custom validation - if err := req.Validate(); err != nil { - h.logger.Error("Custom validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", - "Validasi gagal", err.Error(), requestID) - return - } - - // Struct validation - if err := h.validator.Struct(&req); err != nil { - h.logger.Error("Struct validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", - "Validasi struktur gagal", h.formatValidationError(err), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - var rawResponse models.BpjsRawResponse - if err := h.service.Post(ctx, "` + data.PostEndpoint + `", req, &rawResponse); err != nil { - h.logger.Error("Failed to call BPJS service", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "endpoint": "` + data.PostEndpoint + `", - }) - - statusCode, errorCode := h.categorizeError(err) - h.sendErrorResponse(c, statusCode, errorCode, - "Gagal membuat ` + data.Name + `", err.Error(), requestID) - return - } - - // Check BPJS response - if rawResponse.MetaData.Code != "200" { - h.logger.Warn("BPJS returned error", map[string]interface{}{ - "bpjs_code": rawResponse.MetaData.Code, - "bpjs_message": rawResponse.MetaData.Message, - "request_id": requestID, - }) - - statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) - h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, - rawResponse.MetaData.Message, "", requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("` + data.Name + ` created successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - }) - - h.sendSuccessResponse(c, "` + data.Name + ` berhasil dibuat", rawResponse.Response, requestID) -}` -} - -func generateOptimizedBpjsUpdateMethod(data BpjsHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) - } else { - routePath = data.NameLower - tagName = strings.Title(data.NameLower) - } - - return ` - -// Update` + data.Name + ` updates an existing ` + data.Name + ` with comprehensive validation -// @Summary Update an existing ` + data.NameUpper + ` -// @Description Update an existing ` + data.Name + ` in BPJS system with enhanced validation and logging -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param request body models.` + data.Name + `PutRequest true "` + data.Name + ` update request" -// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` updated successfully" -// @Failure 400 {object} models.` + data.Name + `Response "Bad request - validation error" -// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" -// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" -// @Router /api/v1/` + routePath + ` [put] -func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - - h.logger.Info("Updating ` + data.Name + `", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - }) - - var req models.` + data.Name + `PutRequest - req.RequestID = requestID - req.Timestamp = startTime - - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Failed to bind JSON for update", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT", - "Format request tidak valid", err.Error(), requestID) - return - } - - if err := req.Validate(); err != nil { - h.logger.Error("Update validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", - "Validasi gagal", err.Error(), requestID) - return - } - - if err := h.validator.Struct(&req); err != nil { - h.logger.Error("Struct validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", - "Validasi struktur gagal", h.formatValidationError(err), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - var rawResponse models.BpjsRawResponse - if err := h.service.Put(ctx, "` + data.PutEndpoint + `", req, &rawResponse); err != nil { - h.logger.Error("Failed to update ` + data.Name + `", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - statusCode, errorCode := h.categorizeError(err) - h.sendErrorResponse(c, statusCode, errorCode, - "Gagal memperbarui ` + data.Name + `", err.Error(), requestID) - return - } - - if rawResponse.MetaData.Code != "200" { - h.logger.Warn("BPJS update returned error", map[string]interface{}{ - "bpjs_code": rawResponse.MetaData.Code, - "bpjs_message": rawResponse.MetaData.Message, - "request_id": requestID, - }) - - statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) - h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, - rawResponse.MetaData.Message, "", requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("` + data.Name + ` updated successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - }) - - h.sendSuccessResponse(c, "` + data.Name + ` berhasil diperbarui", rawResponse.Response, requestID) -}` -} - -func generateOptimizedBpjsDeleteMethod(data BpjsHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) - } else { - routePath = data.NameLower - tagName = strings.Title(data.NameLower) - } - - return ` - -// Delete` + data.Name + ` deletes an existing ` + data.Name + ` with comprehensive validation -// @Summary Delete an existing ` + data.NameUpper + ` -// @Description Delete a ` + data.Name + ` by ID with enhanced validation and logging -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param id path string true "` + data.Name + ` ID" -// @Param user query string true "User identifier" -// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` deleted successfully" -// @Failure 400 {object} models.` + data.Name + `Response "Bad request - missing parameters" -// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" -// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [delete] -func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - user := c.Query("user") - - h.logger.Info("Deleting ` + data.Name + `", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "id": id, - "user": user, - }) - - // Validate parameters - if id == "" { - h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", - "Parameter ID wajib diisi", "", requestID) - return - } - - if user == "" { - h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", - "Parameter user wajib diisi", "", requestID) - return - } - - req := models.` + data.Name + `DeleteRequest{ - BaseRequest: models.BaseRequest{ - RequestID: requestID, - Timestamp: startTime, - }, - T` + data.Name + `: models.` + data.Name + `DeleteData{ - ID: id, - User: user, - }, - } - - if err := req.Validate(); err != nil { - h.logger.Error("Delete validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", - "Validasi gagal", err.Error(), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - var rawResponse models.BpjsRawResponse - if err := h.service.Delete(ctx, "` + data.DeleteEndpoint + `", req); err != nil { - h.logger.Error("Failed to delete ` + data.Name + `", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "id": id, - }) - - statusCode, errorCode := h.categorizeError(err) - h.sendErrorResponse(c, statusCode, errorCode, - "Gagal menghapus ` + data.Name + `", err.Error(), requestID) - return - } - - if rawResponse.MetaData.Code != "200" { - h.logger.Warn("BPJS delete returned error", map[string]interface{}{ - "bpjs_code": rawResponse.MetaData.Code, - "bpjs_message": rawResponse.MetaData.Message, - "request_id": requestID, - "id": id, - }) - - statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) - h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, - rawResponse.MetaData.Message, "", requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("` + data.Name + ` deleted successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "id": id, - }) - - h.sendSuccessResponse(c, "` + data.Name + ` berhasil dihapus", rawResponse.Response, requestID) -}` -} - -func generateOptimizedBpjsGetMethod(data BpjsHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) - } else { - routePath = data.NameLower - tagName = strings.Title(data.NameLower) - } - - return ` - -// Get` + data.Name + ` retrieves ` + data.Name + ` details with comprehensive error handling -// @Summary Get an existing ` + data.NameUpper + ` -// @Description Retrieve a ` + data.Name + ` by ID with enhanced validation and logging -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param id path string true "` + data.Name + ` ID" -// @Success 200 {object} models.` + data.Name + `Response "Data ` + data.Name + ` retrieved successfully" -// @Failure 400 {object} models.` + data.Name + `Response "Bad request - invalid ID" -// @Failure 404 {object} models.` + data.Name + `Response "` + data.Name + ` not found" -// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [get] -func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - - h.logger.Info("Getting ` + data.Name + `", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "id": id, - }) - - if id == "" { - h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", - "Parameter ID wajib diisi", "", requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("` + strings.Replace(data.GetEndpoint, "{id}", "%s", 1) + `", id) - var rawResponse models.BpjsRawResponse - - if err := h.service.Get(ctx, endpoint, &rawResponse); err != nil { - h.logger.Error("Failed to get ` + data.Name + `", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "id": id, - }) - - statusCode, errorCode := h.categorizeError(err) - h.sendErrorResponse(c, statusCode, errorCode, - "Gagal mengambil data ` + data.Name + `", err.Error(), requestID) - return - } - - if rawResponse.MetaData.Code != "200" { - // Handle specific BPJS error codes - if rawResponse.MetaData.Code == "201" { - h.logger.Info("` + data.Name + ` not found", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - h.sendErrorResponse(c, http.StatusNotFound, "DATA_NOT_FOUND", - "Data ` + data.Name + ` tidak ditemukan", rawResponse.MetaData.Message, requestID) - return - } - - h.logger.Warn("BPJS get returned error", map[string]interface{}{ - "bpjs_code": rawResponse.MetaData.Code, - "bpjs_message": rawResponse.MetaData.Message, - "request_id": requestID, - "id": id, - }) - - statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) - h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, - rawResponse.MetaData.Message, "", requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("` + data.Name + ` retrieved successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "id": id, - }) - - h.sendSuccessResponse(c, "Data ` + data.Name + ` berhasil diambil", rawResponse.Response, requestID) -}` -} - -func generateHelperMethods(data BpjsHandlerData) string { - return ` - -// Helper methods for ` + data.Name + `Handler -func (h *` + data.Name + `Handler) sendSuccessResponse(c *gin.Context, message string, data interface{}, requestID string) { - response := models.` + data.Name + `Response{ - BaseResponse: models.BaseResponse{ - Status: "success", - Message: message, - Data: data, - Metadata: &models.ResponseMetadata{ - Timestamp: time.Now(), - Version: "2.0", - RequestID: requestID, - }, - }, - } - c.JSON(http.StatusOK, response) -} - -func (h *` + data.Name + `Handler) sendErrorResponse(c *gin.Context, statusCode int, errorCode, message, details, requestID string) { - response := models.` + data.Name + `Response{ - BaseResponse: models.BaseResponse{ - Status: "error", - Message: message, - Error: &models.ErrorResponse{ - Code: errorCode, - Message: message, - Details: details, - }, - Metadata: &models.ResponseMetadata{ - Timestamp: time.Now(), - Version: "2.0", - RequestID: requestID, - }, - }, - } - c.JSON(statusCode, response) -} - -func (h *` + data.Name + `Handler) formatValidationError(err error) string { - if validationErrors, ok := err.(validator.ValidationErrors); ok { - var messages []string - for _, e := range validationErrors { - switch e.Tag() { - case "required": - messages = append(messages, fmt.Sprintf("%s wajib diisi", e.Field())) - case "min": - messages = append(messages, fmt.Sprintf("%s minimal %s karakter", e.Field(), e.Param())) - case "max": - messages = append(messages, fmt.Sprintf("%s maksimal %s karakter", e.Field(), e.Param())) - case "oneof": - messages = append(messages, fmt.Sprintf("%s harus salah satu dari: %s", e.Field(), e.Param())) - default: - messages = append(messages, fmt.Sprintf("%s tidak valid", e.Field())) - } - } - return fmt.Sprintf("Validasi gagal: %v", messages) - } - return err.Error() -} - -func (h *` + data.Name + `Handler) categorizeError(err error) (int, string) { - if err == nil { - return http.StatusOK, "SUCCESS" - } - - errStr := err.Error() - - if h.isTimeoutError(err) { - return http.StatusRequestTimeout, "REQUEST_TIMEOUT" - } - - if h.isNetworkError(err) { - return http.StatusBadGateway, "NETWORK_ERROR" - } - - if h.isAuthError(errStr) { - return http.StatusUnauthorized, "AUTH_ERROR" - } - - return http.StatusInternalServerError, "INTERNAL_ERROR" -} - -func (h *` + data.Name + `Handler) mapBpjsCodeToHttpStatus(bpjsCode string) int { - switch bpjsCode { - case "200": - return http.StatusOK - case "201": - return http.StatusNotFound - case "202": - return http.StatusBadRequest - case "400": - return http.StatusBadRequest - case "401": - return http.StatusUnauthorized - case "403": - return http.StatusForbidden - case "404": - return http.StatusNotFound - case "500": - return http.StatusInternalServerError - default: - return http.StatusUnprocessableEntity - } -} - -func (h *` + data.Name + `Handler) isTimeoutError(err error) bool { - return err != nil && (err.Error() == "context deadline exceeded" || - err.Error() == "timeout") -} - -func (h *` + data.Name + `Handler) isNetworkError(err error) bool { - return err != nil && (err.Error() == "connection refused" || - err.Error() == "no such host") -} - -func (h *` + data.Name + `Handler) isAuthError(errStr string) bool { - return errStr == "unauthorized" || errStr == "invalid credentials" -}` -} - -// ================= OPTIMIZED MODEL GENERATION ===================== - -func generateOptimizedBpjsModelFile(data BpjsHandlerData, modelDir string) { - modelContent := `package models - -import ( - "encoding/json" - "fmt" - "time" -) - -// ` + data.Name + ` BPJS Models with Enhanced Validation -// Generated at: ` + data.Timestamp + ` -// Category: ` + data.Category + ` - -// Base request/response structures -type BaseRequest struct { - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` - Timestamp time.Time ` + "`json:\"timestamp,omitempty\"`" + ` -} - -type BaseResponse struct { - Status string ` + "`json:\"status\"`" + ` - Message string ` + "`json:\"message\"`" + ` - Data interface{} ` + "`json:\"data,omitempty\"`" + ` - Error *ErrorResponse ` + "`json:\"error,omitempty\"`" + ` - Metadata *ResponseMetadata ` + "`json:\"metadata,omitempty\"`" + ` -} - -type ErrorResponse struct { - Code string ` + "`json:\"code\"`" + ` - Message string ` + "`json:\"message\"`" + ` - Details string ` + "`json:\"details,omitempty\"`" + ` -} - -type ResponseMetadata struct { - Timestamp time.Time ` + "`json:\"timestamp\"`" + ` - Version string ` + "`json:\"version\"`" + ` - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` -} - -// ` + data.Name + ` Response Structure -type ` + data.Name + `Response struct { - BaseResponse -} - -// BPJS Raw Response Structure -type BpjsRawResponse struct { - MetaData struct { - Code string ` + "`json:\"code\"`" + ` - Message string ` + "`json:\"message\"`" + ` - } ` + "`json:\"metaData\"`" + ` - Response interface{} ` + "`json:\"response\"`" + ` -}` - - if data.HasPost { - modelContent += ` - -// ` + data.Name + ` POST Request Structure with Enhanced Validation -type ` + data.Name + `PostRequest struct { - BaseRequest - T` + data.Name + ` ` + data.Name + `Post ` + "`json:\"tsep\" binding:\"required\" validate:\"required\"`" + ` -} - -type ` + data.Name + `Post struct { - // Core BPJS fields - customize based on your specific requirements - NoKartu string ` + "`json:\"noKartu\" binding:\"required\" validate:\"required,min=13,max=13\"`" + ` - TglLayanan string ` + "`json:\"tglLayanan\" binding:\"required\" validate:\"required\"`" + ` - JnsPelayanan string ` + "`json:\"jnsPelayanan\" binding:\"required\" validate:\"required,oneof=1 2\"`" + ` - PpkPelayanan string ` + "`json:\"ppkPelayanan\" binding:\"required\" validate:\"required\"`" + ` - Catatan string ` + "`json:\"catatan\" validate:\"omitempty,max=200\"`" + ` - User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` -} - -// Validate validates the ` + data.Name + `PostRequest -func (r *` + data.Name + `PostRequest) Validate() error { - if r.T` + data.Name + `.NoKartu == "" { - return fmt.Errorf("nomor kartu tidak boleh kosong") - } - - if len(r.T` + data.Name + `.NoKartu) != 13 { - return fmt.Errorf("nomor kartu harus 13 digit") - } - - if _, err := time.Parse("2006-01-02", r.T` + data.Name + `.TglLayanan); err != nil { - return fmt.Errorf("format tanggal layanan tidak valid, gunakan yyyy-MM-dd") - } - - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `PostRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` - } - - if data.HasPut { - modelContent += ` - -// ` + data.Name + ` PUT Request Structure with Enhanced Validation -type ` + data.Name + `PutRequest struct { - BaseRequest - T` + data.Name + ` ` + data.Name + `Put ` + "`json:\"tsep\" binding:\"required\" validate:\"required\"`" + ` -} - -type ` + data.Name + `Put struct { - ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + ` - NoKartu string ` + "`json:\"noKartu\" validate:\"omitempty,min=13,max=13\"`" + ` - TglLayanan string ` + "`json:\"tglLayanan\" validate:\"omitempty\"`" + ` - JnsPelayanan string ` + "`json:\"jnsPelayanan\" validate:\"omitempty,oneof=1 2\"`" + ` - PpkPelayanan string ` + "`json:\"ppkPelayanan\" validate:\"omitempty\"`" + ` - Catatan string ` + "`json:\"catatan\" validate:\"omitempty,max=200\"`" + ` - User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` -} - -// Validate validates the ` + data.Name + `PutRequest -func (r *` + data.Name + `PutRequest) Validate() error { - if r.T` + data.Name + `.ID == "" { - return fmt.Errorf("ID tidak boleh kosong") - } - - if r.T` + data.Name + `.NoKartu != "" && len(r.T` + data.Name + `.NoKartu) != 13 { - return fmt.Errorf("nomor kartu harus 13 digit") - } - - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `PutRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` - } - - if data.HasDelete { - modelContent += ` - -// ` + data.Name + ` DELETE Request Structure with Enhanced Validation -type ` + data.Name + `DeleteRequest struct { - BaseRequest - T` + data.Name + ` ` + data.Name + `DeleteData ` + "`json:\"tsep\" binding:\"required\" validate:\"required\"`" + ` -} - -type ` + data.Name + `DeleteData struct { - ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + ` - User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` -} - -// Validate validates the ` + data.Name + `DeleteRequest -func (r *` + data.Name + `DeleteRequest) Validate() error { - if r.T` + data.Name + `.ID == "" { - return fmt.Errorf("ID tidak boleh kosong") - } - - if r.T` + data.Name + `.User == "" { - return fmt.Errorf("User tidak boleh kosong") - } - - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `DeleteRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` - } - - // Add common helper structures - modelContent += ` - -// Common Helper Structures for BPJS -type Flag struct { - Flag string ` + "`json:\"flag\" binding:\"required\" validate:\"required,oneof=0 1\"`" + ` -} - -type Poli struct { - Tujuan string ` + "`json:\"tujuan\" binding:\"required\" validate:\"required\"`" + ` - Eksekutif string ` + "`json:\"eksekutif\" binding:\"required\" validate:\"required,oneof=0 1\"`" + ` -} - -type KlsRawat struct { - KlsRawatHak string ` + "`json:\"klsRawatHak\" binding:\"required\" validate:\"required,oneof=1 2 3\"`" + ` - KlsRawatNaik string ` + "`json:\"klsRawatNaik\" validate:\"omitempty,oneof=1 2 3 4 5 6 7\"`" + ` - Pembiayaan string ` + "`json:\"pembiayaan\" validate:\"omitempty,oneof=1 2 3\"`" + ` - PenanggungJawab string ` + "`json:\"penanggungJawab\" validate:\"omitempty,max=100\"`" + ` -} - -// Validation helper functions -func IsValidStatus(status string) bool { - validStatuses := []string{"active", "inactive", "pending", "processed"} - for _, v := range validStatuses { - if v == status { - return true - } - } - return false -} - -func IsValidJnsPelayanan(jns string) bool { - return jns == "1" || jns == "2" // 1: rawat jalan, 2: rawat inap -} - -func IsValidKlsRawat(kls string) bool { - validKelas := []string{"1", "2", "3"} - for _, v := range validKelas { - if v == kls { - return true - } - } - return false -}` - - writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent) -} - -// ================= OPTIMIZED ROUTES GENERATION ===================== - -func updateOptimizedBpjsRoutesFile(data BpjsHandlerData) { - routesFile := "internal/routes/v1/routes.go" - content, err := os.ReadFile(routesFile) + // Load configuration + config, err := loadConfig(configFile) if err != nil { - fmt.Printf("⚠️ Could not read routes.go: %v\n", err) - fmt.Printf("📝 Please manually add these optimized routes to your routes.go file:\n") - printOptimizedBpjsRoutesSample(data) - return + fmt.Printf("❌ Error loading config: %v\n", err) + os.Exit(1) } - routesContent := string(content) - - var importPath, importAlias string - if data.Category != "" { - importPath = fmt.Sprintf("%s/internal/handlers/%s", data.ModuleName, data.Category) - importAlias = data.NameLower + "Handlers" - } else { - importPath = fmt.Sprintf("%s/internal/handlers", data.ModuleName) - importAlias = data.NameLower + "Handlers" + fmt.Println("🚀 Starting BPJS Dynamic Handler Generation...") + fmt.Printf("📁 Config file: %s\n", configFile) + if targetService != "" { + fmt.Printf("🎯 Target service: %s\n", targetService) } - // Check and add import - importPattern := fmt.Sprintf("%s \"%s\"", importAlias, importPath) - if !strings.Contains(routesContent, importPattern) { - importToAdd := fmt.Sprintf("\t%s \"%s\"", importAlias, importPath) - if strings.Contains(routesContent, "import (") { - routesContent = strings.Replace(routesContent, "import (", - "import (\n"+importToAdd, 1) + generated := 0 + errors := 0 + + // Generate handlers + for serviceName, service := range config.Services { + if targetService != "" && serviceName != targetService { + continue } - } - // Build optimized routes - newRoutes := fmt.Sprintf("\t\t// Optimized %s endpoints with enhanced error handling\n", data.Name) - if data.Category != "" { - newRoutes = fmt.Sprintf("\t\t// Optimized %s %s endpoints with enhanced error handling\n", data.Category, data.Name) - } - - newRoutes += fmt.Sprintf("\t\t%sHandlerConfig := &%s.%sHandlerConfig{\n", data.NameLower, importAlias, data.Name) - newRoutes += fmt.Sprintf("\t\t\tBpjsConfig: &config.LoadConfig().Bpjs,\n") - newRoutes += fmt.Sprintf("\t\t\tLogger: logger.GetLogger(),\n") - newRoutes += fmt.Sprintf("\t\t\tValidator: validator.New(),\n") - newRoutes += fmt.Sprintf("\t\t}\n") - newRoutes += fmt.Sprintf("\t\t%sHandler := %s.New%sHandler(%sHandlerConfig)\n", - data.NameLower, importAlias, data.Name, data.NameLower) - - var routePath string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - } else { - routePath = data.NameLower - } - - if data.HasGet { - newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s/:id\", %sHandler.Get%s)\n", - routePath, data.NameLower, data.Name) - } - - if data.HasPost { - newRoutes += fmt.Sprintf("\t\tv1.POST(\"/%s\", %sHandler.Create%s)\n", - routePath, data.NameLower, data.Name) - } - - if data.HasPut { - newRoutes += fmt.Sprintf("\t\tv1.PUT(\"/%s\", %sHandler.Update%s)\n", - routePath, data.NameLower, data.Name) - } - - if data.HasDelete { - newRoutes += fmt.Sprintf("\t\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", - routePath, data.NameLower, data.Name) - } - - newRoutes += "\n" - - // Insert routes - insertMarker := "\t\tprotected := v1.Group(\"/\")" - if strings.Contains(routesContent, insertMarker) { - if !strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) { - routesContent = strings.Replace(routesContent, insertMarker, - newRoutes+insertMarker, 1) - } else { - fmt.Printf("✅ Optimized routes for %s already exist, skipping...\n", data.Name) - return + fmt.Printf("🔧 Generating handler for service: %s (%s)\n", service.Name, service.Category) + err := generateHandler(serviceName, service, config.Global) + if err != nil { + fmt.Printf("❌ Error generating handler for %s: %v\n", serviceName, err) + errors++ + continue } + fmt.Printf("✅ Generated handler: %s_handler.go\n", strings.ToLower(serviceName)) + generated++ } - if err := os.WriteFile(routesFile, []byte(routesContent), 0644); err != nil { - fmt.Printf("Error writing routes.go: %v\n", err) - return + // Summary + fmt.Println("\n📊 Generation Summary:") + fmt.Printf(" ✅ Successfully generated: %d handlers\n", generated) + if errors > 0 { + fmt.Printf(" ❌ Failed: %d handlers\n", errors) } - if data.Category != "" { - fmt.Printf("✅ Updated routes.go with optimized %s %s endpoints\n", data.Category, data.Name) - } else { - fmt.Printf("✅ Updated routes.go with optimized %s endpoints\n", data.Name) + if generated > 0 { + fmt.Println("🎉 Generation completed successfully!") } } -func printOptimizedBpjsRoutesSample(data BpjsHandlerData) { - var importAlias string - if data.Category != "" { - importAlias = data.NameLower + "Handlers" - } else { - importAlias = data.NameLower + "Handlers" - } - - var routePath string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - } else { - routePath = data.NameLower - } - - if data.Category != "" { - fmt.Printf(` -// Optimized %s %s endpoints with enhanced error handling -%sHandlerConfig := &%s.%sHandlerConfig{ - BpjsConfig: &config.LoadConfig().Bpjs, - Logger: logger.GetLogger(), - Validator: validator.New(), -} -%sHandler := %s.New%sHandler(%sHandlerConfig) -`, data.Category, data.Name, data.NameLower, importAlias, data.Name, data.NameLower, importAlias, data.Name, data.NameLower) - } else { - fmt.Printf(` -// Optimized %s endpoints with enhanced error handling -%sHandlerConfig := &%s.%sHandlerConfig{ - BpjsConfig: &config.LoadConfig().Bpjs, - Logger: logger.GetLogger(), - Validator: validator.New(), -} -%sHandler := %s.New%sHandler(%sHandlerConfig) -`, data.Name, data.NameLower, importAlias, data.Name, data.NameLower, importAlias, data.Name, data.NameLower) - } - - if data.HasGet { - fmt.Printf("\tv1.GET(\"/%s/:id\", %sHandler.Get%s)\n", routePath, data.NameLower, data.Name) - } - if data.HasPost { - fmt.Printf("\tv1.POST(\"/%s\", %sHandler.Create%s)\n", routePath, data.NameLower, data.Name) - } - if data.HasPut { - fmt.Printf("\tv1.PUT(\"/%s\", %sHandler.Update%s)\n", routePath, data.NameLower, data.Name) - } - if data.HasDelete { - fmt.Printf("\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", routePath, data.NameLower, data.Name) - } - +func printUsage() { + fmt.Println("BPJS Dynamic Handler Generator") fmt.Println() + fmt.Println("Usage:") + fmt.Println(" go run generate-dynamic-handler.go [service-name]") + fmt.Println() + fmt.Println("Examples:") + fmt.Println(" go run generate-dynamic-handler.go services-config.yaml") + fmt.Println(" go run generate-dynamic-handler.go services-config.yaml vclaim") } -// ================= UTILITY FUNCTIONS ===================== - -func writeFile(filename, content string) { - if err := os.WriteFile(filename, []byte(content), 0644); err != nil { - fmt.Printf("❌ Error creating file %s: %v\n", filename, err) - return +func loadConfig(filename string) (*ServiceConfig, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %w", err) } - fmt.Printf("✅ Generated optimized file: %s\n", filename) + var config ServiceConfig + err = yaml.Unmarshal(data, &config) + if err != nil { + return nil, fmt.Errorf("failed to parse YAML config: %w", err) + } + + // Set default values + if config.Global.ModuleName == "" { + config.Global.ModuleName = "api-service" + } + if config.Global.OutputDir == "" { + config.Global.OutputDir = "internal/handlers" + } + + return &config, nil +} + +func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error { + // Prepare template data + templateData := TemplateData{ + ServiceName: service.Name, + ServiceLower: strings.ToLower(service.Name), + ServiceUpper: strings.ToUpper(service.Name), + Category: service.Category, + Package: service.Package, + Description: service.Description, + BaseURL: service.BaseURL, + Timeout: getOrDefault(service.Timeout, 30), + RetryCount: getOrDefault(service.RetryCount, 3), + Timestamp: time.Now().Format("2006-01-02 15:04:05"), + ModuleName: globalConfig.ModuleName, + HasValidator: true, + HasLogger: globalConfig.EnableLogging, + HasMetrics: globalConfig.EnableMetrics, + HasSwagger: globalConfig.EnableSwagger, + Dependencies: service.Dependencies, // Now []string + Middleware: service.Middleware, // Now []string + GlobalConfig: globalConfig, + } + + // Check for advanced features + for _, endpoint := range service.Endpoints { + if endpoint.RequireAuth { + templateData.HasAuth = true + } + if endpoint.CacheEnabled { + templateData.HasCache = true + } + } + + // Process endpoints + for endpointName, endpoint := range service.Endpoints { + endpointData := processEndpoint(endpointName, endpoint) + templateData.Endpoints = append(templateData.Endpoints, endpointData) + } + + // Create output directory + outputDir := globalConfig.OutputDir + if outputDir == "" { + outputDir = "internal/handlers" + } + if err := os.MkdirAll(outputDir, 0755); err != nil { + return fmt.Errorf("failed to create output directory: %w", err) + } + + // Generate handler file + filename := filepath.Join(outputDir, fmt.Sprintf("%s_handler.go", strings.ToLower(serviceName))) + + // Create template with custom functions + tmpl := template.New("handler").Funcs(template.FuncMap{ + "contains": strings.Contains, + "join": strings.Join, + "title": strings.Title, + "trimPrefix": strings.TrimPrefix, + }) + + tmpl, err := tmpl.Parse(handlerTemplate) + if err != nil { + return fmt.Errorf("failed to parse template: %w", err) + } + + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + defer file.Close() + + err = tmpl.Execute(file, templateData) + if err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + + return nil +} + +func processEndpoint(name string, endpoint Endpoint) EndpointData { + data := EndpointData{ + Name: strings.Title(name), + NameLower: strings.ToLower(name), + NameUpper: strings.ToUpper(name), + NameCamel: toCamelCase(name), + Methods: endpoint.Methods, + GetPath: endpoint.GetPath, + PostPath: endpoint.PostPath, + PutPath: endpoint.PutPath, + DeletePath: endpoint.DeletePath, + PatchPath: endpoint.PatchPath, + Model: endpoint.Model, + ResponseModel: endpoint.ResponseModel, + Description: endpoint.Description, + Summary: endpoint.Summary, + Tags: endpoint.Tags, + RequireAuth: endpoint.RequireAuth, + RateLimit: endpoint.RateLimit, + CacheEnabled: endpoint.CacheEnabled, + CacheTTL: getOrDefault(endpoint.CacheTTL, 300), + CustomHeaders: endpoint.CustomHeaders, + } + + // Set method flags and extract path parameters + for _, method := range endpoint.Methods { + switch strings.ToUpper(method) { + case "GET": + data.HasGet = true + data.PathParams = extractPathParams(endpoint.GetPath) + case "POST": + data.HasPost = true + case "PUT": + data.HasPut = true + data.PathParams = extractPathParams(endpoint.PutPath) + case "DELETE": + data.HasDelete = true + data.PathParams = extractPathParams(endpoint.DeletePath) + case "PATCH": + data.HasPatch = true + data.PathParams = extractPathParams(endpoint.PatchPath) + } + } + + return data +} + +func extractPathParams(path string) []string { + if path == "" { + return nil + } + + var params []string + parts := strings.Split(path, "/") + for _, part := range parts { + if strings.HasPrefix(part, ":") { + params = append(params, strings.TrimPrefix(part, ":")) + } + } + + return params +} + +func toCamelCase(str string) string { + words := strings.FieldsFunc(str, func(c rune) bool { + return c == '_' || c == '-' || c == ' ' + }) + + if len(words) == 0 { + return str + } + + result := strings.ToLower(words[0]) + for _, word := range words[1:] { + result += strings.Title(strings.ToLower(word)) + } + + return result +} + +func getOrDefault(value, defaultValue int) int { + if value == 0 { + return defaultValue + } + return value } diff --git a/tools/bpjsgenerate-handler.backup b/tools/bpjsgenerate-handler.backup new file mode 100644 index 00000000..10761ef8 --- /dev/null +++ b/tools/bpjsgenerate-handler.backup @@ -0,0 +1,1132 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" +) + +// BpjsHandlerData contains template data for BPJS handler generation +type BpjsHandlerData struct { + Name string + NameLower string + NameUpper string + Category string + CategoryPath string + ModuleName string + HasGet bool + HasPost bool + HasPut bool + HasDelete bool + GetEndpoint string + PostEndpoint string + PutEndpoint string + DeleteEndpoint string + Timestamp string +} + +func main() { + if len(os.Args) < 2 { + fmt.Println("Usage: go run generate-bpjs-handler.go [category/]entity [methods]") + fmt.Println("Examples:") + fmt.Println(" go run generate-bpjs-handler.go vclaim/sep get post put delete") + fmt.Println(" go run generate-bpjs-handler.go eclaim/klaim get post") + fmt.Println(" go run generate-bpjs-handler.go peserta get") + os.Exit(1) + } + + // Parse entity path + entityPath := os.Args[1] + methods := []string{} + if len(os.Args) > 2 { + methods = os.Args[2:] + } else { + methods = []string{"get", "post", "put", "delete"} + } + + // Parse category and entity + var category, entityName string + if strings.Contains(entityPath, "/") { + parts := strings.Split(entityPath, "/") + if len(parts) != 2 { + fmt.Println("❌ Error: Invalid path format. Use 'category/entity' or just 'entity'") + os.Exit(1) + } + category = parts[0] + entityName = parts[1] + } else { + category = "" + entityName = entityPath + } + + // Format names + entityName = strings.Title(entityName) + entityLower := strings.ToLower(entityName) + entityUpper := strings.ToUpper(entityName) + + data := BpjsHandlerData{ + Name: entityName, + NameLower: entityLower, + NameUpper: entityUpper, + Category: category, + CategoryPath: category, + ModuleName: "api-service", + Timestamp: time.Now().Format("2006-01-02 15:04:05"), + } + + // Set methods and endpoints + for _, m := range methods { + switch strings.ToLower(m) { + case "get": + data.HasGet = true + data.GetEndpoint = fmt.Sprintf("%s/{id}", entityUpper) + case "post": + data.HasPost = true + data.PostEndpoint = fmt.Sprintf("%s/2.0/insert", entityUpper) + case "put": + data.HasPut = true + data.PutEndpoint = fmt.Sprintf("%s/2.0/update", entityUpper) + case "delete": + data.HasDelete = true + data.DeleteEndpoint = fmt.Sprintf("%s/2.0/delete", entityUpper) + } + } + + // Create directories + var handlerDir, modelDir string + if category != "" { + handlerDir = filepath.Join("internal", "handlers", category) + modelDir = filepath.Join("internal", "models", category) + } else { + handlerDir = filepath.Join("internal", "handlers") + modelDir = filepath.Join("internal", "models") + } + + for _, d := range []string{handlerDir, modelDir} { + if err := os.MkdirAll(d, 0755); err != nil { + panic(err) + } + } + + // Generate files + generateOptimizedBpjsHandlerFile(data, handlerDir) + generateOptimizedBpjsModelFile(data, modelDir) + updateOptimizedBpjsRoutesFile(data) + + fmt.Printf("✅ Successfully generated optimized BPJS handler: %s\n", entityName) + if category != "" { + fmt.Printf("📁 Category: %s\n", category) + } + fmt.Printf("📁 Handler: %s\n", filepath.Join(handlerDir, entityLower+".go")) + fmt.Printf("📁 Model: %s\n", filepath.Join(modelDir, entityLower+".go")) +} + +// ================= OPTIMIZED HANDLER GENERATION ===================== + +func generateOptimizedBpjsHandlerFile(data BpjsHandlerData, handlerDir string) { + var modelsImportPath string + if data.Category != "" { + modelsImportPath = data.ModuleName + "/internal/models/" + data.Category + } else { + modelsImportPath = data.ModuleName + "/internal/models" + } + + handlerContent := `package handlers + +import ( + "context" + "fmt" + "net/http" + "time" + + "` + data.ModuleName + `/internal/config" + "` + modelsImportPath + `" + services "` + data.ModuleName + `/internal/services/bpjs" + "` + data.ModuleName + `/pkg/logger" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/go-playground/validator/v10" +) + +// ` + data.Name + `Handler handles ` + data.NameLower + ` BPJS services with optimized error handling and logging +type ` + data.Name + `Handler struct { + service services.VClaimService + validator *validator.Validate + logger logger.Logger + config *config.BpjsConfig +} + +// HandlerConfig contains configuration for ` + data.Name + `Handler +type ` + data.Name + `HandlerConfig struct { + BpjsConfig *config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// New` + data.Name + `Handler creates a new optimized ` + data.Name + `Handler +func New` + data.Name + `Handler(cfg *` + data.Name + `HandlerConfig) *` + data.Name + `Handler { + return &` + data.Name + `Handler{ + service: services.NewService(*cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +}` + + // Add optimized methods based on flags + if data.HasPost { + handlerContent += generateOptimizedBpjsCreateMethod(data) + } + + if data.HasPut { + handlerContent += generateOptimizedBpjsUpdateMethod(data) + } + + if data.HasDelete { + handlerContent += generateOptimizedBpjsDeleteMethod(data) + } + + if data.HasGet { + handlerContent += generateOptimizedBpjsGetMethod(data) + } + + // Add helper methods + handlerContent += generateHelperMethods(data) + + writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent) +} + +func generateOptimizedBpjsCreateMethod(data BpjsHandlerData) string { + var routePath, tagName string + if data.Category != "" { + routePath = data.Category + "/" + data.NameLower + tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) + } else { + routePath = data.NameLower + tagName = strings.Title(data.NameLower) + } + + return ` + +// Create` + data.Name + ` creates a new ` + data.Name + ` with comprehensive error handling and validation +// @Summary Create a new ` + data.NameUpper + ` +// @Description Create a new ` + data.Name + ` in BPJS system with enhanced validation and logging +// @Tags ` + tagName + ` +// @Accept json +// @Produce json +// @Param request body models.` + data.Name + `PostRequest true "` + data.Name + ` creation request" +// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` created successfully" +// @Failure 400 {object} models.` + data.Name + `Response "Bad request - validation error" +// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" +// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" +// @Router /api/v1/` + routePath + ` [post] +func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) { + requestID := uuid.New().String() + startTime := time.Now() + + h.logger.Info("Creating ` + data.Name + `", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + }) + + var req models.` + data.Name + `PostRequest + req.RequestID = requestID + req.Timestamp = startTime + + // Bind and validate JSON + if err := c.ShouldBindJSON(&req); err != nil { + h.logger.Error("Failed to bind JSON", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT", + "Format request tidak valid", err.Error(), requestID) + return + } + + // Custom validation + if err := req.Validate(); err != nil { + h.logger.Error("Custom validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi gagal", err.Error(), requestID) + return + } + + // Struct validation + if err := h.validator.Struct(&req); err != nil { + h.logger.Error("Struct validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi struktur gagal", h.formatValidationError(err), requestID) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + var rawResponse models.BpjsRawResponse + if err := h.service.Post(ctx, "` + data.PostEndpoint + `", req, &rawResponse); err != nil { + h.logger.Error("Failed to call BPJS service", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + "endpoint": "` + data.PostEndpoint + `", + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal membuat ` + data.Name + `", err.Error(), requestID) + return + } + + // Check BPJS response + if rawResponse.MetaData.Code != "200" { + h.logger.Warn("BPJS returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("` + data.Name + ` created successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + }) + + h.sendSuccessResponse(c, "` + data.Name + ` berhasil dibuat", rawResponse.Response, requestID) +}` +} + +func generateOptimizedBpjsUpdateMethod(data BpjsHandlerData) string { + var routePath, tagName string + if data.Category != "" { + routePath = data.Category + "/" + data.NameLower + tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) + } else { + routePath = data.NameLower + tagName = strings.Title(data.NameLower) + } + + return ` + +// Update` + data.Name + ` updates an existing ` + data.Name + ` with comprehensive validation +// @Summary Update an existing ` + data.NameUpper + ` +// @Description Update an existing ` + data.Name + ` in BPJS system with enhanced validation and logging +// @Tags ` + tagName + ` +// @Accept json +// @Produce json +// @Param request body models.` + data.Name + `PutRequest true "` + data.Name + ` update request" +// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` updated successfully" +// @Failure 400 {object} models.` + data.Name + `Response "Bad request - validation error" +// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" +// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" +// @Router /api/v1/` + routePath + ` [put] +func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) { + requestID := uuid.New().String() + startTime := time.Now() + + h.logger.Info("Updating ` + data.Name + `", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + }) + + var req models.` + data.Name + `PutRequest + req.RequestID = requestID + req.Timestamp = startTime + + if err := c.ShouldBindJSON(&req); err != nil { + h.logger.Error("Failed to bind JSON for update", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT", + "Format request tidak valid", err.Error(), requestID) + return + } + + if err := req.Validate(); err != nil { + h.logger.Error("Update validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi gagal", err.Error(), requestID) + return + } + + if err := h.validator.Struct(&req); err != nil { + h.logger.Error("Struct validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi struktur gagal", h.formatValidationError(err), requestID) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + var rawResponse models.BpjsRawResponse + if err := h.service.Put(ctx, "` + data.PutEndpoint + `", req, &rawResponse); err != nil { + h.logger.Error("Failed to update ` + data.Name + `", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal memperbarui ` + data.Name + `", err.Error(), requestID) + return + } + + if rawResponse.MetaData.Code != "200" { + h.logger.Warn("BPJS update returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("` + data.Name + ` updated successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + }) + + h.sendSuccessResponse(c, "` + data.Name + ` berhasil diperbarui", rawResponse.Response, requestID) +}` +} + +func generateOptimizedBpjsDeleteMethod(data BpjsHandlerData) string { + var routePath, tagName string + if data.Category != "" { + routePath = data.Category + "/" + data.NameLower + tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) + } else { + routePath = data.NameLower + tagName = strings.Title(data.NameLower) + } + + return ` + +// Delete` + data.Name + ` deletes an existing ` + data.Name + ` with comprehensive validation +// @Summary Delete an existing ` + data.NameUpper + ` +// @Description Delete a ` + data.Name + ` by ID with enhanced validation and logging +// @Tags ` + tagName + ` +// @Accept json +// @Produce json +// @Param id path string true "` + data.Name + ` ID" +// @Param user query string true "User identifier" +// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` deleted successfully" +// @Failure 400 {object} models.` + data.Name + `Response "Bad request - missing parameters" +// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" +// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" +// @Router /api/v1/` + routePath + `/{id} [delete] +func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) { + requestID := uuid.New().String() + startTime := time.Now() + id := c.Param("id") + user := c.Query("user") + + h.logger.Info("Deleting ` + data.Name + `", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + "id": id, + "user": user, + }) + + // Validate parameters + if id == "" { + h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", + "Parameter ID wajib diisi", "", requestID) + return + } + + if user == "" { + h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", + "Parameter user wajib diisi", "", requestID) + return + } + + req := models.` + data.Name + `DeleteRequest{ + BaseRequest: models.BaseRequest{ + RequestID: requestID, + Timestamp: startTime, + }, + T` + data.Name + `: models.` + data.Name + `DeleteData{ + ID: id, + User: user, + }, + } + + if err := req.Validate(); err != nil { + h.logger.Error("Delete validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi gagal", err.Error(), requestID) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + var rawResponse models.BpjsRawResponse + if err := h.service.Delete(ctx, "` + data.DeleteEndpoint + `", req); err != nil { + h.logger.Error("Failed to delete ` + data.Name + `", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + "id": id, + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal menghapus ` + data.Name + `", err.Error(), requestID) + return + } + + if rawResponse.MetaData.Code != "200" { + h.logger.Warn("BPJS delete returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + "id": id, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("` + data.Name + ` deleted successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + "id": id, + }) + + h.sendSuccessResponse(c, "` + data.Name + ` berhasil dihapus", rawResponse.Response, requestID) +}` +} + +func generateOptimizedBpjsGetMethod(data BpjsHandlerData) string { + var routePath, tagName string + if data.Category != "" { + routePath = data.Category + "/" + data.NameLower + tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) + } else { + routePath = data.NameLower + tagName = strings.Title(data.NameLower) + } + + return ` + +// Get` + data.Name + ` retrieves ` + data.Name + ` details with comprehensive error handling +// @Summary Get an existing ` + data.NameUpper + ` +// @Description Retrieve a ` + data.Name + ` by ID with enhanced validation and logging +// @Tags ` + tagName + ` +// @Accept json +// @Produce json +// @Param id path string true "` + data.Name + ` ID" +// @Success 200 {object} models.` + data.Name + `Response "Data ` + data.Name + ` retrieved successfully" +// @Failure 400 {object} models.` + data.Name + `Response "Bad request - invalid ID" +// @Failure 404 {object} models.` + data.Name + `Response "` + data.Name + ` not found" +// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" +// @Router /api/v1/` + routePath + `/{id} [get] +func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) { + requestID := uuid.New().String() + startTime := time.Now() + id := c.Param("id") + + h.logger.Info("Getting ` + data.Name + `", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + "id": id, + }) + + if id == "" { + h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", + "Parameter ID wajib diisi", "", requestID) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + endpoint := fmt.Sprintf("` + strings.Replace(data.GetEndpoint, "{id}", "%s", 1) + `", id) + var rawResponse models.BpjsRawResponse + + if err := h.service.Get(ctx, endpoint, &rawResponse); err != nil { + h.logger.Error("Failed to get ` + data.Name + `", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + "id": id, + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal mengambil data ` + data.Name + `", err.Error(), requestID) + return + } + + if rawResponse.MetaData.Code != "200" { + // Handle specific BPJS error codes + if rawResponse.MetaData.Code == "201" { + h.logger.Info("` + data.Name + ` not found", map[string]interface{}{ + "request_id": requestID, + "id": id, + }) + h.sendErrorResponse(c, http.StatusNotFound, "DATA_NOT_FOUND", + "Data ` + data.Name + ` tidak ditemukan", rawResponse.MetaData.Message, requestID) + return + } + + h.logger.Warn("BPJS get returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + "id": id, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("` + data.Name + ` retrieved successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + "id": id, + }) + + h.sendSuccessResponse(c, "Data ` + data.Name + ` berhasil diambil", rawResponse.Response, requestID) +}` +} + +func generateHelperMethods(data BpjsHandlerData) string { + return ` + +// Helper methods for ` + data.Name + `Handler +func (h *` + data.Name + `Handler) sendSuccessResponse(c *gin.Context, message string, data interface{}, requestID string) { + response := models.` + data.Name + `Response{ + BaseResponse: models.BaseResponse{ + Status: "success", + Message: message, + Data: data, + Metadata: &models.ResponseMetadata{ + Timestamp: time.Now(), + Version: "2.0", + RequestID: requestID, + }, + }, + } + c.JSON(http.StatusOK, response) +} + +func (h *` + data.Name + `Handler) sendErrorResponse(c *gin.Context, statusCode int, errorCode, message, details, requestID string) { + response := models.` + data.Name + `Response{ + BaseResponse: models.BaseResponse{ + Status: "error", + Message: message, + Error: &models.ErrorResponse{ + Code: errorCode, + Message: message, + Details: details, + }, + Metadata: &models.ResponseMetadata{ + Timestamp: time.Now(), + Version: "2.0", + RequestID: requestID, + }, + }, + } + c.JSON(statusCode, response) +} + +func (h *` + data.Name + `Handler) formatValidationError(err error) string { + if validationErrors, ok := err.(validator.ValidationErrors); ok { + var messages []string + for _, e := range validationErrors { + switch e.Tag() { + case "required": + messages = append(messages, fmt.Sprintf("%s wajib diisi", e.Field())) + case "min": + messages = append(messages, fmt.Sprintf("%s minimal %s karakter", e.Field(), e.Param())) + case "max": + messages = append(messages, fmt.Sprintf("%s maksimal %s karakter", e.Field(), e.Param())) + case "oneof": + messages = append(messages, fmt.Sprintf("%s harus salah satu dari: %s", e.Field(), e.Param())) + default: + messages = append(messages, fmt.Sprintf("%s tidak valid", e.Field())) + } + } + return fmt.Sprintf("Validasi gagal: %v", messages) + } + return err.Error() +} + +func (h *` + data.Name + `Handler) categorizeError(err error) (int, string) { + if err == nil { + return http.StatusOK, "SUCCESS" + } + + errStr := err.Error() + + if h.isTimeoutError(err) { + return http.StatusRequestTimeout, "REQUEST_TIMEOUT" + } + + if h.isNetworkError(err) { + return http.StatusBadGateway, "NETWORK_ERROR" + } + + if h.isAuthError(errStr) { + return http.StatusUnauthorized, "AUTH_ERROR" + } + + return http.StatusInternalServerError, "INTERNAL_ERROR" +} + +func (h *` + data.Name + `Handler) mapBpjsCodeToHttpStatus(bpjsCode string) int { + switch bpjsCode { + case "200": + return http.StatusOK + case "201": + return http.StatusNotFound + case "202": + return http.StatusBadRequest + case "400": + return http.StatusBadRequest + case "401": + return http.StatusUnauthorized + case "403": + return http.StatusForbidden + case "404": + return http.StatusNotFound + case "500": + return http.StatusInternalServerError + default: + return http.StatusUnprocessableEntity + } +} + +func (h *` + data.Name + `Handler) isTimeoutError(err error) bool { + return err != nil && (err.Error() == "context deadline exceeded" || + err.Error() == "timeout") +} + +func (h *` + data.Name + `Handler) isNetworkError(err error) bool { + return err != nil && (err.Error() == "connection refused" || + err.Error() == "no such host") +} + +func (h *` + data.Name + `Handler) isAuthError(errStr string) bool { + return errStr == "unauthorized" || errStr == "invalid credentials" +}` +} + +// ================= OPTIMIZED MODEL GENERATION ===================== + +func generateOptimizedBpjsModelFile(data BpjsHandlerData, modelDir string) { + modelContent := `package models + +import ( + "encoding/json" + "fmt" + "time" +) + +// ` + data.Name + ` BPJS Models with Enhanced Validation +// Generated at: ` + data.Timestamp + ` +// Category: ` + data.Category + ` + +// Base request/response structures +type BaseRequest struct { + RequestID string ` + "`json:\"request_id,omitempty\"`" + ` + Timestamp time.Time ` + "`json:\"timestamp,omitempty\"`" + ` +} + +type BaseResponse struct { + Status string ` + "`json:\"status\"`" + ` + Message string ` + "`json:\"message\"`" + ` + Data interface{} ` + "`json:\"data,omitempty\"`" + ` + Error *ErrorResponse ` + "`json:\"error,omitempty\"`" + ` + Metadata *ResponseMetadata ` + "`json:\"metadata,omitempty\"`" + ` +} + +type ErrorResponse struct { + Code string ` + "`json:\"code\"`" + ` + Message string ` + "`json:\"message\"`" + ` + Details string ` + "`json:\"details,omitempty\"`" + ` +} + +type ResponseMetadata struct { + Timestamp time.Time ` + "`json:\"timestamp\"`" + ` + Version string ` + "`json:\"version\"`" + ` + RequestID string ` + "`json:\"request_id,omitempty\"`" + ` +} + +// ` + data.Name + ` Response Structure +type ` + data.Name + `Response struct { + BaseResponse +} + +// BPJS Raw Response Structure +type BpjsRawResponse struct { + MetaData struct { + Code string ` + "`json:\"code\"`" + ` + Message string ` + "`json:\"message\"`" + ` + } ` + "`json:\"metaData\"`" + ` + Response interface{} ` + "`json:\"response\"`" + ` +}` + + if data.HasPost { + modelContent += ` + +// ` + data.Name + ` POST Request Structure with Enhanced Validation +type ` + data.Name + `PostRequest struct { + BaseRequest + T` + data.Name + ` ` + data.Name + `Post ` + "`json:\"tsep\" binding:\"required\" validate:\"required\"`" + ` +} + +type ` + data.Name + `Post struct { + // Core BPJS fields - customize based on your specific requirements + NoKartu string ` + "`json:\"noKartu\" binding:\"required\" validate:\"required,min=13,max=13\"`" + ` + TglLayanan string ` + "`json:\"tglLayanan\" binding:\"required\" validate:\"required\"`" + ` + JnsPelayanan string ` + "`json:\"jnsPelayanan\" binding:\"required\" validate:\"required,oneof=1 2\"`" + ` + PpkPelayanan string ` + "`json:\"ppkPelayanan\" binding:\"required\" validate:\"required\"`" + ` + Catatan string ` + "`json:\"catatan\" validate:\"omitempty,max=200\"`" + ` + User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` +} + +// Validate validates the ` + data.Name + `PostRequest +func (r *` + data.Name + `PostRequest) Validate() error { + if r.T` + data.Name + `.NoKartu == "" { + return fmt.Errorf("nomor kartu tidak boleh kosong") + } + + if len(r.T` + data.Name + `.NoKartu) != 13 { + return fmt.Errorf("nomor kartu harus 13 digit") + } + + if _, err := time.Parse("2006-01-02", r.T` + data.Name + `.TglLayanan); err != nil { + return fmt.Errorf("format tanggal layanan tidak valid, gunakan yyyy-MM-dd") + } + + return nil +} + +// ToJSON converts struct to JSON string +func (r *` + data.Name + `PostRequest) ToJSON() (string, error) { + data, err := json.Marshal(r) + return string(data), err +}` + } + + if data.HasPut { + modelContent += ` + +// ` + data.Name + ` PUT Request Structure with Enhanced Validation +type ` + data.Name + `PutRequest struct { + BaseRequest + T` + data.Name + ` ` + data.Name + `Put ` + "`json:\"tsep\" binding:\"required\" validate:\"required\"`" + ` +} + +type ` + data.Name + `Put struct { + ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + ` + NoKartu string ` + "`json:\"noKartu\" validate:\"omitempty,min=13,max=13\"`" + ` + TglLayanan string ` + "`json:\"tglLayanan\" validate:\"omitempty\"`" + ` + JnsPelayanan string ` + "`json:\"jnsPelayanan\" validate:\"omitempty,oneof=1 2\"`" + ` + PpkPelayanan string ` + "`json:\"ppkPelayanan\" validate:\"omitempty\"`" + ` + Catatan string ` + "`json:\"catatan\" validate:\"omitempty,max=200\"`" + ` + User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` +} + +// Validate validates the ` + data.Name + `PutRequest +func (r *` + data.Name + `PutRequest) Validate() error { + if r.T` + data.Name + `.ID == "" { + return fmt.Errorf("ID tidak boleh kosong") + } + + if r.T` + data.Name + `.NoKartu != "" && len(r.T` + data.Name + `.NoKartu) != 13 { + return fmt.Errorf("nomor kartu harus 13 digit") + } + + return nil +} + +// ToJSON converts struct to JSON string +func (r *` + data.Name + `PutRequest) ToJSON() (string, error) { + data, err := json.Marshal(r) + return string(data), err +}` + } + + if data.HasDelete { + modelContent += ` + +// ` + data.Name + ` DELETE Request Structure with Enhanced Validation +type ` + data.Name + `DeleteRequest struct { + BaseRequest + T` + data.Name + ` ` + data.Name + `DeleteData ` + "`json:\"tsep\" binding:\"required\" validate:\"required\"`" + ` +} + +type ` + data.Name + `DeleteData struct { + ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + ` + User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` +} + +// Validate validates the ` + data.Name + `DeleteRequest +func (r *` + data.Name + `DeleteRequest) Validate() error { + if r.T` + data.Name + `.ID == "" { + return fmt.Errorf("ID tidak boleh kosong") + } + + if r.T` + data.Name + `.User == "" { + return fmt.Errorf("User tidak boleh kosong") + } + + return nil +} + +// ToJSON converts struct to JSON string +func (r *` + data.Name + `DeleteRequest) ToJSON() (string, error) { + data, err := json.Marshal(r) + return string(data), err +}` + } + + // Add common helper structures + modelContent += ` + +// Common Helper Structures for BPJS +type Flag struct { + Flag string ` + "`json:\"flag\" binding:\"required\" validate:\"required,oneof=0 1\"`" + ` +} + +type Poli struct { + Tujuan string ` + "`json:\"tujuan\" binding:\"required\" validate:\"required\"`" + ` + Eksekutif string ` + "`json:\"eksekutif\" binding:\"required\" validate:\"required,oneof=0 1\"`" + ` +} + +type KlsRawat struct { + KlsRawatHak string ` + "`json:\"klsRawatHak\" binding:\"required\" validate:\"required,oneof=1 2 3\"`" + ` + KlsRawatNaik string ` + "`json:\"klsRawatNaik\" validate:\"omitempty,oneof=1 2 3 4 5 6 7\"`" + ` + Pembiayaan string ` + "`json:\"pembiayaan\" validate:\"omitempty,oneof=1 2 3\"`" + ` + PenanggungJawab string ` + "`json:\"penanggungJawab\" validate:\"omitempty,max=100\"`" + ` +} + +// Validation helper functions +func IsValidStatus(status string) bool { + validStatuses := []string{"active", "inactive", "pending", "processed"} + for _, v := range validStatuses { + if v == status { + return true + } + } + return false +} + +func IsValidJnsPelayanan(jns string) bool { + return jns == "1" || jns == "2" // 1: rawat jalan, 2: rawat inap +} + +func IsValidKlsRawat(kls string) bool { + validKelas := []string{"1", "2", "3"} + for _, v := range validKelas { + if v == kls { + return true + } + } + return false +}` + + writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent) +} + +// ================= OPTIMIZED ROUTES GENERATION ===================== + +func updateOptimizedBpjsRoutesFile(data BpjsHandlerData) { + routesFile := "internal/routes/v1/routes.go" + content, err := os.ReadFile(routesFile) + if err != nil { + fmt.Printf("⚠️ Could not read routes.go: %v\n", err) + fmt.Printf("📝 Please manually add these optimized routes to your routes.go file:\n") + printOptimizedBpjsRoutesSample(data) + return + } + + routesContent := string(content) + + var importPath, importAlias string + if data.Category != "" { + importPath = fmt.Sprintf("%s/internal/handlers/%s", data.ModuleName, data.Category) + importAlias = data.NameLower + "Handlers" + } else { + importPath = fmt.Sprintf("%s/internal/handlers", data.ModuleName) + importAlias = data.NameLower + "Handlers" + } + + // Check and add import + importPattern := fmt.Sprintf("%s \"%s\"", importAlias, importPath) + if !strings.Contains(routesContent, importPattern) { + importToAdd := fmt.Sprintf("\t%s \"%s\"", importAlias, importPath) + if strings.Contains(routesContent, "import (") { + routesContent = strings.Replace(routesContent, "import (", + "import (\n"+importToAdd, 1) + } + } + + // Build optimized routes + newRoutes := fmt.Sprintf("\t\t// Optimized %s endpoints with enhanced error handling\n", data.Name) + if data.Category != "" { + newRoutes = fmt.Sprintf("\t\t// Optimized %s %s endpoints with enhanced error handling\n", data.Category, data.Name) + } + + newRoutes += fmt.Sprintf("\t\t%sHandlerConfig := &%s.%sHandlerConfig{\n", data.NameLower, importAlias, data.Name) + newRoutes += fmt.Sprintf("\t\t\tBpjsConfig: &config.LoadConfig().Bpjs,\n") + newRoutes += fmt.Sprintf("\t\t\tLogger: logger.GetLogger(),\n") + newRoutes += fmt.Sprintf("\t\t\tValidator: validator.New(),\n") + newRoutes += fmt.Sprintf("\t\t}\n") + newRoutes += fmt.Sprintf("\t\t%sHandler := %s.New%sHandler(%sHandlerConfig)\n", + data.NameLower, importAlias, data.Name, data.NameLower) + + var routePath string + if data.Category != "" { + routePath = data.Category + "/" + data.NameLower + } else { + routePath = data.NameLower + } + + if data.HasGet { + newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s/:id\", %sHandler.Get%s)\n", + routePath, data.NameLower, data.Name) + } + + if data.HasPost { + newRoutes += fmt.Sprintf("\t\tv1.POST(\"/%s\", %sHandler.Create%s)\n", + routePath, data.NameLower, data.Name) + } + + if data.HasPut { + newRoutes += fmt.Sprintf("\t\tv1.PUT(\"/%s\", %sHandler.Update%s)\n", + routePath, data.NameLower, data.Name) + } + + if data.HasDelete { + newRoutes += fmt.Sprintf("\t\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", + routePath, data.NameLower, data.Name) + } + + newRoutes += "\n" + + // Insert routes + insertMarker := "\t\tprotected := v1.Group(\"/\")" + if strings.Contains(routesContent, insertMarker) { + if !strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) { + routesContent = strings.Replace(routesContent, insertMarker, + newRoutes+insertMarker, 1) + } else { + fmt.Printf("✅ Optimized routes for %s already exist, skipping...\n", data.Name) + return + } + } + + if err := os.WriteFile(routesFile, []byte(routesContent), 0644); err != nil { + fmt.Printf("Error writing routes.go: %v\n", err) + return + } + + if data.Category != "" { + fmt.Printf("✅ Updated routes.go with optimized %s %s endpoints\n", data.Category, data.Name) + } else { + fmt.Printf("✅ Updated routes.go with optimized %s endpoints\n", data.Name) + } +} + +func printOptimizedBpjsRoutesSample(data BpjsHandlerData) { + var importAlias string + if data.Category != "" { + importAlias = data.NameLower + "Handlers" + } else { + importAlias = data.NameLower + "Handlers" + } + + var routePath string + if data.Category != "" { + routePath = data.Category + "/" + data.NameLower + } else { + routePath = data.NameLower + } + + if data.Category != "" { + fmt.Printf(` +// Optimized %s %s endpoints with enhanced error handling +%sHandlerConfig := &%s.%sHandlerConfig{ + BpjsConfig: &config.LoadConfig().Bpjs, + Logger: logger.GetLogger(), + Validator: validator.New(), +} +%sHandler := %s.New%sHandler(%sHandlerConfig) +`, data.Category, data.Name, data.NameLower, importAlias, data.Name, data.NameLower, importAlias, data.Name, data.NameLower) + } else { + fmt.Printf(` +// Optimized %s endpoints with enhanced error handling +%sHandlerConfig := &%s.%sHandlerConfig{ + BpjsConfig: &config.LoadConfig().Bpjs, + Logger: logger.GetLogger(), + Validator: validator.New(), +} +%sHandler := %s.New%sHandler(%sHandlerConfig) +`, data.Name, data.NameLower, importAlias, data.Name, data.NameLower, importAlias, data.Name, data.NameLower) + } + + if data.HasGet { + fmt.Printf("\tv1.GET(\"/%s/:id\", %sHandler.Get%s)\n", routePath, data.NameLower, data.Name) + } + if data.HasPost { + fmt.Printf("\tv1.POST(\"/%s\", %sHandler.Create%s)\n", routePath, data.NameLower, data.Name) + } + if data.HasPut { + fmt.Printf("\tv1.PUT(\"/%s\", %sHandler.Update%s)\n", routePath, data.NameLower, data.Name) + } + if data.HasDelete { + fmt.Printf("\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", routePath, data.NameLower, data.Name) + } + + fmt.Println() +} + +// ================= UTILITY FUNCTIONS ===================== + +func writeFile(filename, content string) { + if err := os.WriteFile(filename, []byte(content), 0644); err != nil { + fmt.Printf("❌ Error creating file %s: %v\n", filename, err) + return + } + + fmt.Printf("✅ Generated optimized file: %s\n", filename) +} diff --git a/vclaim_handler.go b/vclaim_handler.go new file mode 100644 index 00000000..42c7b962 --- /dev/null +++ b/vclaim_handler.go @@ -0,0 +1,573 @@ +// Code generated by generate-dynamic-handler.go; DO NOT EDIT. +// Generated at: 2025-08-28 14:59:09 +// Service: VClaim (vclaim) +// Description: BPJS VClaim service for eligibility and SEP management + +package handlers + +import ( + "context" + "fmt" + "net/http" + "strings" + "time" + + "api-service/internal/config" + "api-service/internal/models/reference" + services "api-service/internal/services/bpjs" + "api-service/pkg/logger" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "github.com/google/uuid" +) + +// VClaimHandler handles VClaim BPJS services +type VClaimHandler struct { + service services.VClaimService + validator *validator.Validate + logger logger.Logger + config *config.BpjsConfig +} + +// VClaimHandlerConfig contains configuration for VClaimHandler +type VClaimHandlerConfig struct { + BpjsConfig *config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// NewVClaimHandler creates a new VClaimHandler +func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler { + return &VClaimHandler{ + service: services.NewService(*cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +} + +// GetPESERTA retrieves Peserta data +// @Summary Get Peserta data +// @Description Get participant eligibility information +// @Tags vclaim,peserta +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param nokartu path string true "Nokartu" +// @Success 200 {object} reference.PesertaResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /Peserta/:nokartu [get] +func (h *VClaimHandler) GetPESERTA(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + // Authentication check + if err := h.authenticateRequest(c); err != nil { + h.logger.Error("Authentication failed", "error", err.Error(), "request_id", requestID) + c.JSON(http.StatusUnauthorized, reference.ErrorResponse{ + Status: "error", + Message: "Authentication failed", + RequestID: requestID, + }) + return + } + + // Extract path parameters + nokartu := c.Param("nokartu") + if nokartu == "" { + h.logger.Error("Missing required parameter: nokartu", "request_id", requestID) + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter: nokartu", + RequestID: requestID, + }) + return + } + // Check cache first + cacheKey := fmt.Sprintf("vclaim:peserta:%s", nokartu) + if cached, found := h.getCachedResponse(cacheKey); found { + h.logger.Info("Cache hit for GetPESERTA", "request_id", requestID, "cache_key", cacheKey) + c.Header("X-Cache", "HIT") + c.JSON(http.StatusOK, cached) + return + } + + h.logger.Info("Processing GetPESERTA request", + "request_id", requestID, + "endpoint", "/Peserta/:nokartu", + "nokartu", nokartu) + + // Call service method + result, err := h.service.GetPESERTA(ctx, nokartu) + + if err != nil { + h.logger.Error("Failed to get Peserta", + "error", err.Error(), + "request_id", requestID) + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Prepare response + response := reference.PesertaResponse{ + Status: "success", + Data: result, + RequestID: requestID, + } + // Cache successful response + h.setCachedResponse(cacheKey, response, 300) + + c.Header("X-Cache", "MISS") + c.JSON(http.StatusOK, response) +} + +// GetSEP retrieves Sep data +// @Summary Get Sep data +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param nosep path string true "Nosep" +// @Success 200 {object} reference.SEPResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /SEP/:nosep [get] +func (h *VClaimHandler) GetSEP(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + // Authentication check + if err := h.authenticateRequest(c); err != nil { + h.logger.Error("Authentication failed", "error", err.Error(), "request_id", requestID) + c.JSON(http.StatusUnauthorized, reference.ErrorResponse{ + Status: "error", + Message: "Authentication failed", + RequestID: requestID, + }) + return + } + + // Extract path parameters + nosep := c.Param("nosep") + if nosep == "" { + h.logger.Error("Missing required parameter: nosep", "request_id", requestID) + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter: nosep", + RequestID: requestID, + }) + return + } + // Check cache first + cacheKey := fmt.Sprintf("vclaim:sep:%s", nosep) + if cached, found := h.getCachedResponse(cacheKey); found { + h.logger.Info("Cache hit for GetSEP", "request_id", requestID, "cache_key", cacheKey) + c.Header("X-Cache", "HIT") + c.JSON(http.StatusOK, cached) + return + } + + h.logger.Info("Processing GetSEP request", + "request_id", requestID, + "endpoint", "/SEP/:nosep", + "nosep", nosep) + + // Call service method + result, err := h.service.GetSEP(ctx, nosep) + + if err != nil { + h.logger.Error("Failed to get Sep", + "error", err.Error(), + "request_id", requestID) + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Prepare response + response := reference.SEPResponse{ + Status: "success", + Data: result, + RequestID: requestID, + } + // Cache successful response + h.setCachedResponse(cacheKey, response, 180) + + c.Header("X-Cache", "MISS") + c.JSON(http.StatusOK, response) +} + +// CreateSEP creates new Sep +// @Summary Create Sep +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param request body reference.SEPRequest true "Sep data" +// @Success 201 {object} reference.SEPResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /sep [post] +func (h *VClaimHandler) CreateSEP(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + // Authentication check + if err := h.authenticateRequest(c); err != nil { + h.logger.Error("Authentication failed", "error", err.Error(), "request_id", requestID) + c.JSON(http.StatusUnauthorized, reference.ErrorResponse{ + Status: "error", + Message: "Authentication failed", + RequestID: requestID, + }) + return + } + + var req reference.SEPRequest + if err := c.ShouldBindJSON(&req); err != nil { + h.logger.Error("Invalid request body", + "error", err.Error(), + "request_id", requestID) + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Invalid request body: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Validate request + if err := h.validator.Struct(&req); err != nil { + h.logger.Error("Validation failed", + "error", err.Error(), + "request_id", requestID) + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Validation failed: " + err.Error(), + RequestID: requestID, + }) + return + } + + h.logger.Info("Processing CreateSEP request", "request_id", requestID) + + // Call service method + result, err := h.service.CreateSEP(ctx, &req) + if err != nil { + h.logger.Error("Failed to create Sep", + "error", err.Error(), + "request_id", requestID) + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + response := reference.SEPResponse{ + Status: "success", + Data: result, + RequestID: requestID, + } + + c.JSON(http.StatusCreated, response) +} + +// UpdateSEP updates existing Sep +// @Summary Update Sep +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param nosep path string true "Nosep" +// @Param request body reference.SEPRequest true "Sep data" +// @Success 200 {object} reference.SEPResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /sep/:nosep [put] +func (h *VClaimHandler) UpdateSEP(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + // Extract path parameters + nosep := c.Param("nosep") + if nosep == "" { + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter: nosep", + RequestID: requestID, + }) + return + } + + var req reference.SEPRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Invalid request body: " + err.Error(), + RequestID: requestID, + }) + return + } + + if err := h.validator.Struct(&req); err != nil { + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Validation failed: " + err.Error(), + RequestID: requestID, + }) + return + } + // Invalidate cache + cacheKey := fmt.Sprintf("vclaim:sep:%s", nosep) + h.invalidateCache(cacheKey) + + // Call service method + result, err := h.service.UpdateSEP(ctx, nosep, &req) + + if err != nil { + h.logger.Error("Failed to update Sep", "error", err.Error(), "request_id", requestID) + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + response := reference.SEPResponse{ + Status: "success", + Data: result, + RequestID: requestID, + } + + c.JSON(http.StatusOK, response) +} + +// DeleteSEP deletes existing Sep +// @Summary Delete Sep +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param nosep path string true "Nosep" +// @Success 200 {object} reference.BaseResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /sep/:nosep [delete] +func (h *VClaimHandler) DeleteSEP(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + // Extract path parameters + nosep := c.Param("nosep") + if nosep == "" { + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter: nosep", + RequestID: requestID, + }) + return + } + // Invalidate cache + cacheKey := fmt.Sprintf("vclaim:sep:%s", nosep) + h.invalidateCache(cacheKey) + + // Call service method + err := h.service.DeleteSEP(ctx, nosep) + + if err != nil { + h.logger.Error("Failed to delete Sep", "error", err.Error(), "request_id", requestID) + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + response := reference.BaseResponse{ + Status: "success", + Message: "Sep deleted successfully", + RequestID: requestID, + } + + c.JSON(http.StatusOK, response) +} + +// GetRUJUKAN retrieves Rujukan data +// @Summary Get Rujukan data +// @Description Get referral information +// @Tags vclaim,rujukan +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param norujukan path string true "Norujukan" +// @Success 200 {object} reference.RujukanResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /rujukan/:norujukan [get] +func (h *VClaimHandler) GetRUJUKAN(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + // Authentication check + if err := h.authenticateRequest(c); err != nil { + h.logger.Error("Authentication failed", "error", err.Error(), "request_id", requestID) + c.JSON(http.StatusUnauthorized, reference.ErrorResponse{ + Status: "error", + Message: "Authentication failed", + RequestID: requestID, + }) + return + } + + // Extract path parameters + norujukan := c.Param("norujukan") + if norujukan == "" { + h.logger.Error("Missing required parameter: norujukan", "request_id", requestID) + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter: norujukan", + RequestID: requestID, + }) + return + } + // Check cache first + cacheKey := fmt.Sprintf("vclaim:rujukan:%s", norujukan) + if cached, found := h.getCachedResponse(cacheKey); found { + h.logger.Info("Cache hit for GetRUJUKAN", "request_id", requestID, "cache_key", cacheKey) + c.Header("X-Cache", "HIT") + c.JSON(http.StatusOK, cached) + return + } + + h.logger.Info("Processing GetRUJUKAN request", + "request_id", requestID, + "endpoint", "/rujukan/:norujukan", + "norujukan", norujukan) + + // Call service method + result, err := h.service.GetRUJUKAN(ctx, norujukan) + + if err != nil { + h.logger.Error("Failed to get Rujukan", + "error", err.Error(), + "request_id", requestID) + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Prepare response + response := reference.RujukanResponse{ + Status: "success", + Data: result, + RequestID: requestID, + } + // Cache successful response + h.setCachedResponse(cacheKey, response, 600) + + c.Header("X-Cache", "MISS") + c.JSON(http.StatusOK, response) +} + +// RegisterRoutes registers all VClaim routes +func (h *VClaimHandler) RegisterRoutes(router *gin.RouterGroup) { + + router.GET("/Peserta/:nokartu", h.GetPESERTA) + + router.GET("/SEP/:nosep", h.GetSEP) + router.POST("/sep", h.CreateSEP) + router.PUT("/sep/:nosep", h.UpdateSEP) + router.DELETE("/sep/:nosep", h.DeleteSEP) + + router.GET("/rujukan/:norujukan", h.GetRUJUKAN) + +} + +// Helper methods +func (h *VClaimHandler) authenticateRequest(c *gin.Context) error { + token := c.GetHeader("Authorization") + if token == "" { + return fmt.Errorf("missing authorization header") + } + + if strings.HasPrefix(token, "Bearer ") { + token = strings.TrimPrefix(token, "Bearer ") + } + + return h.auth.ValidateToken(token) +} +func (h *VClaimHandler) getCachedResponse(key string) (interface{}, bool) { + if h.cache == nil { + return nil, false + } + return h.cache.Get(key) +} + +func (h *VClaimHandler) setCachedResponse(key string, data interface{}, ttl int) { + if h.cache == nil { + return + } + h.cache.Set(key, data, time.Duration(ttl)*time.Second) +} + +func (h *VClaimHandler) invalidateCache(key string) { + if h.cache == nil { + return + } + h.cache.Delete(key) +} diff --git a/vclaim_handler2.go b/vclaim_handler2.go new file mode 100644 index 00000000..78079845 --- /dev/null +++ b/vclaim_handler2.go @@ -0,0 +1,365 @@ +// Code generated by generate-dynamic-handler.go; DO NOT EDIT. +// Generated at: 2025-08-28 14:47:36 +// Service: VClaim (vclaim) +// Description: BPJS VClaim service for eligibility and SEP management + +package handlers + +import ( + "api-service/internal/config" + "api-service/internal/models/reference" + services "api-service/internal/services/bpjs" + "api-service/pkg/logger" + "context" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "github.com/google/uuid" +) + +// VClaimHandler handles VClaim BPJS services +type VClaimHandler struct { + service services.VClaimService + validator *validator.Validate + logger logger.Logger + config config.BpjsConfig +} + +// VClaimHandlerConfig contains configuration for VClaimHandler +type VClaimHandlerConfig struct { + BpjsConfig config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// NewVClaimHandler creates a new VClaimHandler +func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler { + return &VClaimHandler{ + service: services.NewService(cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +} + +// GetPESERTA retrieves Peserta data + +// @Summary Get Peserta data +// @Description Get participant eligibility information +// @Tags vclaim,peserta +// @Accept json +// @Produce json + +// @Security ApiKeyAuth + +// @Param nokartu path string true "nokartu" + +// @Success 200 {object} reference.PesertaResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /Peserta/:nokartu [get] + +func (h *VClaimHandler) GetPeserta(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + h.logger.Info("Processing GetPeserta request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "/Peserta/:nokartu", + + "nokartu": c.Param("nokartu"), + }) + + // Extract path parameters + + nokartu := c.Param("nokartu") + if nokartu == "" { + + h.logger.Error("Missing required parameter nokartu", map[string]interface{}{ + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter nokartu", + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.PesertaResponse + + response, err := h.service.GetPeserta(ctx, nokartu) + + if err != nil { + + h.logger.Error("Failed to get Peserta", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusOK, response) +} + +// GetSEP retrieves Sep data + +// @Summary Get Sep data +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json + +// @Security ApiKeyAuth + +// @Param nosep path string true "nosep" + +// @Success 200 {object} reference.SEPResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /SEP/:nosep [get] + +func (h *VClaimHandler) GetSEP(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + h.logger.Info("Processing GetSep request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "/SEP/:nosep", + + "nosep": c.Param("nosep"), + }) + + // Extract path parameters + + nosep := c.Param("nosep") + if nosep == "" { + + h.logger.Error("Missing required parameter nosep", map[string]interface{}{ + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter nosep", + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.SEPResponse + + result, err := h.service.GetSEP(ctx, nosep) + + if err != nil { + + h.logger.Error("Failed to get Sep", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusOK, response) +} + +// CreateSEP creates new Sep + +// @Summary Create Sep +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json + +// @Security ApiKeyAuth + +// @Param request body reference.SEPRequest true "Sep data" +// @Success 201 {object} reference.SEPResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /sep [post] + +func (h *VClaimHandler) CreateSEP(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + h.logger.Info("Processing CreateSep request", map[string]interface{}{ + "request_id": requestID, + }) + + var req reference.SEPRequest + if err := c.ShouldBindJSON(&req); err != nil { + + h.logger.Error("Invalid request body", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Invalid request body: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Validate request + if err := h.validator.Struct(&req); err != nil { + + h.logger.Error("Validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Validation failed: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.SEPResponse + response, err := h.service.CreateSEP(ctx, &req) + if err != nil { + + h.logger.Error("Failed to create Sep", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusCreated, response) +} + +// GetRUJUKAN retrieves Rujukan data + +// @Summary Get Rujukan data +// @Description Get referral information +// @Tags vclaim,rujukan +// @Accept json +// @Produce json + +// @Security ApiKeyAuth + +// @Param norujukan path string true "norujukan" + +// @Success 200 {object} reference.RujukanResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /rujukan/:norujukan [get] + +func (h *VClaimHandler) GetRUJUKAN(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + h.logger.Info("Processing GetRujukan request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "/rujukan/:norujukan", + + "norujukan": c.Param("norujukan"), + }) + + // Extract path parameters + + norujukan := c.Param("norujukan") + if norujukan == "" { + + h.logger.Error("Missing required parameter norujukan", map[string]interface{}{ + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter norujukan", + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.RujukanResponse + + response, err := h.service.GetRUJUKAN(ctx, norujukan) + + if err != nil { + + h.logger.Error("Failed to get Rujukan", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusOK, response) +}