Merge branch 'dev' of http://39.117.244.52:3000/kjs/ERP-node into ipadress
This commit is contained in:
@@ -8,7 +8,17 @@ datasource db {
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// 테이블 타입관리 관련 모델은 이미 정의되어 있음 (line 11, 717)
|
||||
model dynamic_form_data {
|
||||
id Int @id @default(autoincrement())
|
||||
screen_id Int
|
||||
table_name String @db.VarChar(100)
|
||||
form_data Json
|
||||
created_at DateTime? @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime? @default(now()) @updatedAt @db.Timestamp(6)
|
||||
created_by String @db.VarChar(50)
|
||||
updated_by String @db.VarChar(50)
|
||||
company_code String @db.VarChar(20)
|
||||
}
|
||||
|
||||
model admin_supply_mng {
|
||||
objid Decimal @id @default(0) @db.Decimal
|
||||
@@ -74,15 +84,12 @@ model approval {
|
||||
@@ignore
|
||||
}
|
||||
|
||||
|
||||
|
||||
model approval_kind {
|
||||
target_type String @db.VarChar
|
||||
target_type String @id @db.VarChar
|
||||
target_name String? @db.VarChar
|
||||
regdate DateTime? @db.Timestamp(6)
|
||||
status String? @db.VarChar
|
||||
|
||||
@@id([target_type])
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
@@ -135,8 +142,6 @@ model arrival_plan {
|
||||
@@index([part_objid])
|
||||
}
|
||||
|
||||
|
||||
|
||||
model as_mng {
|
||||
objid Int @id
|
||||
as_no String? @db.VarChar
|
||||
@@ -247,20 +252,13 @@ model attach_file_info {
|
||||
@@ignore
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
model authority_master {
|
||||
objid Decimal @id @default(0) @db.Decimal
|
||||
auth_name String? @default("NULL::character varying") @db.VarChar(256)
|
||||
auth_code String? @default("NULL::character varying") @db.VarChar(64)
|
||||
writer String? @default("NULL::character varying") @db.VarChar(32)
|
||||
regdate DateTime? @db.Timestamp(6)
|
||||
status String? @default("NULL::character varying") @db.VarChar(32)
|
||||
|
||||
// 관계 설정
|
||||
objid Decimal @id @default(0) @db.Decimal
|
||||
auth_name String? @default("NULL::character varying") @db.VarChar(256)
|
||||
auth_code String? @default("NULL::character varying") @db.VarChar(64)
|
||||
writer String? @default("NULL::character varying") @db.VarChar(32)
|
||||
regdate DateTime? @db.Timestamp(6)
|
||||
status String? @default("NULL::character varying") @db.VarChar(32)
|
||||
sub_users authority_sub_user[]
|
||||
}
|
||||
|
||||
@@ -277,15 +275,12 @@ model authority_master_history {
|
||||
}
|
||||
|
||||
model authority_sub_user {
|
||||
objid Decimal @id @default(0) @db.Decimal
|
||||
master_objid Decimal? @db.Decimal
|
||||
user_id String? @default("NULL::character varying") @db.VarChar(64)
|
||||
writer String? @default("NULL::character varying") @db.VarChar(64)
|
||||
regdate DateTime? @db.Timestamp(6)
|
||||
|
||||
// 관계 설정
|
||||
objid Decimal @id @default(0) @db.Decimal
|
||||
master_objid Decimal? @db.Decimal
|
||||
user_id String? @default("NULL::character varying") @db.VarChar(64)
|
||||
writer String? @default("NULL::character varying") @db.VarChar(64)
|
||||
regdate DateTime? @db.Timestamp(6)
|
||||
authority_master authority_master? @relation(fields: [master_objid], references: [objid])
|
||||
user user_info? @relation(fields: [user_id], references: [user_id])
|
||||
|
||||
@@index([master_objid])
|
||||
@@index([user_id])
|
||||
@@ -343,13 +338,6 @@ model bom_part_qty {
|
||||
@@index([parent_objid])
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
model car_distribute_member {
|
||||
objid Decimal @db.Decimal
|
||||
car_objid Decimal @db.Decimal
|
||||
@@ -439,6 +427,7 @@ model column_labels {
|
||||
column_name String? @db.VarChar(100)
|
||||
column_label String? @db.VarChar(200)
|
||||
web_type String? @db.VarChar(50)
|
||||
input_type String? @default("direct") @db.VarChar(20) // direct, auto
|
||||
detail_settings String?
|
||||
description String?
|
||||
display_order Int? @default(0)
|
||||
@@ -530,9 +519,10 @@ model company_mng {
|
||||
regdate DateTime? @db.Timestamp(6)
|
||||
status String? @db.VarChar(32)
|
||||
|
||||
// 관계 설정
|
||||
// 관계 정의
|
||||
menus menu_info[]
|
||||
}
|
||||
|
||||
model contract_mgmt {
|
||||
objid String @id @db.VarChar
|
||||
category_cd String? @db.VarChar
|
||||
@@ -599,8 +589,6 @@ model contract_mgmt_option {
|
||||
@@index([option_objid])
|
||||
}
|
||||
|
||||
|
||||
|
||||
model counselingmgmt {
|
||||
objid String @id @db.VarChar
|
||||
reg_date String? @db.VarChar
|
||||
@@ -625,8 +613,6 @@ model counselingmgmt {
|
||||
parent_seq String? @db.VarChar
|
||||
}
|
||||
|
||||
|
||||
|
||||
model customer_service_mgmt {
|
||||
objid String @id @db.VarChar
|
||||
service_no String? @db.VarChar
|
||||
@@ -1181,8 +1167,6 @@ model inventory_mgmt_in {
|
||||
@@index([parent_objid])
|
||||
}
|
||||
|
||||
|
||||
|
||||
model inventory_mgmt_out {
|
||||
objid String @id @db.VarChar
|
||||
parent_objid String? @db.VarChar
|
||||
@@ -1262,6 +1246,7 @@ model invoice_mgmt {
|
||||
discount_percentage String? @db.VarChar
|
||||
inv_discount_price String? @db.VarChar
|
||||
}
|
||||
|
||||
model invoice_mgmt_part {
|
||||
objid String @id @db.VarChar
|
||||
invoice_objid String? @db.VarChar
|
||||
@@ -1506,7 +1491,7 @@ model menu_info {
|
||||
lang_key String? @db.VarChar(100)
|
||||
lang_key_desc String? @db.VarChar(100)
|
||||
|
||||
// 관계 설정 (나중에 활용 가능)
|
||||
// 관계 정의
|
||||
company company_mng? @relation(fields: [company_code], references: [company_code])
|
||||
|
||||
@@index([parent_obj_id])
|
||||
@@ -1537,8 +1522,6 @@ model mold_dev_request_info {
|
||||
status String? @db.VarChar(50)
|
||||
}
|
||||
|
||||
|
||||
|
||||
model multi_lang_key_master {
|
||||
key_id Int @id @default(autoincrement())
|
||||
company_code String @default("*") @db.VarChar(20)
|
||||
@@ -1801,7 +1784,6 @@ model order_spec_mng_history {
|
||||
@@ignore
|
||||
}
|
||||
|
||||
|
||||
model part_bom_qty {
|
||||
bom_report_objid Decimal @db.Decimal
|
||||
objid Decimal @id @db.Decimal
|
||||
@@ -1837,9 +1819,6 @@ model part_bom_report {
|
||||
@@index([unit_code, contract_objid], map: "part_bom_report_unit_code_idx")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
model part_distribution_list {
|
||||
part_objid Decimal @db.Decimal
|
||||
product_mgmt_objid String? @db.VarChar(100)
|
||||
@@ -2021,9 +2000,6 @@ model part_mng_history {
|
||||
@@ignore
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
model planning_issue {
|
||||
objid String @id @db.VarChar
|
||||
issue_no String @db.VarChar
|
||||
@@ -2221,10 +2197,6 @@ model pms_wbs_task {
|
||||
@@ignore
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
model pms_wbs_task_confirm {
|
||||
objid Decimal? @db.Decimal
|
||||
target_objid Decimal? @db.Decimal
|
||||
@@ -2349,6 +2321,7 @@ model product_group_mng {
|
||||
|
||||
@@ignore
|
||||
}
|
||||
|
||||
model product_kind_spec {
|
||||
objid String @db.VarChar
|
||||
objid_parent String @db.VarChar
|
||||
@@ -2798,13 +2771,6 @@ model purchase_order_master {
|
||||
@@index([multi_master_objid])
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
model purchase_order_master_241216 {
|
||||
objid String? @db.VarChar
|
||||
purchase_order_no String? @db.VarChar
|
||||
@@ -2920,13 +2886,6 @@ model purchase_order_part {
|
||||
@@index([purchase_order_master_objid])
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
model ratecal_mgmt {
|
||||
ratecal_mgmt_objid Decimal @default(0) @db.Decimal
|
||||
position String? @db.VarChar(100)
|
||||
@@ -2944,8 +2903,6 @@ model ratecal_mgmt {
|
||||
@@ignore
|
||||
}
|
||||
|
||||
|
||||
|
||||
model receive_history {
|
||||
objid String @id @db.VarChar
|
||||
part_objid String @db.VarChar
|
||||
@@ -3071,7 +3028,6 @@ model route {
|
||||
@@ignore
|
||||
}
|
||||
|
||||
|
||||
model sales_bom_part_qty {
|
||||
sales_bom_objid String @db.VarChar
|
||||
objid String @id @db.VarChar
|
||||
@@ -3106,7 +3062,6 @@ model sales_bom_part_qty {
|
||||
sales_part_code String? @db.VarChar
|
||||
}
|
||||
|
||||
|
||||
model sales_bom_report {
|
||||
objid String @id @default("") @db.VarChar
|
||||
parent_objid String? @unique(map: "sales_bom_report_parent_objid_idx") @db.VarChar
|
||||
@@ -3152,7 +3107,6 @@ model sales_bom_report_part {
|
||||
@@index([parent_objid])
|
||||
}
|
||||
|
||||
|
||||
model sales_bom_report_part_241218 {
|
||||
objid String? @db.VarChar
|
||||
parent_objid String? @db.VarChar
|
||||
@@ -3193,8 +3147,6 @@ model sales_long_delivery {
|
||||
price String? @default("0") @db.VarChar
|
||||
}
|
||||
|
||||
|
||||
|
||||
model sales_long_delivery_input {
|
||||
objid String @id(map: "sales_long_delivery_plan_pkey") @db.VarChar
|
||||
parent_objid String? @db.VarChar
|
||||
@@ -3205,8 +3157,6 @@ model sales_long_delivery_input {
|
||||
admin_editor String? @db.VarChar
|
||||
}
|
||||
|
||||
|
||||
|
||||
model sales_long_delivery_predict {
|
||||
objid String @id @db.VarChar
|
||||
parent_objid String? @db.VarChar
|
||||
@@ -3249,7 +3199,6 @@ model sales_request_master {
|
||||
remark String? @db.VarChar
|
||||
}
|
||||
|
||||
|
||||
model sales_request_part {
|
||||
objid String @id @db.VarChar
|
||||
sales_bom_qty_objid String? @db.VarChar
|
||||
@@ -3309,7 +3258,7 @@ model setup_wbs_task {
|
||||
}
|
||||
|
||||
model setup_wbs_task_standard {
|
||||
objid String? @unique(map: "setup_wbs_task_standard_objid_key") @db.VarChar
|
||||
objid String? @unique @db.VarChar
|
||||
contract_objid String? @db.VarChar
|
||||
parent_objid String? @db.VarChar
|
||||
task_category String? @db.VarChar
|
||||
@@ -3808,6 +3757,7 @@ model swpc120a_tbl {
|
||||
|
||||
@@id([imitemid, suvndcd], map: "pk_swpc120a_tbl")
|
||||
}
|
||||
|
||||
model swpc130a_tbl {
|
||||
imitemid String @db.VarChar(15)
|
||||
suvndcd String @db.VarChar(5)
|
||||
@@ -4566,6 +4516,7 @@ model swsb500a_tbl {
|
||||
edit_date DateTime? @db.Timestamp(6)
|
||||
edit_emp String? @db.VarChar(30)
|
||||
}
|
||||
|
||||
model swsb510a_tbl {
|
||||
frw_req_no String @id(map: "pk_swsb510a_tbl") @db.VarChar(10)
|
||||
req_date String? @db.VarChar(8)
|
||||
@@ -4874,15 +4825,6 @@ model table_labels {
|
||||
column_labels column_labels[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
model template_mng {
|
||||
objid Int @id
|
||||
template_code String? @db.VarChar
|
||||
@@ -4893,12 +4835,6 @@ model template_mng {
|
||||
template_code_detail String? @db.VarChar
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
model time_sheet {
|
||||
objid Decimal @default(0) @db.Decimal
|
||||
project_mgmt_objid Decimal? @db.Decimal
|
||||
@@ -4971,7 +4907,6 @@ model user_info {
|
||||
user_type String? @db.VarChar(1024)
|
||||
user_type_name String? @db.VarChar(1024)
|
||||
regdate DateTime? @db.Timestamp(6)
|
||||
data_type String? @db.VarChar(64)
|
||||
status String? @db.VarChar(32)
|
||||
end_date DateTime? @db.Timestamp(6)
|
||||
fax_no String? @db.VarChar
|
||||
@@ -4980,9 +4915,7 @@ model user_info {
|
||||
photo Bytes?
|
||||
locale String? @db.VarChar
|
||||
company_code String? @db.VarChar(50)
|
||||
|
||||
// 관계 설정
|
||||
authorities authority_sub_user[]
|
||||
data_type String? @db.VarChar(64)
|
||||
}
|
||||
|
||||
model user_info_history {
|
||||
@@ -5051,44 +4984,38 @@ model zz_230410_user_info {
|
||||
|
||||
@@ignore
|
||||
}
|
||||
// 화면관리 시스템 Prisma 스키마
|
||||
// 기존 schema.prisma에 추가할 모델들
|
||||
|
||||
model screen_definitions {
|
||||
screen_id Int @id @default(autoincrement())
|
||||
screen_name String @db.VarChar(100)
|
||||
screen_code String @unique @db.VarChar(50)
|
||||
table_name String @db.VarChar(100)
|
||||
company_code String @db.VarChar(50)
|
||||
description String? @db.Text
|
||||
is_active String @default("Y") @db.Char(1)
|
||||
created_date DateTime @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
updated_date DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_by String? @db.VarChar(50)
|
||||
|
||||
// 관계
|
||||
layouts screen_layouts[]
|
||||
screen_id Int @id @default(autoincrement())
|
||||
screen_name String @db.VarChar(100)
|
||||
screen_code String @unique @db.VarChar(50)
|
||||
table_name String @db.VarChar(100)
|
||||
company_code String @db.VarChar(50)
|
||||
description String?
|
||||
is_active String @default("Y") @db.Char(1)
|
||||
created_date DateTime @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
updated_date DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_by String? @db.VarChar(50)
|
||||
layouts screen_layouts[]
|
||||
menu_assignments screen_menu_assignments[]
|
||||
|
||||
@@index([company_code])
|
||||
}
|
||||
|
||||
model screen_layouts {
|
||||
layout_id Int @id @default(autoincrement())
|
||||
layout_id Int @id @default(autoincrement())
|
||||
screen_id Int
|
||||
component_type String @db.VarChar(50)
|
||||
component_id String @unique @db.VarChar(100)
|
||||
parent_id String? @db.VarChar(100)
|
||||
component_type String @db.VarChar(50)
|
||||
component_id String @unique @db.VarChar(100)
|
||||
parent_id String? @db.VarChar(100)
|
||||
position_x Int
|
||||
position_y Int
|
||||
width Int
|
||||
height Int
|
||||
properties Json?
|
||||
display_order Int @default(0)
|
||||
created_date DateTime @default(now()) @db.Timestamp(6)
|
||||
|
||||
// 관계
|
||||
display_order Int @default(0)
|
||||
created_date DateTime @default(now()) @db.Timestamp(6)
|
||||
screen screen_definitions @relation(fields: [screen_id], references: [screen_id], onDelete: Cascade)
|
||||
widgets screen_widgets[]
|
||||
|
||||
@@ -5096,99 +5023,82 @@ model screen_layouts {
|
||||
}
|
||||
|
||||
model screen_widgets {
|
||||
widget_id Int @id @default(autoincrement())
|
||||
layout_id Int
|
||||
table_name String @db.VarChar(100)
|
||||
column_name String @db.VarChar(100)
|
||||
widget_type String @db.VarChar(50)
|
||||
label String? @db.VarChar(200)
|
||||
placeholder String? @db.VarChar(200)
|
||||
is_required Boolean @default(false)
|
||||
is_readonly Boolean @default(false)
|
||||
validation_rules Json?
|
||||
display_properties Json?
|
||||
created_date DateTime @default(now()) @db.Timestamp(6)
|
||||
|
||||
// 관계
|
||||
layout screen_layouts @relation(fields: [layout_id], references: [layout_id], onDelete: Cascade)
|
||||
widget_id Int @id @default(autoincrement())
|
||||
layout_id Int
|
||||
table_name String @db.VarChar(100)
|
||||
column_name String @db.VarChar(100)
|
||||
widget_type String @db.VarChar(50)
|
||||
label String? @db.VarChar(200)
|
||||
placeholder String? @db.VarChar(200)
|
||||
is_required Boolean @default(false)
|
||||
is_readonly Boolean @default(false)
|
||||
validation_rules Json?
|
||||
display_properties Json?
|
||||
created_date DateTime @default(now()) @db.Timestamp(6)
|
||||
layout screen_layouts @relation(fields: [layout_id], references: [layout_id], onDelete: Cascade)
|
||||
|
||||
@@index([layout_id])
|
||||
}
|
||||
|
||||
model screen_templates {
|
||||
template_id Int @id @default(autoincrement())
|
||||
template_name String @db.VarChar(100)
|
||||
template_type String @db.VarChar(50)
|
||||
company_code String @db.VarChar(50)
|
||||
description String? @db.Text
|
||||
layout_data Json?
|
||||
is_public Boolean @default(false)
|
||||
created_by String? @db.VarChar(50)
|
||||
created_date DateTime @default(now()) @db.Timestamp(6)
|
||||
template_id Int @id @default(autoincrement())
|
||||
template_name String @db.VarChar(100)
|
||||
template_type String @db.VarChar(50)
|
||||
company_code String @db.VarChar(50)
|
||||
description String?
|
||||
layout_data Json?
|
||||
is_public Boolean @default(false)
|
||||
created_by String? @db.VarChar(50)
|
||||
created_date DateTime @default(now()) @db.Timestamp(6)
|
||||
|
||||
@@index([company_code])
|
||||
}
|
||||
|
||||
model screen_menu_assignments {
|
||||
assignment_id Int @id @default(autoincrement())
|
||||
screen_id Int
|
||||
menu_objid Decimal @db.Decimal
|
||||
company_code String @db.VarChar(50)
|
||||
display_order Int @default(0)
|
||||
is_active String @default("Y") @db.Char(1)
|
||||
created_date DateTime @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
|
||||
// 관계
|
||||
screen screen_definitions @relation(fields: [screen_id], references: [screen_id], onDelete: Cascade)
|
||||
assignment_id Int @id @default(autoincrement())
|
||||
screen_id Int
|
||||
menu_objid Decimal @db.Decimal
|
||||
company_code String @db.VarChar(50)
|
||||
display_order Int @default(0)
|
||||
is_active String @default("Y") @db.Char(1)
|
||||
created_date DateTime @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
screen screen_definitions @relation(fields: [screen_id], references: [screen_id], onDelete: Cascade)
|
||||
|
||||
@@unique([screen_id, menu_objid, company_code])
|
||||
@@index([company_code])
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 공통코드 관리 시스템 모델
|
||||
// =====================================================
|
||||
|
||||
/// 공통코드 카테고리 테이블
|
||||
model code_category {
|
||||
category_code String @id @db.VarChar(50)
|
||||
category_name String @db.VarChar(100)
|
||||
category_name_eng String? @db.VarChar(100)
|
||||
description String? @db.Text
|
||||
sort_order Int @default(0)
|
||||
is_active String @default("Y") @db.Char(1)
|
||||
created_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
updated_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
updated_by String? @db.VarChar(50)
|
||||
|
||||
// 관계 - 코드 상세 정보
|
||||
codes code_info[]
|
||||
|
||||
@@index([is_active])
|
||||
@@index([sort_order])
|
||||
category_code String @id @db.VarChar(50)
|
||||
category_name String @db.VarChar(100)
|
||||
category_name_eng String? @db.VarChar(100)
|
||||
description String?
|
||||
sort_order Int? @default(0)
|
||||
is_active String? @default("Y") @db.Char(1)
|
||||
created_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
updated_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
updated_by String? @db.VarChar(50)
|
||||
codes code_info[]
|
||||
}
|
||||
|
||||
/// 공통코드 상세 정보 테이블
|
||||
model code_info {
|
||||
code_category String @db.VarChar(50)
|
||||
code_value String @db.VarChar(50)
|
||||
code_name String @db.VarChar(100)
|
||||
code_name_eng String? @db.VarChar(100)
|
||||
description String? @db.Text
|
||||
sort_order Int @default(0)
|
||||
is_active String @default("Y") @db.Char(1)
|
||||
created_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
updated_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
updated_by String? @db.VarChar(50)
|
||||
code_category String @db.VarChar(50)
|
||||
code_value String @db.VarChar(50)
|
||||
code_name String @db.VarChar(100)
|
||||
code_name_eng String? @db.VarChar(100)
|
||||
description String?
|
||||
sort_order Int? @default(0)
|
||||
is_active String? @default("Y") @db.Char(1)
|
||||
created_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
updated_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
updated_by String? @db.VarChar(50)
|
||||
category code_category @relation(fields: [code_category], references: [category_code], onDelete: Cascade, map: "fk_code_info_category")
|
||||
|
||||
// 관계 - 코드 카테고리
|
||||
category code_category @relation(fields: [code_category], references: [category_code], onDelete: Cascade, onUpdate: Cascade)
|
||||
|
||||
@@id([code_category, code_value])
|
||||
@@index([code_category])
|
||||
@@index([is_active])
|
||||
@@index([code_category, sort_order])
|
||||
@@id([code_category, code_value], map: "pk_code_info")
|
||||
@@index([code_category, sort_order], map: "idx_code_info_sort")
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import multilangRoutes from "./routes/multilangRoutes";
|
||||
import tableManagementRoutes from "./routes/tableManagementRoutes";
|
||||
import screenManagementRoutes from "./routes/screenManagementRoutes";
|
||||
import commonCodeRoutes from "./routes/commonCodeRoutes";
|
||||
import dynamicFormRoutes from "./routes/dynamicFormRoutes";
|
||||
// import userRoutes from './routes/userRoutes';
|
||||
// import menuRoutes from './routes/menuRoutes';
|
||||
|
||||
@@ -67,6 +68,7 @@ app.use("/api/multilang", multilangRoutes);
|
||||
app.use("/api/table-management", tableManagementRoutes);
|
||||
app.use("/api/screen-management", screenManagementRoutes);
|
||||
app.use("/api/common-codes", commonCodeRoutes);
|
||||
app.use("/api/dynamic-form", dynamicFormRoutes);
|
||||
// app.use('/api/users', userRoutes);
|
||||
// app.use('/api/menus', menuRoutes);
|
||||
|
||||
|
||||
310
backend-node/src/controllers/dynamicFormController.ts
Normal file
310
backend-node/src/controllers/dynamicFormController.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import { Response } from "express";
|
||||
import { dynamicFormService } from "../services/dynamicFormService";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
|
||||
// 폼 데이터 저장
|
||||
export const saveFormData = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<Response | void> => {
|
||||
try {
|
||||
const { companyCode, userId } = req.user as any;
|
||||
const { screenId, tableName, data } = req.body;
|
||||
|
||||
console.log("💾 폼 데이터 저장 요청:", {
|
||||
userId,
|
||||
companyCode,
|
||||
screenId,
|
||||
tableName,
|
||||
data,
|
||||
});
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!screenId || !tableName || !data) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "필수 필드가 누락되었습니다. (screenId, tableName, data)",
|
||||
});
|
||||
}
|
||||
|
||||
// 메타데이터 추가 (사용자가 입력한 경우에만 company_code 추가)
|
||||
const formDataWithMeta = {
|
||||
...data,
|
||||
created_by: userId,
|
||||
updated_by: userId,
|
||||
screen_id: screenId,
|
||||
};
|
||||
|
||||
// company_code는 사용자가 명시적으로 입력한 경우에만 추가
|
||||
if (data.company_code !== undefined) {
|
||||
formDataWithMeta.company_code = data.company_code;
|
||||
} else if (companyCode && companyCode !== "*") {
|
||||
// 기본 company_code가 '*'가 아닌 경우에만 추가
|
||||
formDataWithMeta.company_code = companyCode;
|
||||
}
|
||||
|
||||
const result = await dynamicFormService.saveFormData(
|
||||
screenId,
|
||||
tableName,
|
||||
formDataWithMeta
|
||||
);
|
||||
|
||||
console.log("✅ 폼 데이터 저장 성공:", result);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result,
|
||||
message: "데이터가 성공적으로 저장되었습니다.",
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("❌ 폼 데이터 저장 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "데이터 저장에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 폼 데이터 업데이트
|
||||
export const updateFormData = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<Response | void> => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { companyCode, userId } = req.user as any;
|
||||
const { tableName, data } = req.body;
|
||||
|
||||
console.log("🔄 폼 데이터 업데이트 요청:", {
|
||||
id,
|
||||
userId,
|
||||
companyCode,
|
||||
tableName,
|
||||
data,
|
||||
});
|
||||
|
||||
if (!tableName || !data) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "필수 필드가 누락되었습니다. (tableName, data)",
|
||||
});
|
||||
}
|
||||
|
||||
// 메타데이터 추가
|
||||
const formDataWithMeta = {
|
||||
...data,
|
||||
updated_by: userId,
|
||||
updated_at: new Date(),
|
||||
};
|
||||
|
||||
const result = await dynamicFormService.updateFormData(
|
||||
parseInt(id),
|
||||
tableName,
|
||||
formDataWithMeta
|
||||
);
|
||||
|
||||
console.log("✅ 폼 데이터 업데이트 성공:", result);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result,
|
||||
message: "데이터가 성공적으로 업데이트되었습니다.",
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("❌ 폼 데이터 업데이트 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "데이터 업데이트에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 폼 데이터 삭제
|
||||
export const deleteFormData = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<Response | void> => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { companyCode } = req.user as any;
|
||||
const { tableName } = req.body;
|
||||
|
||||
console.log("🗑️ 폼 데이터 삭제 요청:", { id, companyCode, tableName });
|
||||
|
||||
if (!tableName) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "필수 필드가 누락되었습니다. (tableName)",
|
||||
});
|
||||
}
|
||||
|
||||
await dynamicFormService.deleteFormData(parseInt(id), tableName);
|
||||
|
||||
console.log("✅ 폼 데이터 삭제 성공");
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "데이터가 성공적으로 삭제되었습니다.",
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("❌ 폼 데이터 삭제 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "데이터 삭제에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 단일 폼 데이터 조회
|
||||
export const getFormData = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<Response | void> => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { companyCode } = req.user as any;
|
||||
|
||||
console.log("📄 폼 데이터 단건 조회 요청:", { id, companyCode });
|
||||
|
||||
const data = await dynamicFormService.getFormData(parseInt(id));
|
||||
|
||||
if (!data) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "데이터를 찾을 수 없습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
console.log("✅ 폼 데이터 단건 조회 성공");
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: data,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("❌ 폼 데이터 단건 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "데이터 조회에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 화면별 폼 데이터 목록 조회
|
||||
export const getFormDataList = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<Response | void> => {
|
||||
try {
|
||||
const { screenId } = req.params;
|
||||
const { companyCode } = req.user as any;
|
||||
const {
|
||||
page = 1,
|
||||
size = 10,
|
||||
search = "",
|
||||
sortBy = "created_at",
|
||||
sortOrder = "desc",
|
||||
} = req.query;
|
||||
|
||||
console.log("📋 폼 데이터 목록 조회 요청:", {
|
||||
screenId,
|
||||
companyCode,
|
||||
page,
|
||||
size,
|
||||
search,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
});
|
||||
|
||||
const result = await dynamicFormService.getFormDataList(
|
||||
parseInt(screenId as string),
|
||||
{
|
||||
page: parseInt(page as string),
|
||||
size: parseInt(size as string),
|
||||
search: search as string,
|
||||
sortBy: sortBy as string,
|
||||
sortOrder: sortOrder as "asc" | "desc",
|
||||
}
|
||||
);
|
||||
|
||||
console.log("✅ 폼 데이터 목록 조회 성공");
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("❌ 폼 데이터 목록 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "데이터 조회에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 폼 데이터 검증
|
||||
export const validateFormData = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<Response | void> => {
|
||||
try {
|
||||
const { tableName, data } = req.body;
|
||||
|
||||
console.log("✅ 폼 데이터 검증 요청:", { tableName, data });
|
||||
|
||||
if (!tableName || !data) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "필수 필드가 누락되었습니다. (tableName, data)",
|
||||
});
|
||||
}
|
||||
|
||||
const validationResult = await dynamicFormService.validateFormData(
|
||||
tableName,
|
||||
data
|
||||
);
|
||||
|
||||
console.log("✅ 폼 데이터 검증 성공:", validationResult);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: validationResult,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("❌ 폼 데이터 검증 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "데이터 검증에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 테이블 컬럼 정보 조회 (검증용)
|
||||
export const getTableColumns = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<Response | void> => {
|
||||
try {
|
||||
const { tableName } = req.params;
|
||||
|
||||
console.log("📊 테이블 컬럼 정보 조회 요청:", { tableName });
|
||||
|
||||
const columns = await dynamicFormService.getTableColumns(tableName);
|
||||
|
||||
console.log("✅ 테이블 컬럼 정보 조회 성공");
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
tableName,
|
||||
columns,
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("❌ 테이블 컬럼 정보 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "테이블 정보 조회에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -386,7 +386,7 @@ export async function updateColumnWebType(
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { tableName, columnName } = req.params;
|
||||
const { webType, detailSettings } = req.body;
|
||||
const { webType, detailSettings, inputType } = req.body;
|
||||
|
||||
logger.info(
|
||||
`=== 컬럼 웹 타입 설정 시작: ${tableName}.${columnName} = ${webType} ===`
|
||||
@@ -410,7 +410,8 @@ export async function updateColumnWebType(
|
||||
tableName,
|
||||
columnName,
|
||||
webType,
|
||||
detailSettings
|
||||
detailSettings,
|
||||
inputType
|
||||
);
|
||||
|
||||
logger.info(
|
||||
|
||||
33
backend-node/src/routes/dynamicFormRoutes.ts
Normal file
33
backend-node/src/routes/dynamicFormRoutes.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import express from "express";
|
||||
import { authenticateToken } from "../middleware/authMiddleware";
|
||||
import {
|
||||
saveFormData,
|
||||
updateFormData,
|
||||
deleteFormData,
|
||||
getFormData,
|
||||
getFormDataList,
|
||||
validateFormData,
|
||||
getTableColumns,
|
||||
} from "../controllers/dynamicFormController";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 모든 라우트에 인증 미들웨어 적용
|
||||
router.use(authenticateToken);
|
||||
|
||||
// 폼 데이터 CRUD
|
||||
router.post("/save", saveFormData);
|
||||
router.put("/:id", updateFormData);
|
||||
router.delete("/:id", deleteFormData);
|
||||
router.get("/:id", getFormData);
|
||||
|
||||
// 화면별 폼 데이터 목록 조회
|
||||
router.get("/screen/:screenId", getFormDataList);
|
||||
|
||||
// 폼 데이터 검증
|
||||
router.post("/validate", validateFormData);
|
||||
|
||||
// 테이블 컬럼 정보 조회 (검증용)
|
||||
router.get("/table/:tableName/columns", getTableColumns);
|
||||
|
||||
export default router;
|
||||
573
backend-node/src/services/dynamicFormService.ts
Normal file
573
backend-node/src/services/dynamicFormService.ts
Normal file
@@ -0,0 +1,573 @@
|
||||
import prisma from "../config/database";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
export interface FormDataResult {
|
||||
id: number;
|
||||
screenId: number;
|
||||
tableName: string;
|
||||
data: Record<string, any>;
|
||||
createdAt: Date | null;
|
||||
updatedAt: Date | null;
|
||||
createdBy: string;
|
||||
updatedBy: string;
|
||||
}
|
||||
|
||||
export interface PaginatedFormData {
|
||||
content: FormDataResult[];
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
currentPage: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface ValidationError {
|
||||
field: string;
|
||||
message: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface ValidationResult {
|
||||
valid: boolean;
|
||||
errors: ValidationError[];
|
||||
}
|
||||
|
||||
export interface TableColumn {
|
||||
columnName: string;
|
||||
dataType: string;
|
||||
nullable: boolean;
|
||||
primaryKey: boolean;
|
||||
maxLength?: number;
|
||||
defaultValue?: any;
|
||||
}
|
||||
|
||||
export class DynamicFormService {
|
||||
/**
|
||||
* 테이블의 컬럼명 목록 조회 (간단 버전)
|
||||
*/
|
||||
private async getTableColumnNames(tableName: string): Promise<string[]> {
|
||||
try {
|
||||
const result = (await prisma.$queryRawUnsafe(`
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = '${tableName}'
|
||||
AND table_schema = 'public'
|
||||
`)) as any[];
|
||||
|
||||
return result.map((row) => row.column_name);
|
||||
} catch (error) {
|
||||
console.error(`❌ 테이블 ${tableName} 컬럼 정보 조회 실패:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블의 Primary Key 컬럼 조회
|
||||
*/
|
||||
private async getTablePrimaryKeys(tableName: string): Promise<string[]> {
|
||||
try {
|
||||
const result = (await prisma.$queryRawUnsafe(`
|
||||
SELECT kcu.column_name
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
WHERE tc.table_name = '${tableName}'
|
||||
AND tc.constraint_type = 'PRIMARY KEY'
|
||||
AND tc.table_schema = 'public'
|
||||
`)) as any[];
|
||||
|
||||
return result.map((row) => row.column_name);
|
||||
} catch (error) {
|
||||
console.error(`❌ 테이블 ${tableName} Primary Key 조회 실패:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 폼 데이터 저장 (실제 테이블에 직접 저장)
|
||||
*/
|
||||
async saveFormData(
|
||||
screenId: number,
|
||||
tableName: string,
|
||||
data: Record<string, any>
|
||||
): Promise<FormDataResult> {
|
||||
try {
|
||||
console.log("💾 서비스: 실제 테이블에 폼 데이터 저장 시작:", {
|
||||
screenId,
|
||||
tableName,
|
||||
data,
|
||||
});
|
||||
|
||||
// 테이블의 실제 컬럼 정보와 Primary Key 조회
|
||||
const tableColumns = await this.getTableColumnNames(tableName);
|
||||
const primaryKeys = await this.getTablePrimaryKeys(tableName);
|
||||
console.log(`📋 테이블 ${tableName}의 컬럼:`, tableColumns);
|
||||
console.log(`🔑 테이블 ${tableName}의 Primary Key:`, primaryKeys);
|
||||
|
||||
// 메타데이터 제거 (실제 테이블 컬럼이 아님)
|
||||
const { created_by, updated_by, company_code, screen_id, ...actualData } =
|
||||
data;
|
||||
|
||||
// 기본 데이터 준비
|
||||
const dataToInsert: any = { ...actualData };
|
||||
|
||||
// 테이블에 존재하는 공통 필드들만 추가
|
||||
if (tableColumns.includes("created_at")) {
|
||||
dataToInsert.created_at = new Date();
|
||||
}
|
||||
if (tableColumns.includes("updated_at")) {
|
||||
dataToInsert.updated_at = new Date();
|
||||
}
|
||||
if (tableColumns.includes("regdate") && !dataToInsert.regdate) {
|
||||
dataToInsert.regdate = new Date();
|
||||
}
|
||||
|
||||
// 생성자/수정자 정보가 있고 해당 컬럼이 존재한다면 추가
|
||||
if (created_by && tableColumns.includes("created_by")) {
|
||||
dataToInsert.created_by = created_by;
|
||||
}
|
||||
if (updated_by && tableColumns.includes("updated_by")) {
|
||||
dataToInsert.updated_by = updated_by;
|
||||
}
|
||||
if (company_code && tableColumns.includes("company_code")) {
|
||||
dataToInsert.company_code = company_code;
|
||||
}
|
||||
|
||||
// 존재하지 않는 컬럼 제거
|
||||
Object.keys(dataToInsert).forEach((key) => {
|
||||
if (!tableColumns.includes(key)) {
|
||||
console.log(
|
||||
`⚠️ 컬럼 ${key}는 테이블 ${tableName}에 존재하지 않아 제거됨`
|
||||
);
|
||||
delete dataToInsert[key];
|
||||
}
|
||||
});
|
||||
|
||||
console.log("🎯 실제 테이블에 삽입할 데이터:", {
|
||||
tableName,
|
||||
dataToInsert,
|
||||
});
|
||||
|
||||
// 동적 SQL을 사용하여 실제 테이블에 UPSERT
|
||||
const columns = Object.keys(dataToInsert);
|
||||
const values: any[] = Object.values(dataToInsert);
|
||||
const placeholders = values.map((_, index) => `$${index + 1}`).join(", ");
|
||||
|
||||
let upsertQuery: string;
|
||||
|
||||
if (primaryKeys.length > 0) {
|
||||
// Primary Key가 있는 경우 UPSERT 사용
|
||||
const conflictColumns = primaryKeys.join(", ");
|
||||
const updateSet = columns
|
||||
.filter((col) => !primaryKeys.includes(col)) // Primary Key는 UPDATE에서 제외
|
||||
.map((col) => `${col} = EXCLUDED.${col}`)
|
||||
.join(", ");
|
||||
|
||||
if (updateSet) {
|
||||
upsertQuery = `
|
||||
INSERT INTO ${tableName} (${columns.join(", ")})
|
||||
VALUES (${placeholders})
|
||||
ON CONFLICT (${conflictColumns})
|
||||
DO UPDATE SET ${updateSet}
|
||||
RETURNING *
|
||||
`;
|
||||
} else {
|
||||
// 업데이트할 컬럼이 없는 경우 (Primary Key만 있는 테이블)
|
||||
upsertQuery = `
|
||||
INSERT INTO ${tableName} (${columns.join(", ")})
|
||||
VALUES (${placeholders})
|
||||
ON CONFLICT (${conflictColumns})
|
||||
DO NOTHING
|
||||
RETURNING *
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
// Primary Key가 없는 경우 일반 INSERT
|
||||
upsertQuery = `
|
||||
INSERT INTO ${tableName} (${columns.join(", ")})
|
||||
VALUES (${placeholders})
|
||||
RETURNING *
|
||||
`;
|
||||
}
|
||||
|
||||
console.log("📝 실행할 UPSERT SQL:", upsertQuery);
|
||||
console.log("📊 SQL 파라미터:", values);
|
||||
|
||||
const result = await prisma.$queryRawUnsafe(upsertQuery, ...values);
|
||||
|
||||
console.log("✅ 서비스: 실제 테이블 저장 성공:", result);
|
||||
|
||||
// 결과를 표준 형식으로 변환
|
||||
const insertedRecord = Array.isArray(result) ? result[0] : result;
|
||||
|
||||
return {
|
||||
id: insertedRecord.id || insertedRecord.objid || 0,
|
||||
screenId: screenId,
|
||||
tableName: tableName,
|
||||
data: insertedRecord as Record<string, any>,
|
||||
createdAt: insertedRecord.created_at || new Date(),
|
||||
updatedAt: insertedRecord.updated_at || new Date(),
|
||||
createdBy: insertedRecord.created_by || created_by || "system",
|
||||
updatedBy: insertedRecord.updated_by || updated_by || "system",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("❌ 서비스: 실제 테이블 저장 실패:", error);
|
||||
throw new Error(`실제 테이블 저장 실패: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 폼 데이터 업데이트 (실제 테이블에서 직접 업데이트)
|
||||
*/
|
||||
async updateFormData(
|
||||
id: number,
|
||||
tableName: string,
|
||||
data: Record<string, any>
|
||||
): Promise<FormDataResult> {
|
||||
try {
|
||||
console.log("🔄 서비스: 실제 테이블에서 폼 데이터 업데이트 시작:", {
|
||||
id,
|
||||
tableName,
|
||||
data,
|
||||
});
|
||||
|
||||
// 테이블의 실제 컬럼 정보 조회
|
||||
const tableColumns = await this.getTableColumnNames(tableName);
|
||||
console.log(`📋 테이블 ${tableName}의 컬럼:`, tableColumns);
|
||||
|
||||
// 메타데이터 제거
|
||||
const { created_by, updated_by, company_code, screen_id, ...actualData } =
|
||||
data;
|
||||
|
||||
// 기본 데이터 준비
|
||||
const dataToUpdate: any = { ...actualData };
|
||||
|
||||
// 테이블에 존재하는 업데이트 관련 필드들만 추가
|
||||
if (tableColumns.includes("updated_at")) {
|
||||
dataToUpdate.updated_at = new Date();
|
||||
}
|
||||
if (tableColumns.includes("regdate") && !dataToUpdate.regdate) {
|
||||
dataToUpdate.regdate = new Date();
|
||||
}
|
||||
|
||||
// 수정자 정보가 있고 해당 컬럼이 존재한다면 추가
|
||||
if (updated_by && tableColumns.includes("updated_by")) {
|
||||
dataToUpdate.updated_by = updated_by;
|
||||
}
|
||||
|
||||
// 존재하지 않는 컬럼 제거
|
||||
Object.keys(dataToUpdate).forEach((key) => {
|
||||
if (!tableColumns.includes(key)) {
|
||||
console.log(
|
||||
`⚠️ 컬럼 ${key}는 테이블 ${tableName}에 존재하지 않아 제거됨`
|
||||
);
|
||||
delete dataToUpdate[key];
|
||||
}
|
||||
});
|
||||
|
||||
console.log("🎯 실제 테이블에서 업데이트할 데이터:", {
|
||||
tableName,
|
||||
id,
|
||||
dataToUpdate,
|
||||
});
|
||||
|
||||
// 동적 UPDATE SQL 생성
|
||||
const setClause = Object.keys(dataToUpdate)
|
||||
.map((key, index) => `${key} = $${index + 1}`)
|
||||
.join(", ");
|
||||
|
||||
const values: any[] = Object.values(dataToUpdate);
|
||||
values.push(id); // WHERE 조건용 ID 추가
|
||||
|
||||
// ID 또는 objid로 찾기 시도
|
||||
const updateQuery = `
|
||||
UPDATE ${tableName}
|
||||
SET ${setClause}
|
||||
WHERE (id = $${values.length} OR objid = $${values.length})
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
console.log("📝 실행할 UPDATE SQL:", updateQuery);
|
||||
console.log("📊 SQL 파라미터:", values);
|
||||
|
||||
const result = await prisma.$queryRawUnsafe(updateQuery, ...values);
|
||||
|
||||
console.log("✅ 서비스: 실제 테이블 업데이트 성공:", result);
|
||||
|
||||
const updatedRecord = Array.isArray(result) ? result[0] : result;
|
||||
|
||||
return {
|
||||
id: updatedRecord.id || updatedRecord.objid || id,
|
||||
screenId: 0, // 실제 테이블에는 screenId가 없으므로 0으로 설정
|
||||
tableName: tableName,
|
||||
data: updatedRecord as Record<string, any>,
|
||||
createdAt: updatedRecord.created_at || new Date(),
|
||||
updatedAt: updatedRecord.updated_at || new Date(),
|
||||
createdBy: updatedRecord.created_by || "system",
|
||||
updatedBy: updatedRecord.updated_by || updated_by || "system",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("❌ 서비스: 실제 테이블 업데이트 실패:", error);
|
||||
throw new Error(`실제 테이블 업데이트 실패: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 폼 데이터 삭제 (실제 테이블에서 직접 삭제)
|
||||
*/
|
||||
async deleteFormData(id: number, tableName: string): Promise<void> {
|
||||
try {
|
||||
console.log("🗑️ 서비스: 실제 테이블에서 폼 데이터 삭제 시작:", {
|
||||
id,
|
||||
tableName,
|
||||
});
|
||||
|
||||
// 동적 DELETE SQL 생성
|
||||
const deleteQuery = `
|
||||
DELETE FROM ${tableName}
|
||||
WHERE (id = $1 OR objid = $1)
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
console.log("📝 실행할 DELETE SQL:", deleteQuery);
|
||||
console.log("📊 SQL 파라미터:", [id]);
|
||||
|
||||
const result = await prisma.$queryRawUnsafe(deleteQuery, id);
|
||||
|
||||
console.log("✅ 서비스: 실제 테이블 삭제 성공:", result);
|
||||
} catch (error) {
|
||||
console.error("❌ 서비스: 실제 테이블 삭제 실패:", error);
|
||||
throw new Error(`실제 테이블 삭제 실패: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 단일 폼 데이터 조회
|
||||
*/
|
||||
async getFormData(id: number): Promise<FormDataResult | null> {
|
||||
try {
|
||||
console.log("📄 서비스: 폼 데이터 단건 조회 시작:", { id });
|
||||
|
||||
const result = await prisma.dynamic_form_data.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
console.log("❌ 서비스: 폼 데이터를 찾을 수 없음");
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log("✅ 서비스: 폼 데이터 단건 조회 성공");
|
||||
|
||||
return {
|
||||
id: result.id,
|
||||
screenId: result.screen_id,
|
||||
tableName: result.table_name,
|
||||
data: result.form_data as Record<string, any>,
|
||||
createdAt: result.created_at,
|
||||
updatedAt: result.updated_at,
|
||||
createdBy: result.created_by,
|
||||
updatedBy: result.updated_by,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("❌ 서비스: 폼 데이터 단건 조회 실패:", error);
|
||||
throw new Error(`폼 데이터 조회 실패: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 화면별 폼 데이터 목록 조회 (페이징)
|
||||
*/
|
||||
async getFormDataList(
|
||||
screenId: number,
|
||||
params: {
|
||||
page: number;
|
||||
size: number;
|
||||
search?: string;
|
||||
sortBy?: string;
|
||||
sortOrder?: "asc" | "desc";
|
||||
}
|
||||
): Promise<PaginatedFormData> {
|
||||
try {
|
||||
console.log("📋 서비스: 폼 데이터 목록 조회 시작:", { screenId, params });
|
||||
|
||||
const {
|
||||
page,
|
||||
size,
|
||||
search,
|
||||
sortBy = "created_at",
|
||||
sortOrder = "desc",
|
||||
} = params;
|
||||
const skip = (page - 1) * size;
|
||||
|
||||
// 검색 조건 구성
|
||||
const where: Prisma.dynamic_form_dataWhereInput = {
|
||||
screen_id: screenId,
|
||||
};
|
||||
|
||||
// 검색어가 있는 경우 form_data 필드에서 검색
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{
|
||||
form_data: {
|
||||
path: [],
|
||||
string_contains: search,
|
||||
},
|
||||
},
|
||||
{
|
||||
table_name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 정렬 조건 구성
|
||||
const orderBy: Prisma.dynamic_form_dataOrderByWithRelationInput = {};
|
||||
if (sortBy === "created_at" || sortBy === "updated_at") {
|
||||
orderBy[sortBy] = sortOrder;
|
||||
} else {
|
||||
orderBy.created_at = "desc"; // 기본값
|
||||
}
|
||||
|
||||
// 데이터 조회
|
||||
const [results, totalCount] = await Promise.all([
|
||||
prisma.dynamic_form_data.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
skip,
|
||||
take: size,
|
||||
}),
|
||||
prisma.dynamic_form_data.count({ where }),
|
||||
]);
|
||||
|
||||
const formDataResults: FormDataResult[] = results.map((result) => ({
|
||||
id: result.id,
|
||||
screenId: result.screen_id,
|
||||
tableName: result.table_name,
|
||||
data: result.form_data as Record<string, any>,
|
||||
createdAt: result.created_at,
|
||||
updatedAt: result.updated_at,
|
||||
createdBy: result.created_by,
|
||||
updatedBy: result.updated_by,
|
||||
}));
|
||||
|
||||
const totalPages = Math.ceil(totalCount / size);
|
||||
|
||||
console.log("✅ 서비스: 폼 데이터 목록 조회 성공:", {
|
||||
totalCount,
|
||||
totalPages,
|
||||
currentPage: page,
|
||||
});
|
||||
|
||||
return {
|
||||
content: formDataResults,
|
||||
totalElements: totalCount,
|
||||
totalPages,
|
||||
currentPage: page,
|
||||
size,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("❌ 서비스: 폼 데이터 목록 조회 실패:", error);
|
||||
throw new Error(`폼 데이터 목록 조회 실패: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 폼 데이터 검증
|
||||
*/
|
||||
async validateFormData(
|
||||
tableName: string,
|
||||
data: Record<string, any>
|
||||
): Promise<ValidationResult> {
|
||||
try {
|
||||
console.log("✅ 서비스: 폼 데이터 검증 시작:", { tableName, data });
|
||||
|
||||
const errors: ValidationError[] = [];
|
||||
|
||||
// 기본 검증 로직 (실제로는 테이블 스키마를 확인해야 함)
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
// 예시: 빈 값 검증
|
||||
if (value === null || value === undefined || value === "") {
|
||||
// 특정 필드가 required인지 확인하는 로직이 필요
|
||||
// 지금은 간단히 모든 필드를 선택사항으로 처리
|
||||
}
|
||||
|
||||
// 예시: 데이터 타입 검증
|
||||
// 실제로는 테이블 스키마의 컬럼 타입과 비교해야 함
|
||||
});
|
||||
|
||||
const result: ValidationResult = {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
|
||||
console.log("✅ 서비스: 폼 데이터 검증 완료:", result);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("❌ 서비스: 폼 데이터 검증 실패:", error);
|
||||
throw new Error(`폼 데이터 검증 실패: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 컬럼 정보 조회 (PostgreSQL 시스템 테이블 활용)
|
||||
*/
|
||||
async getTableColumns(tableName: string): Promise<TableColumn[]> {
|
||||
try {
|
||||
console.log("📊 서비스: 테이블 컬럼 정보 조회 시작:", { tableName });
|
||||
|
||||
// PostgreSQL의 information_schema를 사용하여 컬럼 정보 조회
|
||||
const columns = await prisma.$queryRaw<any[]>`
|
||||
SELECT
|
||||
column_name,
|
||||
data_type,
|
||||
is_nullable,
|
||||
column_default,
|
||||
character_maximum_length
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = ${tableName}
|
||||
AND table_schema = 'public'
|
||||
ORDER BY ordinal_position
|
||||
`;
|
||||
|
||||
// Primary key 정보 조회
|
||||
const primaryKeys = await prisma.$queryRaw<any[]>`
|
||||
SELECT
|
||||
kcu.column_name
|
||||
FROM
|
||||
information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
WHERE
|
||||
tc.constraint_type = 'PRIMARY KEY'
|
||||
AND tc.table_name = ${tableName}
|
||||
AND tc.table_schema = 'public'
|
||||
`;
|
||||
|
||||
const primaryKeyColumns = new Set(
|
||||
primaryKeys.map((pk) => pk.column_name)
|
||||
);
|
||||
|
||||
const result: TableColumn[] = columns.map((col) => ({
|
||||
columnName: col.column_name,
|
||||
dataType: col.data_type,
|
||||
nullable: col.is_nullable === "YES",
|
||||
primaryKey: primaryKeyColumns.has(col.column_name),
|
||||
maxLength: col.character_maximum_length,
|
||||
defaultValue: col.column_default,
|
||||
}));
|
||||
|
||||
console.log("✅ 서비스: 테이블 컬럼 정보 조회 성공:", result);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("❌ 서비스: 테이블 컬럼 정보 조회 실패:", error);
|
||||
throw new Error(`테이블 컬럼 정보 조회 실패: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 싱글톤 인스턴스 생성 및 export
|
||||
export const dynamicFormService = new DynamicFormService();
|
||||
@@ -69,6 +69,7 @@ export class TableManagementService {
|
||||
COALESCE(cl.column_label, c.column_name) as "displayName",
|
||||
c.data_type as "dbType",
|
||||
COALESCE(cl.web_type, 'text') as "webType",
|
||||
COALESCE(cl.input_type, 'direct') as "inputType",
|
||||
COALESCE(cl.detail_settings, '') as "detailSettings",
|
||||
COALESCE(cl.description, '') as "description",
|
||||
c.is_nullable as "isNullable",
|
||||
@@ -368,7 +369,8 @@ export class TableManagementService {
|
||||
tableName: string,
|
||||
columnName: string,
|
||||
webType: string,
|
||||
detailSettings?: Record<string, any>
|
||||
detailSettings?: Record<string, any>,
|
||||
inputType?: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
logger.info(
|
||||
@@ -394,30 +396,42 @@ export class TableManagementService {
|
||||
|
||||
if (existingColumn) {
|
||||
// 기존 컬럼 라벨 업데이트
|
||||
const updateData: any = {
|
||||
web_type: webType,
|
||||
detail_settings: JSON.stringify(finalDetailSettings),
|
||||
updated_date: new Date(),
|
||||
};
|
||||
|
||||
if (inputType) {
|
||||
updateData.input_type = inputType;
|
||||
}
|
||||
|
||||
await prisma.column_labels.update({
|
||||
where: {
|
||||
id: existingColumn.id,
|
||||
},
|
||||
data: {
|
||||
web_type: webType,
|
||||
detail_settings: JSON.stringify(finalDetailSettings),
|
||||
updated_date: new Date(),
|
||||
},
|
||||
data: updateData,
|
||||
});
|
||||
logger.info(
|
||||
`컬럼 웹 타입 업데이트 완료: ${tableName}.${columnName} = ${webType}`
|
||||
);
|
||||
} else {
|
||||
// 새로운 컬럼 라벨 생성
|
||||
const createData: any = {
|
||||
table_name: tableName,
|
||||
column_name: columnName,
|
||||
web_type: webType,
|
||||
detail_settings: JSON.stringify(finalDetailSettings),
|
||||
created_date: new Date(),
|
||||
updated_date: new Date(),
|
||||
};
|
||||
|
||||
if (inputType) {
|
||||
createData.input_type = inputType;
|
||||
}
|
||||
|
||||
await prisma.column_labels.create({
|
||||
data: {
|
||||
table_name: tableName,
|
||||
column_name: columnName,
|
||||
web_type: webType,
|
||||
detail_settings: JSON.stringify(finalDetailSettings),
|
||||
created_date: new Date(),
|
||||
updated_date: new Date(),
|
||||
},
|
||||
data: createData,
|
||||
});
|
||||
logger.info(
|
||||
`컬럼 라벨 생성 및 웹 타입 설정 완료: ${tableName}.${columnName} = ${webType}`
|
||||
|
||||
@@ -224,6 +224,7 @@ export interface ColumnInfo {
|
||||
columnLabel?: string;
|
||||
dataType: string;
|
||||
webType?: WebType;
|
||||
inputType?: "direct" | "auto"; // 입력 타입
|
||||
isNullable: string;
|
||||
columnDefault?: string;
|
||||
characterMaximumLength?: number;
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface ColumnTypeInfo {
|
||||
displayName: string;
|
||||
dbType: string;
|
||||
webType: string;
|
||||
inputType?: "direct" | "auto";
|
||||
detailSettings: string;
|
||||
description: string;
|
||||
isNullable: string;
|
||||
|
||||
Reference in New Issue
Block a user