oerubahan
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
teguh nugroho 2026-09-05 14:31:03 +07:00
parent 8354634117
commit 39265bfcb1
32 changed files with 1766 additions and 79 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

BIN
api Executable file

Binary file not shown.

BIN
docs/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -136,20 +136,37 @@ const E=[
{cat:"Master Data / Postal Code",m:"post",p:"/master-data/postal-codes/import",s:"Import postal codes from CSV",q:[],rb:"_MULTIPART_",rbe:"{\n \"success\": true,\n \"data\": {\n \"job_id\": \"job_abc123def456\",\n \"total_rows\": 100,\n \"imported\": 95,\n \"skipped\": 5,\n \"logs\": [\n {\n \"row\": 2,\n \"status\": \"skipped\",\n \"data\": \"Jawa Barat\",\n \"message\": \"duplicate\"\n }\n ],\n \"errors\": [],\n \"is_async\": false\n }\n}",rf:[]}, {cat:"Master Data / Postal Code",m:"post",p:"/master-data/postal-codes/import",s:"Import postal codes from CSV",q:[],rb:"_MULTIPART_",rbe:"{\n \"success\": true,\n \"data\": {\n \"job_id\": \"job_abc123def456\",\n \"total_rows\": 100,\n \"imported\": 95,\n \"skipped\": 5,\n \"logs\": [\n {\n \"row\": 2,\n \"status\": \"skipped\",\n \"data\": \"Jawa Barat\",\n \"message\": \"duplicate\"\n }\n ],\n \"errors\": [],\n \"is_async\": false\n }\n}",rf:[]},
{cat:"Master Data / Postal Code",m:"post",p:"/master-data/postal-codes/import/stop",s:"Stop postal code import job",q:[],rb:"{\n \"job_id\": \"job_abc123def456\"\n}",rbe:"{\n \"success\": true,\n \"message\": \"Job stopped\"\n}",rf:["job_id"]}, {cat:"Master Data / Postal Code",m:"post",p:"/master-data/postal-codes/import/stop",s:"Stop postal code import job",q:[],rb:"{\n \"job_id\": \"job_abc123def456\"\n}",rbe:"{\n \"success\": true,\n \"message\": \"Job stopped\"\n}",rf:["job_id"]},
{cat:"Master Data / Postal Code",m:"get",p:"/master-data/postal-codes/import/status/:jobId",s:"Check postal code import job status",q:[{n:"jobId",t:"string",req:true,d:"Async job ID"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": {\n \"job\": {\n \"id\": \"job_abc123def456\",\n \"status\": \"completed\",\n \"total\": 100,\n \"imported\": 95,\n \"skipped\": 5\n },\n \"logs\": []\n }\n}",rf:[]}, {cat:"Master Data / Postal Code",m:"get",p:"/master-data/postal-codes/import/status/:jobId",s:"Check postal code import job status",q:[{n:"jobId",t:"string",req:true,d:"Async job ID"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": {\n \"job\": {\n \"id\": \"job_abc123def456\",\n \"status\": \"completed\",\n \"total\": 100,\n \"imported\": 95,\n \"skipped\": 5\n },\n \"logs\": []\n }\n}",rf:[]},
{cat:"Master Data / Cost Component",m:"get",p:"/master-data/cost-components/",s:"Get all cost components",q:[{n:"name",t:"string",req:false,d:"Filter by name"},{n:"code",t:"string",req:false,d:"Filter by code"},{n:"component_type",t:"integer",req:false,d:"Filter by type (0=Input, 1=Formula)"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Biaya Pengiriman\",\n \"code\": \"SHIP\",\n \"inc\": 1,\n \"value\": \"15000\",\n \"component_type\": 0,\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n ]\n}",rf:[]}, {cat:"Master Data / Cost Component",m:"get",p:"/master-data/cost-components/",s:"Get all cost components",q:[{n:"name",t:"string",req:false,d:"Filter by name"},{n:"code",t:"string",req:false,d:"Filter by code"},{n:"component_type",t:"integer",req:false,d:"Filter by type (0=Input, 1=Formula)"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Biaya Pengiriman\",\n \"code\": \"SHIP\",\n \"inc\": 1,\n \"value\": \"15000\",\n \"component_type\": 0,\n \"unit_type\": 0,\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n ]\n}",rf:[]},
{cat:"Master Data / Cost Component",m:"post",p:"/master-data/cost-components/list",s:"Cost component list (DataTable)",q:[],rb:"_DT_",rbe:"{\n \"success\": true,\n \"data\": {\n \"draw\": 1,\n \"recordsTotal\": 34,\n \"recordsFiltered\": 34,\n \"data\": [\n { \"id\": \"01HXYZ123456789012345678\", \"name\": \"...\" }\n ]\n }\n}",rf:[]}, {cat:"Master Data / Cost Component",m:"post",p:"/master-data/cost-components/list",s:"Cost component list (DataTable)",q:[],rb:"_DT_",rbe:"{\n \"success\": true,\n \"data\": {\n \"draw\": 1,\n \"recordsTotal\": 34,\n \"recordsFiltered\": 34,\n \"data\": [\n { \"id\": \"01HXYZ123456789012345678\", \"name\": \"...\" }\n ]\n }\n}",rf:[]},
{cat:"Master Data / Cost Component",m:"get",p:"/master-data/cost-components/:id",s:"Get cost component by ID",q:[{n:"id",t:"string",req:true,d:"Cost component ID"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Biaya Pengiriman\",\n \"code\": \"SHIP\",\n \"inc\": 1,\n \"value\": \"15000\",\n \"component_type\": 0,\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:[]}, {cat:"Master Data / Cost Component",m:"get",p:"/master-data/cost-components/:id",s:"Get cost component by ID",q:[{n:"id",t:"string",req:true,d:"Cost component ID"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Biaya Pengiriman\",\n \"code\": \"SHIP\",\n \"inc\": 1,\n \"value\": \"15000\",\n \"component_type\": 0,\n \"unit_type\": 0,\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:[]},
{cat:"Master Data / Cost Component",m:"post",p:"/master-data/cost-components/",s:"Create cost component",q:[],rb:"{\n \"name\": \"Biaya Pengiriman\",\n \"code\": \"SHIP\",\n \"value\": \"15000\",\n \"component_type\": 0\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Biaya Pengiriman\",\n \"code\": \"SHIP\",\n \"inc\": 1,\n \"value\": \"15000\",\n \"component_type\": 0,\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:["name","code","value"]}, {cat:"Master Data / Cost Component",m:"post",p:"/master-data/cost-components/",s:"Create cost component",q:[],rb:"{\n \"name\": \"Biaya Pengiriman\",\n \"code\": \"SHIP\",\n \"value\": \"15000\",\n \"component_type\": 0,\n \"unit_type\": 0\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Biaya Pengiriman\",\n \"code\": \"SHIP\",\n \"inc\": 1,\n \"value\": \"15000\",\n \"component_type\": 0,\n \"unit_type\": 0,\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:["name","code","value"]},
{cat:"Master Data / Cost Component",m:"put",p:"/master-data/cost-components/:id",s:"Update cost component",q:[{n:"id",t:"string",req:true,d:"Cost component ID"}],rb:"{\n \"name\": \"Biaya Pengiriman Updated\",\n \"code\": \"SHIP\",\n \"value\": \"20000\",\n \"component_type\": 0\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Biaya Pengiriman Updated\",\n \"code\": \"SHIP\",\n \"inc\": 1,\n \"value\": \"20000\",\n \"component_type\": 0,\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:["name","code","value"]}, {cat:"Master Data / Cost Component",m:"put",p:"/master-data/cost-components/:id",s:"Update cost component",q:[{n:"id",t:"string",req:true,d:"Cost component ID"}],rb:"{\n \"name\": \"Biaya Pengiriman Updated\",\n \"code\": \"SHIP\",\n \"value\": \"20000\",\n \"component_type\": 0,\n \"unit_type\": 0\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Biaya Pengiriman Updated\",\n \"code\": \"SHIP\",\n \"inc\": 1,\n \"value\": \"20000\",\n \"component_type\": 0,\n \"unit_type\": 0,\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:["name","code","value"]},
{cat:"Master Data / Cost Component",m:"delete",p:"/master-data/cost-components/:id",s:"Delete cost component",q:[{n:"id",t:"string",req:true,d:"Cost component ID"}],rb:null,rbe:"{\n \"success\": true,\n \"message\": \"cost component deleted\"\n}",rf:[]}, {cat:"Master Data / Cost Component",m:"delete",p:"/master-data/cost-components/:id",s:"Delete cost component",q:[{n:"id",t:"string",req:true,d:"Cost component ID"}],rb:null,rbe:"{\n \"success\": true,\n \"message\": \"cost component deleted\"\n}",rf:[]},
{cat:"Master Data / Cost Component",m:"post",p:"/master-data/cost-components/sortup",s:"Move cost component up in sort order",q:[],rb:"{\n \"id\": \"01HXYZ123456789012345678\"\n}",rbe:"{\n \"success\": true,\n \"message\": \"Sorting Up Success\"\n}",rf:["id"]}, {cat:"Master Data / Cost Component",m:"post",p:"/master-data/cost-components/sortup",s:"Move cost component up in sort order",q:[],rb:"{\n \"id\": \"01HXYZ123456789012345678\"\n}",rbe:"{\n \"success\": true,\n \"message\": \"Sorting Up Success\"\n}",rf:["id"]},
{cat:"Master Data / Cost Component",m:"post",p:"/master-data/cost-components/sortdown",s:"Move cost component down in sort order",q:[],rb:"{\n \"id\": \"01HXYZ123456789012345678\"\n}",rbe:"{\n \"success\": true,\n \"message\": \"Sorting Down Success\"\n}",rf:["id"]}, {cat:"Master Data / Cost Component",m:"post",p:"/master-data/cost-components/sortdown",s:"Move cost component down in sort order",q:[],rb:"{\n \"id\": \"01HXYZ123456789012345678\"\n}",rbe:"{\n \"success\": true,\n \"message\": \"Sorting Down Success\"\n}",rf:["id"]},
{cat:"Basic Pricing",m:"get",p:"/basic-pricings/",s:"Get all basic pricings",q:[{n:"name",t:"string",req:false,d:"Filter by name"},{n:"province_source_id",t:"string",req:false,d:"Source province ID"},{n:"city_source_id",t:"string",req:false,d:"Source city ID"},{n:"package_type_id",t:"string",req:false,d:"Package type ID"},{n:"moda_id",t:"string",req:false,d:"Transport mode ID"}],rb:null,rbe:"{\\n \"success\": true,\\n \"data\": [\\n { ... }\\n ]\\n}",rf:[]}, {cat:"Basic Pricing",m:"get",p:"/basic-pricings/",s:"Get all basic pricings",q:[{n:"province_source_id",t:"string",req:false,d:"Source province ID"},{n:"city_source_id",t:"string",req:false,d:"Source city ID"},{n:"package_type_id",t:"string",req:false,d:"Package type ID"},{n:"moda_id",t:"string",req:false,d:"Transport mode ID"}],rb:null,rbe:"{\\n \"success\": true,\\n \"data\": [\\n { ... }\\n ]\\n}",rf:[]},
{cat:"Basic Pricing",m:"post",p:"/basic-pricings/list",s:"Basic pricing list (DataTable)",q:[],rb:"_DT_",rbe:"{\n \"success\": true,\n \"data\": {\n \"draw\": 1,\n \"recordsTotal\": 34,\n \"recordsFiltered\": 34,\n \"data\": [\n { \"id\": \"01HXYZ123456789012345678\", \"name\": \"...\" }\n ]\n }\n}",rf:[]}, {cat:"Basic Pricing",m:"post",p:"/basic-pricings/list",s:"Basic pricing list (DataTable)",q:[],rb:"_DT_",rbe:"{\n \"success\": true,\n \"data\": {\n \"draw\": 1,\n \"recordsTotal\": 34,\n \"recordsFiltered\": 34,\n \"data\": [\n { \"id\": \"01HXYZ123456789012345678\", \"name\": \"...\" }\n ]\n }\n}",rf:[]},
{cat:"Basic Pricing",m:"get",p:"/basic-pricings/:id",s:"Get basic pricing by ID",q:[{n:"id",t:"string",req:true,d:"Basic pricing ID"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Basic Price Bandung-Jakarta\",\n \"province_source_id\": \"01HXYZ123456789012345678\",\n \"city_source_id\": \"01HXYZ123456789012345678\",\n \"district_source_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_source_id\": \"01HXYZ123456789012345678\",\n \"postal_code_source_id\": \"01HXYZ123456789012345678\",\n \"province_destination_id\": \"01HXYZ123456789012345678\",\n \"city_destination_id\": \"01HXYZ123456789012345678\",\n \"district_destination_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_destination_id\": \"01HXYZ123456789012345678\",\n \"postal_code_destination_id\": \"01HXYZ123456789012345678\",\n \"package_type_id\": \"01HXYZ123456789012345678\",\n \"moda_id\": \"01HXYZ123456789012345678\",\n \"merchant_id\": \"01HXYZ123456789012345678\",\n \"components\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"cost_component_id\": \"01HXYZ123456789012345678\",\n \"value\": 15000\n }\n ]\n }\n}",rf:[]}, {cat:"Basic Pricing",m:"get",p:"/basic-pricings/:id",s:"Get basic pricing by ID",q:[{n:"id",t:"string",req:true,d:"Basic pricing ID"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"province_source_id\": \"01HXYZ123456789012345678\",\n \"city_source_id\": \"01HXYZ123456789012345678\",\n \"district_source_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_source_id\": \"01HXYZ123456789012345678\",\n \"postal_code_source_id\": \"01HXYZ123456789012345678\",\n \"province_destination_id\": \"01HXYZ123456789012345678\",\n \"city_destination_id\": \"01HXYZ123456789012345678\",\n \"district_destination_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_destination_id\": \"01HXYZ123456789012345678\",\n \"postal_code_destination_id\": \"01HXYZ123456789012345678\",\n \"package_type_id\": \"01HXYZ123456789012345678\",\n \"moda_id\": \"01HXYZ123456789012345678\",\n \"lead_time\": \"2-3 days\",\n \"minimum_weight\": 1.0,\n \"merchant_id\": \"01HXYZ123456789012345678\",\n \"components\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"cost_component_id\": \"01HXYZ123456789012345678\",\n \"value\": \"15000\"\n }\n ]\n }\n}",rf:[]},
{cat:"Basic Pricing",m:"post",p:"/basic-pricings/",s:"Create basic pricing",q:[],rb:"{\n \"name\": \"Basic Price Bandung-Jakarta\",\n \"province_source_id\": \"01HXYZ123456789012345678\",\n \"city_source_id\": \"01HXYZ123456789012345678\",\n \"district_source_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_source_id\": \"01HXYZ123456789012345678\",\n \"postal_code_source_id\": \"01HXYZ123456789012345678\",\n \"province_destination_id\": \"01HXYZ123456789012345678\",\n \"city_destination_id\": \"01HXYZ123456789012345678\",\n \"district_destination_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_destination_id\": \"01HXYZ123456789012345678\",\n \"postal_code_destination_id\": \"01HXYZ123456789012345678\",\n \"package_type_id\": \"01HXYZ123456789012345678\",\n \"moda_id\": \"01HXYZ123456789012345678\",\n \"components\": [\n {\n \"cost_component_id\": \"01HXYZ123456789012345678\",\n \"value\": 15000\n }\n ]\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Basic Price Bandung-Jakarta\",\n \"province_source_id\": \"01HXYZ123456789012345678\",\n \"city_source_id\": \"01HXYZ123456789012345678\",\n \"district_source_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_source_id\": \"01HXYZ123456789012345678\",\n \"postal_code_source_id\": \"01HXYZ123456789012345678\",\n \"province_destination_id\": \"01HXYZ123456789012345678\",\n \"city_destination_id\": \"01HXYZ123456789012345678\",\n \"district_destination_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_destination_id\": \"01HXYZ123456789012345678\",\n \"postal_code_destination_id\": \"01HXYZ123456789012345678\",\n \"package_type_id\": \"01HXYZ123456789012345678\",\n \"moda_id\": \"01HXYZ123456789012345678\",\n \"merchant_id\": \"01HXYZ123456789012345678\",\n \"components\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"cost_component_id\": \"01HXYZ123456789012345678\",\n \"value\": 15000\n }\n ]\n }\n}",rf:["name"]}, {cat:"Basic Pricing",m:"post",p:"/basic-pricings/match",s:"Match basic pricing by ID",q:[],rb:"{\n \"id\": \"01HXYZ123456789012345678\"\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"components\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"cost_component_id\": \"01HXYZ123456789012345678\",\n \"value\": \"15000\"\n }\n ]\n }\n}",rf:["id"]},
{cat:"Basic Pricing",m:"put",p:"/basic-pricings/:id",s:"Update basic pricing",q:[{n:"id",t:"string",req:true,d:"Basic pricing ID"}],rb:"{\n \"name\": \"Basic Price Bandung-Jakarta\",\n \"province_source_id\": \"01HXYZ123456789012345678\",\n \"city_source_id\": \"01HXYZ123456789012345678\",\n \"district_source_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_source_id\": \"01HXYZ123456789012345678\",\n \"postal_code_source_id\": \"01HXYZ123456789012345678\",\n \"province_destination_id\": \"01HXYZ123456789012345678\",\n \"city_destination_id\": \"01HXYZ123456789012345678\",\n \"district_destination_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_destination_id\": \"01HXYZ123456789012345678\",\n \"postal_code_destination_id\": \"01HXYZ123456789012345678\",\n \"package_type_id\": \"01HXYZ123456789012345678\",\n \"moda_id\": \"01HXYZ123456789012345678\",\n \"components\": [\n {\n \"cost_component_id\": \"01HXYZ123456789012345678\",\n \"value\": 15000\n }\n ]\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Basic Price Bandung-Jakarta\",\n \"province_source_id\": \"01HXYZ123456789012345678\",\n \"city_source_id\": \"01HXYZ123456789012345678\",\n \"district_source_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_source_id\": \"01HXYZ123456789012345678\",\n \"postal_code_source_id\": \"01HXYZ123456789012345678\",\n \"province_destination_id\": \"01HXYZ123456789012345678\",\n \"city_destination_id\": \"01HXYZ123456789012345678\",\n \"district_destination_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_destination_id\": \"01HXYZ123456789012345678\",\n \"postal_code_destination_id\": \"01HXYZ123456789012345678\",\n \"package_type_id\": \"01HXYZ123456789012345678\",\n \"moda_id\": \"01HXYZ123456789012345678\",\n \"merchant_id\": \"01HXYZ123456789012345678\",\n \"components\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"cost_component_id\": \"01HXYZ123456789012345678\",\n \"value\": 15000\n }\n ]\n }\n}",rf:["name"]}, {cat:"Basic Pricing",m:"post",p:"/basic-pricings/",s:"Create basic pricing",q:[],rb:"{\n \"province_source_id\": \"01HXYZ123456789012345678\",\n \"city_source_id\": \"01HXYZ123456789012345678\",\n \"district_source_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_source_id\": \"01HXYZ123456789012345678\",\n \"postal_code_source_id\": \"01HXYZ123456789012345678\",\n \"province_destination_id\": \"01HXYZ123456789012345678\",\n \"city_destination_id\": \"01HXYZ123456789012345678\",\n \"district_destination_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_destination_id\": \"01HXYZ123456789012345678\",\n \"postal_code_destination_id\": \"01HXYZ123456789012345678\",\n \"package_type_id\": \"01HXYZ123456789012345678\",\n \"moda_id\": \"01HXYZ123456789012345678\",\n \"lead_time\": \"2-3 days\",\n \"minimum_weight\": 1.0,\n \"components\": [\n {\n \"cost_component_id\": \"01HXYZ123456789012345678\",\n \"value\": \"15000\"\n }\n ]\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"province_source_id\": \"01HXYZ123456789012345678\",\n \"city_source_id\": \"01HXYZ123456789012345678\",\n \"district_source_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_source_id\": \"01HXYZ123456789012345678\",\n \"postal_code_source_id\": \"01HXYZ123456789012345678\",\n \"province_destination_id\": \"01HXYZ123456789012345678\",\n \"city_destination_id\": \"01HXYZ123456789012345678\",\n \"district_destination_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_destination_id\": \"01HXYZ123456789012345678\",\n \"postal_code_destination_id\": \"01HXYZ123456789012345678\",\n \"package_type_id\": \"01HXYZ123456789012345678\",\n \"moda_id\": \"01HXYZ123456789012345678\",\n \"lead_time\": \"2-3 days\",\n \"minimum_weight\": 1.0,\n \"merchant_id\": \"01HXYZ123456789012345678\",\n \"components\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"cost_component_id\": \"01HXYZ123456789012345678\",\n \"value\": \"15000\"\n }\n ]\n }\n}",rf:["name"]},
{cat:"Basic Pricing",m:"delete",p:"/basic-pricings/:id",s:"Delete basic pricing",q:[{n:"id",t:"string",req:true,d:"Basic pricing ID"}],rb:null,rbe:"{\n \"success\": true,\n \"message\": \"basic pricing deleted\"\n}",rf:[]} {cat:"Basic Pricing",m:"put",p:"/basic-pricings/:id",s:"Update basic pricing",q:[{n:"id",t:"string",req:true,d:"Basic pricing ID"}],rb:"{\n \"province_source_id\": \"01HXYZ123456789012345678\",\n \"city_source_id\": \"01HXYZ123456789012345678\",\n \"district_source_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_source_id\": \"01HXYZ123456789012345678\",\n \"postal_code_source_id\": \"01HXYZ123456789012345678\",\n \"province_destination_id\": \"01HXYZ123456789012345678\",\n \"city_destination_id\": \"01HXYZ123456789012345678\",\n \"district_destination_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_destination_id\": \"01HXYZ123456789012345678\",\n \"postal_code_destination_id\": \"01HXYZ123456789012345678\",\n \"package_type_id\": \"01HXYZ123456789012345678\",\n \"moda_id\": \"01HXYZ123456789012345678\",\n \"lead_time\": \"2-3 days\",\n \"minimum_weight\": 1.0,\n \"components\": [\n {\n \"cost_component_id\": \"01HXYZ123456789012345678\",\n \"value\": \"15000\"\n }\n ]\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"province_source_id\": \"01HXYZ123456789012345678\",\n \"city_source_id\": \"01HXYZ123456789012345678\",\n \"district_source_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_source_id\": \"01HXYZ123456789012345678\",\n \"postal_code_source_id\": \"01HXYZ123456789012345678\",\n \"province_destination_id\": \"01HXYZ123456789012345678\",\n \"city_destination_id\": \"01HXYZ123456789012345678\",\n \"district_destination_id\": \"01HXYZ123456789012345678\",\n \"subdistrict_destination_id\": \"01HXYZ123456789012345678\",\n \"postal_code_destination_id\": \"01HXYZ123456789012345678\",\n \"package_type_id\": \"01HXYZ123456789012345678\",\n \"moda_id\": \"01HXYZ123456789012345678\",\n \"lead_time\": \"2-3 days\",\n \"minimum_weight\": 1.0,\n \"merchant_id\": \"01HXYZ123456789012345678\",\n \"components\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"cost_component_id\": \"01HXYZ123456789012345678\",\n \"value\": \"15000\"\n }\n ]\n }\n}",rf:["name"]},
{cat:"Basic Pricing",m:"delete",p:"/basic-pricings/:id",s:"Delete basic pricing",q:[{n:"id",t:"string",req:true,d:"Basic pricing ID"}],rb:null,rbe:"{\n \"success\": true,\n \"message\": \"basic pricing deleted\"\n}",rf:[]},
{cat:"Master Data / Package Type",m:"get",p:"/master-data/package-types/",s:"Get all package types",q:[{n:"id",t:"string",req:false,d:"Filter by ID"},{n:"name",t:"string",req:false,d:"Filter by name"},{n:"merchant_id",t:"string",req:false,d:"Filter by merchant"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Reguler\",\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n ]\n}",rf:[]},
{cat:"Master Data / Package Type",m:"post",p:"/master-data/package-types/list",s:"Package type list (DataTable)",q:[],rb:"_DT_",rbe:"{\n \"success\": true,\n \"data\": {\n \"draw\": 1,\n \"recordsTotal\": 10,\n \"recordsFiltered\": 10,\n \"data\": [\n { \"id\": \"01HXYZ123456789012345678\", \"name\": \"Reguler\" }\n ]\n }\n}",rf:[]},
{cat:"Master Data / Package Type",m:"get",p:"/master-data/package-types/:id",s:"Get package type by ID",q:[{n:"id",t:"string",req:true,d:"Package type ID"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Reguler\",\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:[]},
{cat:"Master Data / Package Type",m:"post",p:"/master-data/package-types/",s:"Create package type",q:[],rb:"{\n \"name\": \"Reguler\"\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Reguler\",\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:["name"]},
{cat:"Master Data / Package Type",m:"put",p:"/master-data/package-types/:id",s:"Update package type",q:[{n:"id",t:"string",req:true,d:"Package type ID"}],rb:"{\n \"name\": \"Express\"\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Express\",\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:["name"]},
{cat:"Master Data / Package Type",m:"delete",p:"/master-data/package-types/:id",s:"Delete package type",q:[{n:"id",t:"string",req:true,d:"Package type ID"}],rb:null,rbe:"{\n \"success\": true,\n \"message\": \"package type deleted\"\n}",rf:[]},
{cat:"Master Data / Moda",m:"get",p:"/master-data/modas/",s:"Get all modas",q:[{n:"id",t:"string",req:false,d:"Filter by ID"},{n:"name",t:"string",req:false,d:"Filter by name"},{n:"merchant_id",t:"string",req:false,d:"Filter by merchant"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Darat\",\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n ]\n}",rf:[]},
{cat:"Master Data / Moda",m:"post",p:"/master-data/modas/list",s:"Moda list (DataTable)",q:[],rb:"_DT_",rbe:"{\n \"success\": true,\n \"data\": {\n \"draw\": 1,\n \"recordsTotal\": 5,\n \"recordsFiltered\": 5,\n \"data\": [\n { \"id\": \"01HXYZ123456789012345678\", \"name\": \"Darat\" }\n ]\n }\n}",rf:[]},
{cat:"Master Data / Moda",m:"get",p:"/master-data/modas/:id",s:"Get moda by ID",q:[{n:"id",t:"string",req:true,d:"Moda ID"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Darat\",\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:[]},
{cat:"Master Data / Moda",m:"post",p:"/master-data/modas/",s:"Create moda",q:[],rb:"{\n \"name\": \"Darat\"\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Darat\",\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:["name"]},
{cat:"Master Data / Moda",m:"put",p:"/master-data/modas/:id",s:"Update moda",q:[{n:"id",t:"string",req:true,d:"Moda ID"}],rb:"{\n \"name\": \"Udara\"\n}",rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"name\": \"Udara\",\n \"merchant_id\": \"01HXYZ123456789012345678\"\n }\n}",rf:["name"]},
{cat:"Master Data / Moda",m:"delete",p:"/master-data/modas/:id",s:"Delete moda",q:[{n:"id",t:"string",req:true,d:"Moda ID"}],rb:null,rbe:"{\n \"success\": true,\n \"message\": \"moda deleted\"\n}",rf:[]},
{cat:"Activity Log",m:"get",p:"/activity-logs/",s:"Get all activity logs",q:[{n:"table_name",t:"string",req:false,d:"Filter by table name"},{n:"record_id",t:"string",req:false,d:"Filter by record ID"},{n:"action",t:"string",req:false,d:"Filter by action (CREATE, UPDATE, DELETE, REVERT)"},{n:"user_id",t:"string",req:false,d:"Filter by user ID"},{n:"date_from",t:"string",req:false,d:"Filter from date"},{n:"date_to",t:"string",req:false,d:"Filter to date"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": [\n {\n \"id\": \"01HXYZ123456789012345678\",\n \"table_name\": \"province\",\n \"record_id\": \"01HXYZ123456789012345678\",\n \"action\": \"DELETE\",\n \"old_data\": \"{...}\",\n \"new_data\": null,\n \"cascade_data\": \"[{...}]\",\n \"user_id\": \"01HXYZ123456789012345678\",\n \"user_name\": \"Admin Utama\",\n \"created_on\": \"2026-09-05 10:30:00\",\n \"cascade_count\": 1102\n }\n ]\n}",rf:[]},
{cat:"Activity Log",m:"post",p:"/activity-logs/list",s:"Activity log list (DataTable)",q:[],rb:"_DT_",rbe:"{\n \"success\": true,\n \"data\": {\n \"draw\": 1,\n \"recordsTotal\": 42,\n \"recordsFiltered\": 42,\n \"data\": []\n }\n}",rf:[]},
{cat:"Activity Log",m:"get",p:"/activity-logs/:id",s:"Get activity log by ID",q:[{n:"id",t:"string",req:true,d:"Activity log ID"}],rb:null,rbe:"{\n \"success\": true,\n \"data\": {\n \"id\": \"01HXYZ123456789012345678\",\n \"table_name\": \"province\",\n \"record_id\": \"01HXYZ123456789012345678\",\n \"action\": \"DELETE\",\n \"old_data\": \"{...}\",\n \"cascade_data\": \"[{...}]\",\n \"user_id\": \"01HXYZ123456789012345678\",\n \"user_name\": \"Admin Utama\",\n \"created_on\": \"2026-09-05 10:30:00\",\n \"cascade_count\": 1102\n }\n}",rf:[]},
{cat:"Activity Log",m:"post",p:"/activity-logs/:id/revert",s:"Revert activity log action",q:[{n:"id",t:"string",req:true,d:"Activity log ID"}],rb:null,rbe:"{\n \"success\": true,\n \"message\": \"action reverted successfully\"\n}",rf:["id"]}
]; ];
function fmtMethod(m){return m.toUpperCase()} function fmtMethod(m){return m.toUpperCase()}
function methodColor(m){return{get:"var(--get)",post:"var(--post)",put:"var(--put)",delete:"var(--delete)"}[m]||"var(--text2)"} function methodColor(m){return{get:"var(--get)",post:"var(--post)",put:"var(--put)",delete:"var(--delete)"}[m]||"var(--text2)"}

View File

@ -54,7 +54,10 @@ func (d *Database) Connect() {
) )
d.AppLogger.Info("Connect To Master DB") d.AppLogger.Info("Connect To Master DB")
gormLogger := logger.NewGormZapLogger(d.Logger, 5*time.Second) gormLogger := logger.NewGormZapLogger(d.Logger, 5*time.Second)
dbMaster, err := gorm.Open(postgres.Open(dsnMaster), &gorm.Config{ dbMaster, err := gorm.Open(postgres.New(postgres.Config{
DSN: dsnMaster,
PreferSimpleProtocol: true,
}), &gorm.Config{
Logger: gormLogger, Logger: gormLogger,
}) })
if err != nil { if err != nil {
@ -76,7 +79,10 @@ func (d *Database) Connect() {
ssl_slave, ssl_slave,
) )
d.AppLogger.Info("Connect To Slave DB") d.AppLogger.Info("Connect To Slave DB")
dbSlave, err := gorm.Open(postgres.Open(dsnSlave), &gorm.Config{ dbSlave, err := gorm.Open(postgres.New(postgres.Config{
DSN: dsnSlave,
PreferSimpleProtocol: true,
}), &gorm.Config{
Logger: gormLogger, Logger: gormLogger,
}) })
if err != nil { if err != nil {

View File

@ -0,0 +1,23 @@
package domain
import "time"
const TableNameActivityLogConst = "activity_log"
type ActivityLog struct {
ID string `gorm:"column:id;type:character(26);primaryKey;default:generate_ulid()" json:"id"`
TargetTable string `gorm:"column:table_name;type:character varying(100);not null" json:"table_name"`
RecordID string `gorm:"column:record_id;type:character(26);not null" json:"record_id"`
Action string `gorm:"column:action;type:character varying(20);not null" json:"action"`
OldData *string `gorm:"column:old_data;type:jsonb" json:"old_data"`
NewData *string `gorm:"column:new_data;type:jsonb" json:"new_data"`
CascadeData *string `gorm:"column:cascade_data;type:jsonb" json:"cascade_data"`
UserID string `gorm:"column:user_id;type:character(26);not null" json:"user_id"`
UserName string `gorm:"column:user_name;type:character varying(255);not null" json:"user_name"`
MerchantID *string `gorm:"column:merchant_id;type:character(26)" json:"merchant_id"`
CreatedOn *time.Time `gorm:"column:created_on;type:timestamp without time zone;default:now()" json:"created_on"`
}
func (*ActivityLog) TableName() string {
return TableNameActivityLogConst
}

View File

@ -10,7 +10,7 @@ const TableNameBasicPricingComponent = "basic_pricing_component"
type BasicPricingComponent struct { type BasicPricingComponent struct {
ID string `gorm:"column:id;type:character(26);primaryKey;default:generate_ulid()" json:"id"` ID string `gorm:"column:id;type:character(26);primaryKey;default:generate_ulid()" json:"id"`
CostComponentID *string `gorm:"column:cost_component_id;type:character(26);index:basic_pricing_component_cost_component_id_idx,priority:1" json:"cost_component_id"` CostComponentID *string `gorm:"column:cost_component_id;type:character(26);index:basic_pricing_component_cost_component_id_idx,priority:1" json:"cost_component_id"`
Value *float32 `gorm:"column:value;type:real" json:"value"` Value *string `gorm:"column:value;type:character varying(255)" json:"value"`
BasicPricingID *string `gorm:"column:basic_pricing_id;type:character(26);index:basic_pricing_component_basic_pricing_id_idx,priority:1" json:"basic_pricing_id"` BasicPricingID *string `gorm:"column:basic_pricing_id;type:character(26);index:basic_pricing_component_basic_pricing_id_idx,priority:1" json:"basic_pricing_id"`
BasicPricing_Relation BasicPricing `gorm:"foreignKey:basic_pricing_id;references:id" json:"basic_pricing_relation"` BasicPricing_Relation BasicPricing `gorm:"foreignKey:basic_pricing_id;references:id" json:"basic_pricing_relation"`
CostComponent_Relation CostComponent `gorm:"foreignKey:cost_component_id;references:id" json:"cost_component_relation"` CostComponent_Relation CostComponent `gorm:"foreignKey:cost_component_id;references:id" json:"cost_component_relation"`

View File

@ -21,7 +21,7 @@ type PostalCode struct {
CreatedBy *string `gorm:"column:created_by;type:character(26);index:postal_code_created_by_idx,priority:1" json:"created_by"` CreatedBy *string `gorm:"column:created_by;type:character(26);index:postal_code_created_by_idx,priority:1" json:"created_by"`
CreatedOn *time.Time `gorm:"column:created_on;type:timestamp without time zone" json:"created_on"` CreatedOn *time.Time `gorm:"column:created_on;type:timestamp without time zone" json:"created_on"`
UpdatedBy *string `gorm:"column:updated_by;type:character(26)" json:"updated_by"` UpdatedBy *string `gorm:"column:updated_by;type:character(26)" json:"updated_by"`
UpdatedOn *time.Time `gorm:"column:updated_on;type:timestamp without time zone" json:"updated_on"` UpdatedOn *string `gorm:"column:updated_on;type:character varying" json:"updated_on"`
MerchantID *string `gorm:"column:merchant_id;type:character(26)" json:"merchant_id"` MerchantID *string `gorm:"column:merchant_id;type:character(26)" json:"merchant_id"`
BasicPricing_ByPostalCodeDestination_Relation []BasicPricing `gorm:"foreignKey:postal_code_destination_id;references:id" json:"basic_pricing_by_postal_code_destination_relation"` BasicPricing_ByPostalCodeDestination_Relation []BasicPricing `gorm:"foreignKey:postal_code_destination_id;references:id" json:"basic_pricing_by_postal_code_destination_relation"`
BasicPricing_ByPostalCodeSource_Relation []BasicPricing `gorm:"foreignKey:postal_code_source_id;references:id" json:"basic_pricing_by_postal_code_source_relation"` BasicPricing_ByPostalCodeSource_Relation []BasicPricing `gorm:"foreignKey:postal_code_source_id;references:id" json:"basic_pricing_by_postal_code_source_relation"`

View File

@ -0,0 +1,27 @@
package dto
type ActivityLogFilterRequest struct {
ID string `json:"id" form:"id"`
TableName string `json:"table_name" form:"table_name"`
RecordID string `json:"record_id" form:"record_id"`
Action string `json:"action" form:"action"`
UserID string `json:"user_id" form:"user_id"`
MerchantID string `json:"merchant_id" form:"merchant_id"`
DateFrom string `json:"date_from" form:"date_from"`
DateTo string `json:"date_to" form:"date_to"`
}
type ActivityLogResponse struct {
ID string `json:"id"`
TableName string `json:"table_name"`
RecordID string `json:"record_id"`
Action string `json:"action"`
OldData *string `json:"old_data"`
NewData *string `json:"new_data"`
CascadeData *string `json:"cascade_data"`
UserID string `json:"user_id"`
UserName string `json:"user_name"`
MerchantID *string `json:"merchant_id"`
CreatedOn *string `json:"created_on"`
CascadeCount int `json:"cascade_count,omitempty"`
}

View File

@ -2,13 +2,13 @@ package dto
type BasicPricingComponentRequest struct { type BasicPricingComponentRequest struct {
CostComponentID string `json:"cost_component_id" binding:"required"` CostComponentID string `json:"cost_component_id" binding:"required"`
Value float32 `json:"value"` Value string `json:"value"`
} }
type BasicPricingComponentResponse struct { type BasicPricingComponentResponse struct {
ID string `json:"id"` ID string `json:"id"`
CostComponentID *string `json:"cost_component_id"` CostComponentID *string `json:"cost_component_id"`
Value *float32 `json:"value"` Value *string `json:"value"`
} }
type CreateBasicPricingRequest struct { type CreateBasicPricingRequest struct {
@ -24,8 +24,8 @@ type CreateBasicPricingRequest struct {
PostalCodeDestinationID string `json:"postal_code_destination_id" binding:"required"` PostalCodeDestinationID string `json:"postal_code_destination_id" binding:"required"`
PackageTypeID string `json:"package_type_id" binding:"required"` PackageTypeID string `json:"package_type_id" binding:"required"`
ModaID string `json:"moda_id" binding:"required"` ModaID string `json:"moda_id" binding:"required"`
LeadTime string `json:"lead_time"` LeadTime string `json:"lead_time" binding:"required"`
MinimumWeight float32 `json:"minimum_weight"` MinimumWeight float32 `json:"minimum_weight" binding:"required"`
Components []BasicPricingComponentRequest `json:"components" binding:"required,dive"` Components []BasicPricingComponentRequest `json:"components" binding:"required,dive"`
} }
@ -65,3 +65,7 @@ type BasicPricingFilterAllRequest struct {
ModaID string `json:"moda_id" form:"moda_id"` ModaID string `json:"moda_id" form:"moda_id"`
MerchantID string `json:"merchant_id" form:"merchant_id"` MerchantID string `json:"merchant_id" form:"merchant_id"`
} }
type BasicPricingMatchRequest struct {
ID string `json:"id" form:"id" binding:"required"`
}

View File

@ -0,0 +1,21 @@
package dto
type CreateModaRequest struct {
Name string `json:"name" binding:"required,min=2"`
}
type CreateModaResponse struct {
ID string `json:"id"`
Name *string `json:"name"`
MerchantID *string `json:"merchant_id"`
}
type ModaFilterAllRequest struct {
ID string `json:"id" form:"id"`
Name string `json:"name" form:"name"`
MerchantID string `json:"merchant_id" form:"merchant_id"`
}
type UpdateModaRequest struct {
Name string `json:"name" binding:"required,min=2"`
}

View File

@ -0,0 +1,21 @@
package dto
type CreatePackageTypeRequest struct {
Name string `json:"name" binding:"required,min=2"`
}
type CreatePackageTypeResponse struct {
ID string `json:"id"`
Name *string `json:"name"`
MerchantID *string `json:"merchant_id"`
}
type PackageTypeFilterAllRequest struct {
ID string `json:"id" form:"id"`
Name string `json:"name" form:"name"`
MerchantID string `json:"merchant_id" form:"merchant_id"`
}
type UpdatePackageTypeRequest struct {
Name string `json:"name" binding:"required,min=2"`
}

View File

@ -1,18 +1,24 @@
package handlers package handlers
import ( import (
"cargo-erp-backend/internal/handlers/router/activity_log"
"cargo-erp-backend/internal/handlers/router/auth" "cargo-erp-backend/internal/handlers/router/auth"
"cargo-erp-backend/internal/handlers/router/basic_pricing" "cargo-erp-backend/internal/handlers/router/basic_pricing"
"cargo-erp-backend/internal/handlers/router/city" "cargo-erp-backend/internal/handlers/router/city"
"cargo-erp-backend/internal/handlers/router/cost_component" "cargo-erp-backend/internal/handlers/router/cost_component"
"cargo-erp-backend/internal/handlers/router/district" "cargo-erp-backend/internal/handlers/router/district"
"cargo-erp-backend/internal/handlers/router/moda"
"cargo-erp-backend/internal/handlers/router/package_type"
"cargo-erp-backend/internal/handlers/router/postal_code" "cargo-erp-backend/internal/handlers/router/postal_code"
"cargo-erp-backend/internal/handlers/router/province" "cargo-erp-backend/internal/handlers/router/province"
"cargo-erp-backend/internal/handlers/router/subdistrict" "cargo-erp-backend/internal/handlers/router/subdistrict"
"cargo-erp-backend/internal/middleware" "cargo-erp-backend/internal/middleware"
"cargo-erp-backend/pkg/helpers" "cargo-erp-backend/pkg/helpers"
"cargo-erp-backend/internal/usecases"
"fmt" "fmt"
"net/http" "net/http"
"os"
"path/filepath"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -108,6 +114,13 @@ func (h *Handlers) Run() {
"message": "pong", "message": "pong",
}) })
}) })
// serve API docs (no auth)
docsPath := filepath.Join(".", "..", "..", "docs")
if _, err := os.Stat(docsPath); err == nil {
r.Static("/docs", docsPath)
}
//auth //auth
auth.NewAuthHandler(h.Helper, r).Router() auth.NewAuthHandler(h.Helper, r).Router()
@ -122,6 +135,8 @@ func (h *Handlers) Run() {
subdistrict.NewSubdistrictHandler(h.Helper, masterdata).Router() subdistrict.NewSubdistrictHandler(h.Helper, masterdata).Router()
postal_code.NewPostalCodeHandler(h.Helper, masterdata).Router() postal_code.NewPostalCodeHandler(h.Helper, masterdata).Router()
cost_component.NewCostComponentHandler(h.Helper, masterdata).Router() cost_component.NewCostComponentHandler(h.Helper, masterdata).Router()
package_type.NewPackageTypeHandler(h.Helper, masterdata).Router()
moda.NewModaHandler(h.Helper, masterdata).Router()
} }
//basic-pricing //basic-pricing
@ -129,6 +144,19 @@ func (h *Handlers) Run() {
basicPricingGroup.Use(middleware.NewAuthMiddleware(h.Helper).Check()) basicPricingGroup.Use(middleware.NewAuthMiddleware(h.Helper).Check())
basic_pricing.NewBasicPricingHandler(h.Helper, basicPricingGroup).Router() basic_pricing.NewBasicPricingHandler(h.Helper, basicPricingGroup).Router()
//activity-logs
activityLogGroup := r.Group("activity-logs")
activityLogGroup.Use(middleware.NewAuthMiddleware(h.Helper).Check())
activity_log.NewActivityLogHandler(h.Helper, activityLogGroup).Router()
// load cascade rules from DB
db := h.Helper.GetDB("master")
if err := usecases.LoadCascadeRules(db); err != nil {
h.Helper.Log().Error("failed to load cascade rules", zap.Error(err))
} else {
h.Helper.Log().Info("Cascade rules loaded from database")
}
h.Helper.Log().Info("Starting Apps") h.Helper.Log().Info("Starting Apps")
r.Run(fmt.Sprintf("%v:%v", host, port)) r.Run(fmt.Sprintf("%v:%v", host, port))
} }

View File

@ -0,0 +1,119 @@
package activity_log
import (
"cargo-erp-backend/internal/handlers/dto"
"cargo-erp-backend/internal/usecases"
"cargo-erp-backend/pkg/helpers"
"cargo-erp-backend/pkg/response"
"net/http"
"github.com/gin-gonic/gin"
)
type ActivityLogHandlerInterface interface {
Router()
}
type ActivityLogHandler struct {
Helper helpers.HelperInterface
Group *gin.RouterGroup
}
func NewActivityLogHandler(helper helpers.HelperInterface, g *gin.RouterGroup) ActivityLogHandlerInterface {
return &ActivityLogHandler{Helper: helper, Group: g}
}
func (h *ActivityLogHandler) Router() {
r := h.Group
{
r.GET("/", h.GetAll)
r.POST("/list", h.GetList)
r.GET("/:id", h.GetByID)
r.POST("/:id/revert", h.Revert)
}
}
func (h *ActivityLogHandler) GetAll(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
var req dto.ActivityLogFilterRequest
if err := c.ShouldBindQuery(&req); err != nil {
response.Error(c, http.StatusBadRequest, err.Error())
return
}
u := usecases.NewActivityLogUsecase(h.Helper, *userid)
logs, _, err := u.GetAll(req)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, logs)
}
func (h *ActivityLogHandler) GetList(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
var req dto.DataTableRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "Request Format request invalid")
return
}
u := usecases.NewActivityLogUsecase(h.Helper, *userid)
res, err := u.GetList(req)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, res)
}
func (h *ActivityLogHandler) GetByID(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
id := c.Param("id")
u := usecases.NewActivityLogUsecase(h.Helper, *userid)
log, err := u.GetByID(id)
if err != nil {
response.Error(c, http.StatusNotFound, "activity log not found")
return
}
response.Success(c, log)
}
func (h *ActivityLogHandler) Revert(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
roleKey := ""
if userid.UsersRole_Relation != nil {
roleKey = userid.UsersRole_Relation.RoleKey
}
if roleKey != "SPM" {
response.Error(c, http.StatusForbidden, "only Super Admin can revert")
return
}
id := c.Param("id")
u := usecases.NewActivityLogUsecase(h.Helper, *userid)
if err := u.Revert(id); err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, map[string]interface{}{"message": "action reverted successfully"})
}

View File

@ -29,6 +29,7 @@ func (h *BasicPricingHandler) Router() {
r.GET("/", h.GetAll) r.GET("/", h.GetAll)
r.POST("/list", h.GetList) r.POST("/list", h.GetList)
r.GET("/:id", h.GetByID) r.GET("/:id", h.GetByID)
r.POST("/match", h.GetMatch)
r.POST("/", h.Create) r.POST("/", h.Create)
r.PUT("/:id", h.Update) r.PUT("/:id", h.Update)
r.DELETE("/:id", h.Delete) r.DELETE("/:id", h.Delete)
@ -160,3 +161,24 @@ func (h *BasicPricingHandler) Delete(c *gin.Context) {
response.Success(c, map[string]interface{}{"message": "basic pricing deleted"}) response.Success(c, map[string]interface{}{"message": "basic pricing deleted"})
} }
func (h *BasicPricingHandler) GetMatch(c *gin.Context) {
var req dto.BasicPricingMatchRequest
if err := c.BindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, err.Error())
return
}
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
u := usecases.NewBasicPricingUsecase(h.Helper, *userid)
pricing := u.GetMatch(req.ID)
if pricing == nil {
response.Error(c, http.StatusNotFound, "basic pricing not found")
return
}
response.Success(c, pricing)
}

View File

@ -0,0 +1,172 @@
package moda
import (
"cargo-erp-backend/internal/handlers/dto"
"cargo-erp-backend/internal/usecases"
"cargo-erp-backend/pkg/helpers"
"cargo-erp-backend/pkg/response"
"net/http"
"github.com/gin-gonic/gin"
)
type ModaHandlerInterface interface {
Router()
}
type ModaHandler struct {
Helper helpers.HelperInterface
Group *gin.RouterGroup
}
func NewModaHandler(helper helpers.HelperInterface, g *gin.RouterGroup) ModaHandlerInterface {
return &ModaHandler{Helper: helper, Group: g}
}
func (h *ModaHandler) Router() {
r := h.Group.Group("modas")
{
r.GET("/", h.GetAll)
r.POST("/list", h.GetList)
r.GET("/:id", h.GetByID)
r.POST("/", h.Create)
r.PUT("/:id", h.Update)
r.DELETE("/:id", h.Delete)
}
}
func (h *ModaHandler) GetAll(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
var req dto.ModaFilterAllRequest
if err := c.ShouldBindQuery(&req); err != nil {
response.Error(c, http.StatusBadRequest, err.Error())
return
}
u := usecases.NewModaUsecase(h.Helper, *userid)
items, _, err := u.GetAll(req)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
var responseDTO = make([]dto.CreateModaResponse, 0)
for _, v := range items {
var DTO dto.CreateModaResponse
h.Helper.MapStructToStruct(v, "json", &DTO, "json")
responseDTO = append(responseDTO, DTO)
}
response.Success(c, responseDTO)
}
func (h *ModaHandler) GetList(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
var req dto.DataTableRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "Request Format request invalid")
return
}
u := usecases.NewModaUsecase(h.Helper, *userid)
res, err := u.GetList(req)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, res)
return
}
func (h *ModaHandler) GetByID(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
id := c.Param("id")
u := usecases.NewModaUsecase(h.Helper, *userid)
item, err := u.GetByID(id)
if err != nil {
response.Error(c, http.StatusNotFound, "moda not found")
return
}
response.Success(c, item)
}
func (h *ModaHandler) Create(c *gin.Context) {
var req dto.CreateModaRequest
if err := c.BindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, err.Error())
return
}
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
var merchantid *string = nil
if userid.UsersRole_Relation == nil || userid.UsersRole_Relation.RoleKey != "SPM" {
merchantid = userid.MerchantID
}
createdBy := userid.ID
u := usecases.NewModaUsecase(h.Helper, *userid)
item, err := u.Create(req.Name, createdBy, merchantid)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
var responseDTO dto.CreateModaResponse
h.Helper.MapStructToStruct(item, "json", &responseDTO, "json")
response.Created(c, responseDTO)
}
func (h *ModaHandler) Update(c *gin.Context) {
id := c.Param("id")
var req dto.UpdateModaRequest
if err := c.BindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, err.Error())
return
}
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
updatedBy := userid.ID
u := usecases.NewModaUsecase(h.Helper, *userid)
item, err := u.Update(id, req.Name, updatedBy)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
var responseDTO dto.CreateModaResponse
h.Helper.MapStructToStruct(item, "json", &responseDTO, "json")
response.Success(c, responseDTO)
}
func (h *ModaHandler) Delete(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
id := c.Param("id")
u := usecases.NewModaUsecase(h.Helper, *userid)
if err := u.Delete(id); err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, map[string]interface{}{"message": "moda deleted"})
}

View File

@ -0,0 +1,172 @@
package package_type
import (
"cargo-erp-backend/internal/handlers/dto"
"cargo-erp-backend/internal/usecases"
"cargo-erp-backend/pkg/helpers"
"cargo-erp-backend/pkg/response"
"net/http"
"github.com/gin-gonic/gin"
)
type PackageTypeHandlerInterface interface {
Router()
}
type PackageTypeHandler struct {
Helper helpers.HelperInterface
Group *gin.RouterGroup
}
func NewPackageTypeHandler(helper helpers.HelperInterface, g *gin.RouterGroup) PackageTypeHandlerInterface {
return &PackageTypeHandler{Helper: helper, Group: g}
}
func (h *PackageTypeHandler) Router() {
r := h.Group.Group("package-types")
{
r.GET("/", h.GetAll)
r.POST("/list", h.GetList)
r.GET("/:id", h.GetByID)
r.POST("/", h.Create)
r.PUT("/:id", h.Update)
r.DELETE("/:id", h.Delete)
}
}
func (h *PackageTypeHandler) GetAll(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
var req dto.PackageTypeFilterAllRequest
if err := c.ShouldBindQuery(&req); err != nil {
response.Error(c, http.StatusBadRequest, err.Error())
return
}
u := usecases.NewPackageTypeUsecase(h.Helper, *userid)
items, _, err := u.GetAll(req)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
var responseDTO = make([]dto.CreatePackageTypeResponse, 0)
for _, v := range items {
var DTO dto.CreatePackageTypeResponse
h.Helper.MapStructToStruct(v, "json", &DTO, "json")
responseDTO = append(responseDTO, DTO)
}
response.Success(c, responseDTO)
}
func (h *PackageTypeHandler) GetList(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
var req dto.DataTableRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "Request Format request invalid")
return
}
u := usecases.NewPackageTypeUsecase(h.Helper, *userid)
res, err := u.GetList(req)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, res)
return
}
func (h *PackageTypeHandler) GetByID(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
id := c.Param("id")
u := usecases.NewPackageTypeUsecase(h.Helper, *userid)
item, err := u.GetByID(id)
if err != nil {
response.Error(c, http.StatusNotFound, "package type not found")
return
}
response.Success(c, item)
}
func (h *PackageTypeHandler) Create(c *gin.Context) {
var req dto.CreatePackageTypeRequest
if err := c.BindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, err.Error())
return
}
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
var merchantid *string = nil
if userid.UsersRole_Relation == nil || userid.UsersRole_Relation.RoleKey != "SPM" {
merchantid = userid.MerchantID
}
createdBy := userid.ID
u := usecases.NewPackageTypeUsecase(h.Helper, *userid)
item, err := u.Create(req.Name, createdBy, merchantid)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
var responseDTO dto.CreatePackageTypeResponse
h.Helper.MapStructToStruct(item, "json", &responseDTO, "json")
response.Created(c, responseDTO)
}
func (h *PackageTypeHandler) Update(c *gin.Context) {
id := c.Param("id")
var req dto.UpdatePackageTypeRequest
if err := c.BindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, err.Error())
return
}
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
updatedBy := userid.ID
u := usecases.NewPackageTypeUsecase(h.Helper, *userid)
item, err := u.Update(id, req.Name, updatedBy)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
var responseDTO dto.CreatePackageTypeResponse
h.Helper.MapStructToStruct(item, "json", &responseDTO, "json")
response.Success(c, responseDTO)
}
func (h *PackageTypeHandler) Delete(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
id := c.Param("id")
u := usecases.NewPackageTypeUsecase(h.Helper, *userid)
if err := u.Delete(id); err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, map[string]interface{}{"message": "package type deleted"})
}

View File

@ -97,6 +97,7 @@ func (h *PostalCodeImportHandler) ValidateRow(row []string, rowNumber int) (impo
h.lastSubdistrictName = subdistrictName h.lastSubdistrictName = subdistrictName
now := time.Now() now := time.Now()
nowStr := now.Format("2006-01-02 15:04:05.999999-07:00")
pc := &domain.PostalCode{ pc := &domain.PostalCode{
PostalCode: &postalCode, PostalCode: &postalCode,
ProvinceID: &province.ID, ProvinceID: &province.ID,
@ -107,7 +108,7 @@ func (h *PostalCodeImportHandler) ValidateRow(row []string, rowNumber int) (impo
CreatedBy: &h.UserData.ID, CreatedBy: &h.UserData.ID,
CreatedOn: &now, CreatedOn: &now,
UpdatedBy: &h.UserData.ID, UpdatedBy: &h.UserData.ID,
UpdatedOn: &now, UpdatedOn: &nowStr,
} }
return pc, nil return pc, nil

View File

@ -0,0 +1,500 @@
package usecases
import (
"cargo-erp-backend/internal/domain"
"cargo-erp-backend/internal/handlers/dto"
"cargo-erp-backend/pkg/datatable"
"cargo-erp-backend/pkg/helpers"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
)
type CascadeRule struct {
Table string
FKColumn string
DeleteRule string
}
type CascadeGroup struct {
Table string `json:"table"`
FKColumn string `json:"fk_column"`
DeleteRule string `json:"delete_rule"`
Records []map[string]interface{} `json:"records"`
}
var (
CascadeMap map[string][]CascadeRule
CascadeMapMu sync.RWMutex
)
type ActivityLogUsecaseInterface interface {
GetAll(filter dto.ActivityLogFilterRequest) ([]dto.ActivityLogResponse, int64, error)
GetList(req dto.DataTableRequest) (dto.DataTableResponse, error)
GetByID(id string) (dto.ActivityLogResponse, error)
Revert(logID string) error
LogCreate(tableName string, record interface{}, merchantID *string)
LogUpdate(tableName string, oldData, newData interface{}, merchantID *string)
LogDelete(tableName string, recordID string, oldData interface{}, merchantID *string)
}
type ActivityLogUsecase struct {
Helper helpers.HelperInterface
AuthUser domain.User
}
func NewActivityLogUsecase(helper helpers.HelperInterface, authUser domain.User) ActivityLogUsecaseInterface {
return &ActivityLogUsecase{Helper: helper, AuthUser: authUser}
}
func LoadCascadeRules(db *gorm.DB) error {
rows, err := db.Raw(`
SELECT
tc.table_name AS source_table,
kcu.column_name AS source_column,
ccu.table_name AS target_table,
ccu.column_name AS target_column,
rc.delete_rule
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = ccu.table_schema
JOIN information_schema.referential_constraints rc
ON rc.constraint_name = tc.constraint_name AND rc.constraint_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema = 'public'
ORDER BY tc.table_name
`).Rows()
if err != nil {
return fmt.Errorf("failed to query FK constraints: %w", err)
}
defer rows.Close()
newMap := make(map[string][]CascadeRule)
for rows.Next() {
var fk ForeignKeyInfo
if err := rows.Scan(&fk.SourceTable, &fk.SourceColumn, &fk.TargetTable, &fk.TargetColumn, &fk.DeleteRule); err != nil {
continue
}
if _, ok := newMap[fk.TargetTable]; !ok {
newMap[fk.TargetTable] = make([]CascadeRule, 0)
}
newMap[fk.TargetTable] = append(newMap[fk.TargetTable], CascadeRule{
Table: fk.SourceTable,
FKColumn: fk.SourceColumn,
DeleteRule: fk.DeleteRule,
})
}
CascadeMapMu.Lock()
CascadeMap = newMap
CascadeMapMu.Unlock()
return nil
}
type ForeignKeyInfo struct {
SourceTable string
SourceColumn string
TargetTable string
TargetColumn string
DeleteRule string
}
func CaptureCascade(tx *gorm.DB, tableName string, recordID string) ([]CascadeGroup, error) {
groups := make([]CascadeGroup, 0)
visited := make(map[string]bool)
CascadeMapMu.RLock()
defer CascadeMapMu.RUnlock()
var walk func(table string, ids []string)
walk = func(table string, ids []string) {
if len(ids) == 0 {
return
}
rules, ok := CascadeMap[table]
if !ok {
return
}
for _, rule := range rules {
key := rule.Table + ":" + strings.Join(ids, ",")
if visited[key] {
continue
}
visited[key] = true
var records []map[string]interface{}
if err := tx.Table(rule.Table).Where(rule.FKColumn+" IN ?", ids).Find(&records).Error; err != nil {
continue
}
if len(records) > 0 {
groups = append(groups, CascadeGroup{
Table: rule.Table,
FKColumn: rule.FKColumn,
DeleteRule: rule.DeleteRule,
Records: records,
})
childIDs := extractIDs(records, "id")
walk(rule.Table, childIDs)
}
}
}
walk(tableName, []string{recordID})
return groups, nil
}
func extractIDs(records []map[string]interface{}, key string) []string {
ids := make([]string, 0, len(records))
for _, r := range records {
if v, ok := r[key]; ok && v != nil {
switch id := v.(type) {
case string:
ids = append(ids, id)
case float64:
ids = append(ids, fmt.Sprintf("%.0f", id))
}
}
}
return ids
}
func toJSON(v interface{}) *string {
if v == nil {
return nil
}
b, err := json.Marshal(v)
if err != nil {
return nil
}
s := string(b)
return &s
}
func (u *ActivityLogUsecase) LogCreate(tableName string, record interface{}, merchantID *string) {
db := u.Helper.GetDB("master")
log := domain.ActivityLog{
TargetTable: tableName,
RecordID: extractRecordID(record),
Action: "CREATE",
NewData: toJSON(record),
UserID: u.AuthUser.ID,
UserName: u.getDisplayName(),
MerchantID: merchantID,
CreatedOn: timePtr(time.Now()),
}
db.Create(&log)
}
func (u *ActivityLogUsecase) LogUpdate(tableName string, oldData, newData interface{}, merchantID *string) {
db := u.Helper.GetDB("master")
log := domain.ActivityLog{
TargetTable: tableName,
RecordID: extractRecordID(oldData),
Action: "UPDATE",
OldData: toJSON(oldData),
NewData: toJSON(newData),
UserID: u.AuthUser.ID,
UserName: u.getDisplayName(),
MerchantID: merchantID,
CreatedOn: timePtr(time.Now()),
}
db.Create(&log)
}
func (u *ActivityLogUsecase) LogDelete(tableName string, recordID string, oldData interface{}, merchantID *string) {
db := u.Helper.GetDB("master")
var cascadeData []CascadeGroup
err := db.Transaction(func(tx *gorm.DB) error {
var err error
cascadeData, err = CaptureCascade(tx, tableName, recordID)
return err
})
if err != nil {
u.Helper.Log().Error("failed to capture cascade", zap.Error(err))
cascadeData = []CascadeGroup{}
}
log := domain.ActivityLog{
TargetTable: tableName,
RecordID: recordID,
Action: "DELETE",
OldData: toJSON(oldData),
CascadeData: toJSON(cascadeData),
UserID: u.AuthUser.ID,
UserName: u.getDisplayName(),
MerchantID: merchantID,
CreatedOn: timePtr(time.Now()),
}
db.Create(&log)
}
func (u *ActivityLogUsecase) GetAll(filter dto.ActivityLogFilterRequest) ([]dto.ActivityLogResponse, int64, error) {
var logs []domain.ActivityLog
var total int64
db := u.Helper.GetDB("slave")
tx := db.Model(&logs)
if filter.TableName != "" {
tx = tx.Where("table_name = ?", filter.TableName)
}
if filter.RecordID != "" {
tx = tx.Where("record_id = ?", filter.RecordID)
}
if filter.Action != "" {
tx = tx.Where("action = ?", filter.Action)
}
if filter.UserID != "" {
tx = tx.Where("user_id = ?", filter.UserID)
}
if filter.MerchantID != "" {
tx = tx.Where("merchant_id = ?", filter.MerchantID)
}
if filter.DateFrom != "" {
tx = tx.Where("created_on >= ?", filter.DateFrom)
}
if filter.DateTo != "" {
tx = tx.Where("created_on <= ?", filter.DateTo)
}
tx.Count(&total)
tx.Order("created_on DESC")
if err := tx.Find(&logs).Error; err != nil {
return nil, 0, err
}
responses := make([]dto.ActivityLogResponse, 0, len(logs))
for _, l := range logs {
responses = append(responses, toActivityLogResponse(l))
}
return responses, total, nil
}
func (u *ActivityLogUsecase) GetList(req dto.DataTableRequest) (dto.DataTableResponse, error) {
db := u.Helper.GetDB("slave")
tx := db.Table("activity_log")
dt := datatable.NewDatatable(tx, req)
coldef := make([]dto.DataTableColDef, 0)
coldef = append(coldef, dto.DataTableColDef{Field: "activity_log.table_name", Alias: "table_name"})
coldef = append(coldef, dto.DataTableColDef{Field: "activity_log.record_id", Alias: "record_id"})
coldef = append(coldef, dto.DataTableColDef{Field: "activity_log.action", Alias: "action"})
coldef = append(coldef, dto.DataTableColDef{Field: "activity_log.user_id", Alias: "user_id"})
coldef = append(coldef, dto.DataTableColDef{Field: "activity_log.user_name", Alias: "user_name"})
coldef = append(coldef, dto.DataTableColDef{Field: "activity_log.id", Alias: "id"})
return dt.Render(coldef), nil
}
func (u *ActivityLogUsecase) GetByID(id string) (dto.ActivityLogResponse, error) {
var log domain.ActivityLog
db := u.Helper.GetDB("slave")
if err := db.Where("id = ?", id).First(&log).Error; err != nil {
return dto.ActivityLogResponse{}, err
}
return toActivityLogResponse(log), nil
}
func (u *ActivityLogUsecase) Revert(logID string) error {
db := u.Helper.GetDB("master")
var log domain.ActivityLog
if err := db.Where("id = ?", logID).First(&log).Error; err != nil {
return fmt.Errorf("activity log not found")
}
switch log.Action {
case "CREATE":
return u.revertCreate(db, log)
case "UPDATE":
return u.revertUpdate(db, log)
case "DELETE":
return u.revertDelete(db, log)
default:
return fmt.Errorf("unsupported action: %s", log.Action)
}
}
func (u *ActivityLogUsecase) revertCreate(db *gorm.DB, log domain.ActivityLog) error {
return db.Transaction(func(tx *gorm.DB) error {
if err := tx.Exec("DELETE FROM "+log.TargetTable+" WHERE id = ?", log.RecordID).Error; err != nil {
return err
}
u.logRevert("CREATE", log)
return nil
})
}
func (u *ActivityLogUsecase) revertUpdate(db *gorm.DB, log domain.ActivityLog) error {
return db.Transaction(func(tx *gorm.DB) error {
if log.OldData == nil {
return fmt.Errorf("no old data to revert")
}
var oldRecord map[string]interface{}
if err := json.Unmarshal([]byte(*log.OldData), &oldRecord); err != nil {
return fmt.Errorf("failed to parse old data: %w", err)
}
cleaned := cleanRecordMap(oldRecord)
delete(cleaned, "created_on")
delete(cleaned, "created_by")
if err := tx.Table(log.TargetTable).Where("id = ?", log.RecordID).Updates(cleaned).Error; err != nil {
return err
}
u.logRevert("UPDATE", log)
return nil
})
}
func (u *ActivityLogUsecase) revertDelete(db *gorm.DB, log domain.ActivityLog) error {
return db.Transaction(func(tx *gorm.DB) error {
var cascadeGroups []CascadeGroup
if log.CascadeData != nil {
if err := json.Unmarshal([]byte(*log.CascadeData), &cascadeGroups); err != nil {
return fmt.Errorf("failed to parse cascade data: %w", err)
}
}
for i := len(cascadeGroups) - 1; i >= 0; i-- {
group := cascadeGroups[i]
for _, record := range group.Records {
cleaned := cleanRecordMap(record)
tx.Table(group.Table).Create(cleaned)
}
}
if log.OldData != nil {
var mainRecord map[string]interface{}
if err := json.Unmarshal([]byte(*log.OldData), &mainRecord); err == nil {
cleaned := cleanRecordMap(mainRecord)
tx.Table(log.TargetTable).Create(cleaned)
}
}
u.logRevert("DELETE", log)
return nil
})
}
func (u *ActivityLogUsecase) logRevert(originalAction string, originalLog domain.ActivityLog) {
db := u.Helper.GetDB("master")
revertLog := domain.ActivityLog{
TargetTable: originalLog.TargetTable,
RecordID: originalLog.RecordID,
Action: "REVERT",
OldData: toJSON(fmt.Sprintf("Reverted %s action (log_id: %s)", originalAction, originalLog.ID)),
UserID: u.AuthUser.ID,
UserName: u.getDisplayName(),
MerchantID: originalLog.MerchantID,
CreatedOn: timePtr(time.Now()),
}
db.Create(&revertLog)
}
func (u *ActivityLogUsecase) getDisplayName() string {
if u.AuthUser.NickName != nil && *u.AuthUser.NickName != "" {
return *u.AuthUser.NickName
}
if u.AuthUser.Email != nil {
return *u.AuthUser.Email
}
return u.AuthUser.ID
}
func extractRecordID(record interface{}) string {
switch v := record.(type) {
case map[string]interface{}:
if id, ok := v["id"].(string); ok {
return id
}
case domain.Province:
return v.ID
case domain.City:
return v.ID
case domain.District:
return v.ID
case domain.Subdistrict:
return v.ID
case domain.PostalCode:
return v.ID
case domain.CostComponent:
return v.ID
case domain.PackageType:
return v.ID
case domain.Moda:
return v.ID
case domain.BasicPricing:
return v.ID
}
b, err := json.Marshal(record)
if err != nil {
return ""
}
var m map[string]interface{}
if err := json.Unmarshal(b, &m); err != nil {
return ""
}
if id, ok := m["id"].(string); ok {
return id
}
return ""
}
func timePtr(t time.Time) *time.Time {
return &t
}
func toActivityLogResponse(log domain.ActivityLog) dto.ActivityLogResponse {
resp := dto.ActivityLogResponse{
ID: log.ID,
TableName: log.TargetTable,
RecordID: log.RecordID,
Action: log.Action,
OldData: log.OldData,
NewData: log.NewData,
CascadeData: log.CascadeData,
UserID: log.UserID,
UserName: log.UserName,
MerchantID: log.MerchantID,
}
if log.CreatedOn != nil {
s := log.CreatedOn.Format("2006-01-02 15:04:05")
resp.CreatedOn = &s
}
if log.CascadeData != nil {
var groups []CascadeGroup
if err := json.Unmarshal([]byte(*log.CascadeData), &groups); err == nil {
count := 0
for _, g := range groups {
count += len(g.Records)
}
resp.CascadeCount = count
}
}
return resp
}
func cleanRecordMap(record map[string]interface{}) map[string]interface{} {
cleaned := make(map[string]interface{})
for k, v := range record {
if v == nil {
continue
}
switch v.(type) {
case map[string]interface{}, []interface{}:
continue
}
cleaned[k] = v
}
return cleaned
}

View File

@ -44,17 +44,20 @@ func (u *AuthUsecase) Login(email string, password string) (token string, err er
if err != nil { if err != nil {
return "", err return "", err
} }
// //
key := fmt.Sprintf("session_login:%v:*", v.ID) key := fmt.Sprintf("session_login:%v:*", v.ID)
iter := u.Helper.GetRedis("master").Scan(ctx, 0, key, 1000).Iterator() iter := u.Helper.GetRedis("master").Scan(ctx, 0, key, 1000).Iterator()
for iter.Next(ctx) { for iter.Next(ctx) {
keys := iter.Val() keys := iter.Val()
keys_ex := strings.Split(keys, ":") keys_ex := strings.Split(keys, ":")
//change to del if using single login
if keys_ex[1] == v.ID { if keys_ex[1] == v.ID {
cachedToken := keys_ex[2]
_, verifyErr := secretJwt.VerifyToken(cachedToken)
if verifyErr == nil {
u.Helper.GetRedis("master").Expire(ctx, keys, time.Duration(t)*time.Second) u.Helper.GetRedis("master").Expire(ctx, keys, time.Duration(t)*time.Second)
return keys_ex[2], nil return cachedToken, nil
}
u.Helper.GetRedis("master").Del(ctx, keys)
} }
} }

View File

@ -19,6 +19,7 @@ type BasicPricingUsecaseInterface interface {
Create(req dto.CreateBasicPricingRequest, createdBy string, merchantid *string) (dto.CreateBasicPricingResponse, error) Create(req dto.CreateBasicPricingRequest, createdBy string, merchantid *string) (dto.CreateBasicPricingResponse, error)
Update(id string, req dto.CreateBasicPricingRequest, updatedBy string) (dto.CreateBasicPricingResponse, error) Update(id string, req dto.CreateBasicPricingRequest, updatedBy string) (dto.CreateBasicPricingResponse, error)
Delete(id string) error Delete(id string) error
GetMatch(id string) *dto.CreateBasicPricingResponse
} }
type BasicPricingUsecase struct { type BasicPricingUsecase struct {
@ -77,7 +78,29 @@ func (u *BasicPricingUsecase) toResponse(pricing domain.BasicPricing, components
} }
return resp return resp
} }
func (u *BasicPricingUsecase) GetMatch(id string) *dto.CreateBasicPricingResponse {
var pricings domain.BasicPricing
db := u.Helper.GetDB("slave")
tx := db.Model(&pricings).Where("basic_pricing.id = ?", id)
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
tx = tx.Where("basic_pricing.merchant_id = ?", u.AuthUser.MerchantID)
}
if err := tx.First(&pricings).Error; err != nil {
return nil
}
pricingIDs := make([]string, 0)
pricingIDs = append(pricingIDs, pricings.ID)
componentsMap, err := u.loadComponents(pricingIDs)
if err != nil {
return nil
}
responses := u.toResponse(pricings, componentsMap[pricings.ID])
return &responses
}
func (u *BasicPricingUsecase) GetAll(filter dto.BasicPricingFilterAllRequest) ([]dto.CreateBasicPricingResponse, int64, error) { func (u *BasicPricingUsecase) GetAll(filter dto.BasicPricingFilterAllRequest) ([]dto.CreateBasicPricingResponse, int64, error) {
var pricings []domain.BasicPricing var pricings []domain.BasicPricing
var total int64 var total int64
@ -85,7 +108,7 @@ func (u *BasicPricingUsecase) GetAll(filter dto.BasicPricingFilterAllRequest) ([
db := u.Helper.GetDB("slave") db := u.Helper.GetDB("slave")
tx := db.Model(&pricings) tx := db.Model(&pricings)
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
tx = tx.Where(db.Where("merchant_id IS NULL").Or("merchant_id=?", u.AuthUser.MerchantID)) tx = tx.Where("merchant_id=?", u.AuthUser.MerchantID)
} }
v := reflect.ValueOf(filter) v := reflect.ValueOf(filter)
t := reflect.TypeOf(filter) t := reflect.TypeOf(filter)
@ -129,7 +152,7 @@ func (u *BasicPricingUsecase) GetList(req dto.DataTableRequest) (dto.DataTableRe
Joins("LEFT JOIN city AS cd ON basic_pricing.city_destination_id=cd.id"). Joins("LEFT JOIN city AS cd ON basic_pricing.city_destination_id=cd.id").
Joins("LEFT JOIN merchant ON basic_pricing.merchant_id=merchant.id") Joins("LEFT JOIN merchant ON basic_pricing.merchant_id=merchant.id")
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
tx = tx.Where("basic_pricing.merchant_id IS NULL OR basic_pricing.merchant_id=?", u.AuthUser.MerchantID) tx = tx.Where("basic_pricing.merchant_id=?", u.AuthUser.MerchantID)
} }
dt := datatable.NewDatatable(tx, req) dt := datatable.NewDatatable(tx, req)
coldef := make([]dto.DataTableColDef, 0) coldef := make([]dto.DataTableColDef, 0)
@ -209,12 +232,15 @@ func (u *BasicPricingUsecase) Create(req dto.CreateBasicPricingRequest, createdB
if err != nil { if err != nil {
return dto.CreateBasicPricingResponse{}, err return dto.CreateBasicPricingResponse{}, err
} }
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogCreate("basic_pricing", pricing, merchantid)
return u.toResponse(pricing, savedComponents), nil return u.toResponse(pricing, savedComponents), nil
} }
func (u *BasicPricingUsecase) Update(id string, req dto.CreateBasicPricingRequest, updatedBy string) (dto.CreateBasicPricingResponse, error) { func (u *BasicPricingUsecase) Update(id string, req dto.CreateBasicPricingRequest, updatedBy string) (dto.CreateBasicPricingResponse, error) {
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
var pricing domain.BasicPricing var pricing domain.BasicPricing
var oldPricing domain.BasicPricing
var savedComponents []domain.BasicPricingComponent var savedComponents []domain.BasicPricingComponent
err := db.Transaction(func(tx *gorm.DB) error { err := db.Transaction(func(tx *gorm.DB) error {
@ -225,6 +251,7 @@ func (u *BasicPricingUsecase) Update(id string, req dto.CreateBasicPricingReques
if err := q.Where("id = ?", id).First(&pricing).Error; err != nil { if err := q.Where("id = ?", id).First(&pricing).Error; err != nil {
return err return err
} }
oldPricing = pricing
now := time.Now() now := time.Now()
pricing.ProvinceSourceID = strPtr(req.ProvinceSourceID) pricing.ProvinceSourceID = strPtr(req.ProvinceSourceID)
pricing.CitySourceID = strPtr(req.CitySourceID) pricing.CitySourceID = strPtr(req.CitySourceID)
@ -267,6 +294,12 @@ func (u *BasicPricingUsecase) Update(id string, req dto.CreateBasicPricingReques
if err != nil { if err != nil {
return dto.CreateBasicPricingResponse{}, err return dto.CreateBasicPricingResponse{}, err
} }
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogUpdate("basic_pricing", oldPricing, pricing, merchantID)
return u.toResponse(pricing, savedComponents), nil return u.toResponse(pricing, savedComponents), nil
} }
@ -281,6 +314,12 @@ func (u *BasicPricingUsecase) Delete(id string) error {
if err := q.Where("id = ?", id).First(&pricing).Error; err != nil { if err := q.Where("id = ?", id).First(&pricing).Error; err != nil {
return err return err
} }
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogDelete("basic_pricing", id, pricing, merchantID)
if err := tx.Where("basic_pricing_id = ?", id).Delete(&domain.BasicPricingComponent{}).Error; err != nil { if err := tx.Where("basic_pricing_id = ?", id).Delete(&domain.BasicPricingComponent{}).Error; err != nil {
return err return err
} }

View File

@ -125,11 +125,14 @@ func (u *CityUsecase) Create(name string, provinceID string, port string, create
if err != nil { if err != nil {
return domain.City{}, err return domain.City{}, err
} }
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogCreate("city", city, merchantid)
return city, nil return city, nil
} }
func (u *CityUsecase) Update(id string, name string, provinceID string, port string, updatedBy string) (domain.City, error) { func (u *CityUsecase) Update(id string, name string, provinceID string, port string, updatedBy string) (domain.City, error) {
var city domain.City var city domain.City
var oldCity domain.City
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
err := db.Transaction(func(tx *gorm.DB) error { err := db.Transaction(func(tx *gorm.DB) error {
q := tx q := tx
@ -139,6 +142,7 @@ func (u *CityUsecase) Update(id string, name string, provinceID string, port str
if err := q.Where("id = ?", id).First(&city).Error; err != nil { if err := q.Where("id = ?", id).First(&city).Error; err != nil {
return err return err
} }
oldCity = city
now := time.Now() now := time.Now()
city.Name = &name city.Name = &name
city.ProvinceID = &provinceID city.ProvinceID = &provinceID
@ -150,14 +154,31 @@ func (u *CityUsecase) Update(id string, name string, provinceID string, port str
if err != nil { if err != nil {
return domain.City{}, err return domain.City{}, err
} }
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogUpdate("city", oldCity, city, merchantID)
return city, nil return city, nil
} }
func (u *CityUsecase) Delete(id string) error { func (u *CityUsecase) Delete(id string) error {
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
var city domain.City
q := db
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
db = db.Where("merchant_id=?", u.AuthUser.MerchantID) q = db.Where("merchant_id=?", u.AuthUser.MerchantID)
} }
if err := q.Where("id = ?", id).First(&city).Error; err != nil {
return err
}
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogDelete("city", id, city, merchantID)
if err := db.Delete(&domain.City{}, "id = ?", id).Error; err != nil { if err := db.Delete(&domain.City{}, "id = ?", id).Error; err != nil {
return err return err
} }

View File

@ -148,11 +148,14 @@ func (u *CostComponentUsecase) Create(name string, code string, value string, co
if err != nil { if err != nil {
return domain.CostComponent{}, err return domain.CostComponent{}, err
} }
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogCreate("cost_component", component, merchantid)
return component, nil return component, nil
} }
func (u *CostComponentUsecase) Update(id string, name string, code string, value string, componentType int32, unitType int32, updatedBy string) (domain.CostComponent, error) { func (u *CostComponentUsecase) Update(id string, name string, code string, value string, componentType int32, unitType int32, updatedBy string) (domain.CostComponent, error) {
var component domain.CostComponent var component domain.CostComponent
var oldComponent domain.CostComponent
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
err := db.Transaction(func(tx *gorm.DB) error { err := db.Transaction(func(tx *gorm.DB) error {
q := tx q := tx
@ -162,6 +165,7 @@ func (u *CostComponentUsecase) Update(id string, name string, code string, value
if err := q.Where("id = ?", id).First(&component).Error; err != nil { if err := q.Where("id = ?", id).First(&component).Error; err != nil {
return err return err
} }
oldComponent = component
now := time.Now() now := time.Now()
component.Name = &name component.Name = &name
component.Code = &code component.Code = &code
@ -175,14 +179,31 @@ func (u *CostComponentUsecase) Update(id string, name string, code string, value
if err != nil { if err != nil {
return domain.CostComponent{}, err return domain.CostComponent{}, err
} }
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogUpdate("cost_component", oldComponent, component, merchantID)
return component, nil return component, nil
} }
func (u *CostComponentUsecase) Delete(id string) error { func (u *CostComponentUsecase) Delete(id string) error {
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
var component domain.CostComponent
q := db
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
db = db.Where("merchant_id=?", u.AuthUser.MerchantID) q = db.Where("merchant_id=?", u.AuthUser.MerchantID)
} }
if err := q.Where("id = ?", id).First(&component).Error; err != nil {
return err
}
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogDelete("cost_component", id, component, merchantID)
if err := db.Delete(&domain.CostComponent{}, "id = ?", id).Error; err != nil { if err := db.Delete(&domain.CostComponent{}, "id = ?", id).Error; err != nil {
return err return err
} }

View File

@ -134,11 +134,14 @@ func (u *DistrictUsecase) Create(name string, provinceID string, cityID string,
if err != nil { if err != nil {
return domain.District{}, err return domain.District{}, err
} }
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogCreate("district", district, merchantid)
return district, nil return district, nil
} }
func (u *DistrictUsecase) Update(id string, name string, provinceID string, cityID string, updatedBy string) (domain.District, error) { func (u *DistrictUsecase) Update(id string, name string, provinceID string, cityID string, updatedBy string) (domain.District, error) {
var district domain.District var district domain.District
var oldDistrict domain.District
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
err := db.Transaction(func(tx *gorm.DB) error { err := db.Transaction(func(tx *gorm.DB) error {
q := tx q := tx
@ -148,6 +151,7 @@ func (u *DistrictUsecase) Update(id string, name string, provinceID string, city
if err := q.Where("id = ?", id).First(&district).Error; err != nil { if err := q.Where("id = ?", id).First(&district).Error; err != nil {
return err return err
} }
oldDistrict = district
now := time.Now() now := time.Now()
district.Name = &name district.Name = &name
district.ProvinceID = &provinceID district.ProvinceID = &provinceID
@ -159,14 +163,31 @@ func (u *DistrictUsecase) Update(id string, name string, provinceID string, city
if err != nil { if err != nil {
return domain.District{}, err return domain.District{}, err
} }
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogUpdate("district", oldDistrict, district, merchantID)
return district, nil return district, nil
} }
func (u *DistrictUsecase) Delete(id string) error { func (u *DistrictUsecase) Delete(id string) error {
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
var district domain.District
q := db
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
db = db.Where("merchant_id=?", u.AuthUser.MerchantID) q = db.Where("merchant_id=?", u.AuthUser.MerchantID)
} }
if err := q.Where("id = ?", id).First(&district).Error; err != nil {
return err
}
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogDelete("district", id, district, merchantID)
if err := db.Delete(&domain.District{}, "id = ?", id).Error; err != nil { if err := db.Delete(&domain.District{}, "id = ?", id).Error; err != nil {
return err return err
} }

175
internal/usecases/moda.go Normal file
View File

@ -0,0 +1,175 @@
package usecases
import (
"cargo-erp-backend/internal/domain"
"cargo-erp-backend/internal/handlers/dto"
"cargo-erp-backend/pkg/datatable"
"cargo-erp-backend/pkg/helpers"
"fmt"
"reflect"
"time"
"gorm.io/gorm"
)
type ModaUsecaseInterface interface {
GetAll(filter dto.ModaFilterAllRequest) ([]domain.Moda, int64, error)
GetList(dto.DataTableRequest) (dto.DataTableResponse, error)
GetByID(id string) (domain.Moda, error)
Create(name string, createdBy string, merchantid *string) (domain.Moda, error)
Update(id string, name string, updatedBy string) (domain.Moda, error)
Delete(id string) error
}
type ModaUsecase struct {
Helper helpers.HelperInterface
AuthUser domain.User
}
func NewModaUsecase(helper helpers.HelperInterface, authUser domain.User) ModaUsecaseInterface {
return &ModaUsecase{Helper: helper, AuthUser: authUser}
}
func (u *ModaUsecase) GetAll(filter dto.ModaFilterAllRequest) ([]domain.Moda, int64, error) {
var items []domain.Moda
var total int64
db := u.Helper.GetDB("slave")
tx := db.Model(&items)
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
tx = tx.Where(db.Where("merchant_id IS NULL").Or("merchant_id=?", u.AuthUser.MerchantID))
}
v := reflect.ValueOf(filter)
t := reflect.TypeOf(filter)
for i := 0; i < v.NumField(); i++ {
if v.Field(i).String() != "" {
tagKey := t.Field(i).Tag.Get("form")
tx = tx.Where(fmt.Sprintf("%v=?", tagKey), v.Field(i))
}
}
tx.Model(&domain.Moda{}).Count(&total)
tx.Order("name ASC")
if err := tx.Find(&items).Error; err != nil {
return nil, 0, err
}
return items, total, nil
}
func (u *ModaUsecase) GetList(req dto.DataTableRequest) (dto.DataTableResponse, error) {
var response dto.DataTableResponse
db := u.Helper.GetDB("slave")
tx := db.Table("moda").
Joins("LEFT JOIN merchant ON moda.merchant_id=merchant.id")
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
tx = tx.Where("moda.merchant_id IS NULL OR moda.merchant_id=?", u.AuthUser.MerchantID)
}
dt := datatable.NewDatatable(tx, req)
coldef := make([]dto.DataTableColDef, 0)
coldef = append(coldef, dto.DataTableColDef{
Field: "moda.name",
Alias: "name",
})
coldef = append(coldef, dto.DataTableColDef{
Field: "merchant.id",
Alias: "merchant_id",
})
coldef = append(coldef, dto.DataTableColDef{
Field: "moda.id",
Alias: "id",
})
response = dt.Render(coldef)
return response, nil
}
func (u *ModaUsecase) GetByID(id string) (domain.Moda, error) {
var item domain.Moda
db := u.Helper.GetDB("slave")
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
db = db.Where("merchant_id IS NULL OR merchant_id=?", u.AuthUser.MerchantID)
}
if err := db.Where("id = ?", id).First(&item).Error; err != nil {
return domain.Moda{}, err
}
return item, nil
}
func (u *ModaUsecase) Create(name string, createdBy string, merchantid *string) (domain.Moda, error) {
now := time.Now()
db := u.Helper.GetDB("master")
var item domain.Moda
err := db.Transaction(func(tx *gorm.DB) error {
item = domain.Moda{
Name: &name,
MerchantID: merchantid,
CreatedBy: &createdBy,
CreatedOn: &now,
UpdatedBy: &createdBy,
UpdatedOn: &now,
}
return tx.Create(&item).Error
})
if err != nil {
return domain.Moda{}, err
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogCreate("moda", item, merchantid)
return item, nil
}
func (u *ModaUsecase) Update(id string, name string, updatedBy string) (domain.Moda, error) {
var item domain.Moda
var oldItem domain.Moda
db := u.Helper.GetDB("master")
err := db.Transaction(func(tx *gorm.DB) error {
q := tx
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
q = tx.Where("merchant_id=?", u.AuthUser.MerchantID)
}
if err := q.Where("id = ?", id).First(&item).Error; err != nil {
return err
}
oldItem = item
now := time.Now()
item.Name = &name
item.UpdatedBy = &updatedBy
item.UpdatedOn = &now
return tx.Save(&item).Error
})
if err != nil {
return domain.Moda{}, err
}
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogUpdate("moda", oldItem, item, merchantID)
return item, nil
}
func (u *ModaUsecase) Delete(id string) error {
db := u.Helper.GetDB("master")
var item domain.Moda
q := db
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
q = db.Where("merchant_id=?", u.AuthUser.MerchantID)
}
if err := q.Where("id = ?", id).First(&item).Error; err != nil {
return err
}
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogDelete("moda", id, item, merchantID)
if err := db.Delete(&domain.Moda{}, "id = ?", id).Error; err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,175 @@
package usecases
import (
"cargo-erp-backend/internal/domain"
"cargo-erp-backend/internal/handlers/dto"
"cargo-erp-backend/pkg/datatable"
"cargo-erp-backend/pkg/helpers"
"fmt"
"reflect"
"time"
"gorm.io/gorm"
)
type PackageTypeUsecaseInterface interface {
GetAll(filter dto.PackageTypeFilterAllRequest) ([]domain.PackageType, int64, error)
GetList(dto.DataTableRequest) (dto.DataTableResponse, error)
GetByID(id string) (domain.PackageType, error)
Create(name string, createdBy string, merchantid *string) (domain.PackageType, error)
Update(id string, name string, updatedBy string) (domain.PackageType, error)
Delete(id string) error
}
type PackageTypeUsecase struct {
Helper helpers.HelperInterface
AuthUser domain.User
}
func NewPackageTypeUsecase(helper helpers.HelperInterface, authUser domain.User) PackageTypeUsecaseInterface {
return &PackageTypeUsecase{Helper: helper, AuthUser: authUser}
}
func (u *PackageTypeUsecase) GetAll(filter dto.PackageTypeFilterAllRequest) ([]domain.PackageType, int64, error) {
var items []domain.PackageType
var total int64
db := u.Helper.GetDB("slave")
tx := db.Model(&items)
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
tx = tx.Where(db.Where("merchant_id IS NULL").Or("merchant_id=?", u.AuthUser.MerchantID))
}
v := reflect.ValueOf(filter)
t := reflect.TypeOf(filter)
for i := 0; i < v.NumField(); i++ {
if v.Field(i).String() != "" {
tagKey := t.Field(i).Tag.Get("form")
tx = tx.Where(fmt.Sprintf("%v=?", tagKey), v.Field(i))
}
}
tx.Model(&domain.PackageType{}).Count(&total)
tx.Order("name ASC")
if err := tx.Find(&items).Error; err != nil {
return nil, 0, err
}
return items, total, nil
}
func (u *PackageTypeUsecase) GetList(req dto.DataTableRequest) (dto.DataTableResponse, error) {
var response dto.DataTableResponse
db := u.Helper.GetDB("slave")
tx := db.Table("package_type").
Joins("LEFT JOIN merchant ON package_type.merchant_id=merchant.id")
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
tx = tx.Where("package_type.merchant_id IS NULL OR package_type.merchant_id=?", u.AuthUser.MerchantID)
}
dt := datatable.NewDatatable(tx, req)
coldef := make([]dto.DataTableColDef, 0)
coldef = append(coldef, dto.DataTableColDef{
Field: "package_type.name",
Alias: "name",
})
coldef = append(coldef, dto.DataTableColDef{
Field: "merchant.id",
Alias: "merchant_id",
})
coldef = append(coldef, dto.DataTableColDef{
Field: "package_type.id",
Alias: "id",
})
response = dt.Render(coldef)
return response, nil
}
func (u *PackageTypeUsecase) GetByID(id string) (domain.PackageType, error) {
var item domain.PackageType
db := u.Helper.GetDB("slave")
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
db = db.Where("merchant_id IS NULL OR merchant_id=?", u.AuthUser.MerchantID)
}
if err := db.Where("id = ?", id).First(&item).Error; err != nil {
return domain.PackageType{}, err
}
return item, nil
}
func (u *PackageTypeUsecase) Create(name string, createdBy string, merchantid *string) (domain.PackageType, error) {
now := time.Now()
db := u.Helper.GetDB("master")
var item domain.PackageType
err := db.Transaction(func(tx *gorm.DB) error {
item = domain.PackageType{
Name: &name,
MerchantID: merchantid,
CreatedBy: &createdBy,
CreatedOn: &now,
UpdatedBy: &createdBy,
UpdatedOn: &now,
}
return tx.Create(&item).Error
})
if err != nil {
return domain.PackageType{}, err
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogCreate("package_type", item, merchantid)
return item, nil
}
func (u *PackageTypeUsecase) Update(id string, name string, updatedBy string) (domain.PackageType, error) {
var item domain.PackageType
var oldItem domain.PackageType
db := u.Helper.GetDB("master")
err := db.Transaction(func(tx *gorm.DB) error {
q := tx
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
q = tx.Where("merchant_id=?", u.AuthUser.MerchantID)
}
if err := q.Where("id = ?", id).First(&item).Error; err != nil {
return err
}
oldItem = item
now := time.Now()
item.Name = &name
item.UpdatedBy = &updatedBy
item.UpdatedOn = &now
return tx.Save(&item).Error
})
if err != nil {
return domain.PackageType{}, err
}
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogUpdate("package_type", oldItem, item, merchantID)
return item, nil
}
func (u *PackageTypeUsecase) Delete(id string) error {
db := u.Helper.GetDB("master")
var item domain.PackageType
q := db
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
q = db.Where("merchant_id=?", u.AuthUser.MerchantID)
}
if err := q.Where("id = ?", id).First(&item).Error; err != nil {
return err
}
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogDelete("package_type", id, item, merchantID)
if err := db.Delete(&domain.PackageType{}, "id = ?", id).Error; err != nil {
return err
}
return nil
}

View File

@ -147,18 +147,21 @@ func (u *PostalCodeUsecase) Create(postalCode string, provinceID string, cityID
CreatedBy: &createdBy, CreatedBy: &createdBy,
CreatedOn: &now, CreatedOn: &now,
UpdatedBy: &createdBy, UpdatedBy: &createdBy,
UpdatedOn: &now, UpdatedOn: strPtr(now.Format("2006-01-02 15:04:05.999999-07:00")),
} }
return tx.Create(&pc).Error return tx.Create(&pc).Error
}) })
if err != nil { if err != nil {
return domain.PostalCode{}, err return domain.PostalCode{}, err
} }
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogCreate("postal_code", pc, merchantid)
return pc, nil return pc, nil
} }
func (u *PostalCodeUsecase) Update(id string, postalCode string, provinceID string, cityID string, districtID string, subdistrictID string, updatedBy string) (domain.PostalCode, error) { func (u *PostalCodeUsecase) Update(id string, postalCode string, provinceID string, cityID string, districtID string, subdistrictID string, updatedBy string) (domain.PostalCode, error) {
var pc domain.PostalCode var pc domain.PostalCode
var oldPC domain.PostalCode
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
err := db.Transaction(func(tx *gorm.DB) error { err := db.Transaction(func(tx *gorm.DB) error {
q := tx q := tx
@ -168,6 +171,7 @@ func (u *PostalCodeUsecase) Update(id string, postalCode string, provinceID stri
if err := q.Where("id = ?", id).First(&pc).Error; err != nil { if err := q.Where("id = ?", id).First(&pc).Error; err != nil {
return err return err
} }
oldPC = pc
now := time.Now() now := time.Now()
pc.PostalCode = &postalCode pc.PostalCode = &postalCode
pc.ProvinceID = &provinceID pc.ProvinceID = &provinceID
@ -175,20 +179,37 @@ func (u *PostalCodeUsecase) Update(id string, postalCode string, provinceID stri
pc.DistrictID = &districtID pc.DistrictID = &districtID
pc.SubdistrictID = &subdistrictID pc.SubdistrictID = &subdistrictID
pc.UpdatedBy = &updatedBy pc.UpdatedBy = &updatedBy
pc.UpdatedOn = &now pc.UpdatedOn = strPtr(now.Format("2006-01-02 15:04:05.999999-07:00"))
return tx.Save(&pc).Error return tx.Save(&pc).Error
}) })
if err != nil { if err != nil {
return domain.PostalCode{}, err return domain.PostalCode{}, err
} }
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogUpdate("postal_code", oldPC, pc, merchantID)
return pc, nil return pc, nil
} }
func (u *PostalCodeUsecase) Delete(id string) error { func (u *PostalCodeUsecase) Delete(id string) error {
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
var pc domain.PostalCode
q := db
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
db = db.Where("merchant_id=?", u.AuthUser.MerchantID) q = db.Where("merchant_id=?", u.AuthUser.MerchantID)
} }
if err := q.Where("id = ?", id).First(&pc).Error; err != nil {
return err
}
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogDelete("postal_code", id, pc, merchantID)
if err := db.Delete(&domain.PostalCode{}, "id = ?", id).Error; err != nil { if err := db.Delete(&domain.PostalCode{}, "id = ?", id).Error; err != nil {
return err return err
} }

View File

@ -121,11 +121,14 @@ func (u *ProvinceUsecase) Create(name string, createdBy string, merchantid *stri
if err != nil { if err != nil {
return domain.Province{}, err return domain.Province{}, err
} }
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogCreate("province", province, merchantid)
return province, nil return province, nil
} }
func (u *ProvinceUsecase) Update(id string, name string, updatedBy string) (domain.Province, error) { func (u *ProvinceUsecase) Update(id string, name string, updatedBy string) (domain.Province, error) {
var province domain.Province var province domain.Province
var oldProvince domain.Province
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
err := db.Transaction(func(tx *gorm.DB) error { err := db.Transaction(func(tx *gorm.DB) error {
q := tx q := tx
@ -135,6 +138,7 @@ func (u *ProvinceUsecase) Update(id string, name string, updatedBy string) (doma
if err := q.Where("id = ?", id).First(&province).Error; err != nil { if err := q.Where("id = ?", id).First(&province).Error; err != nil {
return err return err
} }
oldProvince = province
now := time.Now() now := time.Now()
province.Name = &name province.Name = &name
province.UpdatedBy = &updatedBy province.UpdatedBy = &updatedBy
@ -144,14 +148,34 @@ func (u *ProvinceUsecase) Update(id string, name string, updatedBy string) (doma
if err != nil { if err != nil {
return domain.Province{}, err return domain.Province{}, err
} }
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogUpdate("province", oldProvince, province, merchantID)
return province, nil return province, nil
} }
func (u *ProvinceUsecase) Delete(id string) error { func (u *ProvinceUsecase) Delete(id string) error {
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
var province domain.Province
q := db
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
db = db.Where("merchant_id=?", u.AuthUser.MerchantID) q = db.Where("merchant_id=?", u.AuthUser.MerchantID)
} }
if err := q.Where("id = ?", id).First(&province).Error; err != nil {
return err
}
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogDelete("province", id, province, merchantID)
if err := db.Delete(&domain.Province{}, "id = ?", id).Error; err != nil { if err := db.Delete(&domain.Province{}, "id = ?", id).Error; err != nil {
return err return err
} }

View File

@ -144,11 +144,14 @@ func (u *SubdistrictUsecase) Create(name string, provinceID string, cityID strin
if err != nil { if err != nil {
return domain.Subdistrict{}, err return domain.Subdistrict{}, err
} }
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogCreate("subdistrict", subdistrict, merchantid)
return subdistrict, nil return subdistrict, nil
} }
func (u *SubdistrictUsecase) Update(id string, name string, provinceID string, cityID string, districtID string, updatedBy string) (domain.Subdistrict, error) { func (u *SubdistrictUsecase) Update(id string, name string, provinceID string, cityID string, districtID string, updatedBy string) (domain.Subdistrict, error) {
var subdistrict domain.Subdistrict var subdistrict domain.Subdistrict
var oldSubdistrict domain.Subdistrict
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
err := db.Transaction(func(tx *gorm.DB) error { err := db.Transaction(func(tx *gorm.DB) error {
q := tx q := tx
@ -158,6 +161,7 @@ func (u *SubdistrictUsecase) Update(id string, name string, provinceID string, c
if err := q.Where("id = ?", id).First(&subdistrict).Error; err != nil { if err := q.Where("id = ?", id).First(&subdistrict).Error; err != nil {
return err return err
} }
oldSubdistrict = subdistrict
now := time.Now() now := time.Now()
subdistrict.Name = &name subdistrict.Name = &name
subdistrict.ProvinceID = &provinceID subdistrict.ProvinceID = &provinceID
@ -170,14 +174,31 @@ func (u *SubdistrictUsecase) Update(id string, name string, provinceID string, c
if err != nil { if err != nil {
return domain.Subdistrict{}, err return domain.Subdistrict{}, err
} }
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogUpdate("subdistrict", oldSubdistrict, subdistrict, merchantID)
return subdistrict, nil return subdistrict, nil
} }
func (u *SubdistrictUsecase) Delete(id string) error { func (u *SubdistrictUsecase) Delete(id string) error {
db := u.Helper.GetDB("master") db := u.Helper.GetDB("master")
var subdistrict domain.Subdistrict
q := db
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
db = db.Where("merchant_id=?", u.AuthUser.MerchantID) q = db.Where("merchant_id=?", u.AuthUser.MerchantID)
} }
if err := q.Where("id = ?", id).First(&subdistrict).Error; err != nil {
return err
}
var merchantID *string
if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
merchantID = u.AuthUser.MerchantID
}
logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
logUsecase.LogDelete("subdistrict", id, subdistrict, merchantID)
if err := db.Delete(&domain.Subdistrict{}, "id = ?", id).Error; err != nil { if err := db.Delete(&domain.Subdistrict{}, "id = ?", id).Error; err != nil {
return err return err
} }

View File

@ -0,0 +1,22 @@
-- Activity Log table for user activity tracking and revert capability
-- Run this migration before deploying the new code
CREATE TABLE IF NOT EXISTS activity_log (
id character(26) PRIMARY KEY DEFAULT generate_ulid(),
table_name character varying(100) NOT NULL,
record_id character(26) NOT NULL,
action character varying(20) NOT NULL,
old_data jsonb,
new_data jsonb,
cascade_data jsonb,
user_id character(26) NOT NULL,
user_name character varying(255) NOT NULL,
merchant_id character(26),
created_on timestamp without time zone DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_activity_log_table_name ON activity_log(table_name);
CREATE INDEX IF NOT EXISTS idx_activity_log_record_id ON activity_log(record_id);
CREATE INDEX IF NOT EXISTS idx_activity_log_user_id ON activity_log(user_id);
CREATE INDEX IF NOT EXISTS idx_activity_log_created_on ON activity_log(created_on);
CREATE INDEX IF NOT EXISTS idx_activity_log_action ON activity_log(action);

View File

@ -1280,3 +1280,8 @@
{"level":"info","ts":"2026-08-25T16:16:54.090+0700","caller":"database/database.go:63","msg":"DB Master Connected"} {"level":"info","ts":"2026-08-25T16:16:54.090+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-25T16:16:54.090+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"} {"level":"info","ts":"2026-08-25T16:16:54.090+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-25T16:16:54.250+0700","caller":"database/database.go:86","msg":"DB Connection Done"} {"level":"info","ts":"2026-08-25T16:16:54.250+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-09-04T21:21:20.045+0700","caller":"database/database.go:55","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-09-04T21:21:20.135+0700","caller":"database/database.go:68","msg":"DB Master Connected"}
{"level":"info","ts":"2026-09-04T21:21:20.135+0700","caller":"database/database.go:81","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-09-04T21:21:20.232+0700","caller":"database/database.go:94","msg":"DB Connection Done"}
{"level":"info","ts":"2026-09-04T21:21:20.362+0700","caller":"handlers/handlers.go:136","msg":"Starting Apps"}

View File

@ -15418,3 +15418,9 @@
{"level":"info","ts":"2026-08-25T16:17:15.380+0700","caller":"logger/database.go:40","msg":"generate query file: /Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/cmd/api/internal/domain/query_utils/subdistrict.gen.go"} {"level":"info","ts":"2026-08-25T16:17:15.380+0700","caller":"logger/database.go:40","msg":"generate query file: /Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/cmd/api/internal/domain/query_utils/subdistrict.gen.go"}
{"level":"info","ts":"2026-08-25T16:17:15.392+0700","caller":"logger/database.go:40","msg":"generate query file: /Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/cmd/api/internal/domain/query_utils/gen.go"} {"level":"info","ts":"2026-08-25T16:17:15.392+0700","caller":"logger/database.go:40","msg":"generate query file: /Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/cmd/api/internal/domain/query_utils/gen.go"}
{"level":"info","ts":"2026-08-25T16:17:15.392+0700","caller":"logger/database.go:40","msg":"Generate code done."} {"level":"info","ts":"2026-08-25T16:17:15.392+0700","caller":"logger/database.go:40","msg":"Generate code done."}
{"level":"info","ts":"2026-09-04T21:21:20.232+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-09-04T21:21:20.234+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-09-04T21:21:20.293+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-09-04T21:21:20.293+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-09-04T21:21:20.293+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-09-04T21:21:20.361+0700","caller":"database/redis.go:62","msg":"Connected"}