Workflow problemleri

This commit is contained in:
Sedat Öztürk 2026-06-06 21:31:03 +03:00
parent 2f1b9d4e77
commit 64084679e8
15 changed files with 132 additions and 46 deletions

View file

@ -305,7 +305,7 @@ public class ListFormWizardAppService(
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
SearchPanelJson = WizardConsts.DefaultSearchPanelJson,
GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }),
SelectionJson = WizardConsts.DefaultSelectionSingleJson,
SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.Widgets.Count > 0 ? GridOptions.SelectionModeSingle : GridOptions.SelectionModeNone),
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
PermissionJson = WizardConsts.DefaultPermissionJson(code),
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,

View file

@ -18038,6 +18038,12 @@
"en": "Add Multi-Tenant Column",
"tr": "MultiTenant Sütunları Ekle"
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.AddWorkflowColumns",
"en": "Add Workflow Column",
"tr": "Workflow Sütunları Ekle"
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.ClearAllColumns",

View file

@ -797,7 +797,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
PermissionJson = DefaultPermissionJson(PlatformConsts.IdentityPermissions.Users.Create, listFormName, PlatformConsts.IdentityPermissions.Users.Update, PlatformConsts.IdentityPermissions.Users.Delete, PlatformConsts.IdentityPermissions.Users.Export, PlatformConsts.IdentityPermissions.Users.Import, PlatformConsts.IdentityPermissions.Users.Note),
DeleteCommand = $"UPDATE \"AbpUsers\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id",
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 730, true, true, true, true, false),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 710, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
new () { Order=1,ColCount=1,ColSpan=1,ItemType="group",Items=[
new EditingFormItemDto { Order=1, DataField="Email", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },

View file

@ -352,7 +352,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
SearchPanelJson = WizardConsts.DefaultSearchPanelJson,
GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }),
SelectionJson = WizardConsts.DefaultSelectionSingleJson,
SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.Widgets.Count > 0 ? GridOptions.SelectionModeSingle : GridOptions.SelectionModeNone),
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
PermissionJson = WizardConsts.DefaultPermissionJson(code),
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,

View file

@ -95,9 +95,9 @@ public static class WizardConsts
public static readonly string DefaultSearchPanelJson = JsonSerializer.Serialize(new { Visible = true });
public static readonly string DefaultGroupPanelJson = JsonSerializer.Serialize(new { Visible = true });
public static readonly string DefaultSelectionSingleJson = JsonSerializer.Serialize(new
public static string DefaultSelectionSingleJson(string Mode = "none") => JsonSerializer.Serialize(new
{
Mode = GridOptions.SelectionModeNone,
Mode = Mode,
AllowSelectAll = false
});

View file

@ -347,7 +347,8 @@ export function toCriteriaForm(item: WorkflowCriteriaDto): WorkflowCriteriaForm
}
export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput {
const sharedPerson = item.approver || ''
const sharedPerson =
item.kind === 'Approval' || item.kind === 'Inform' ? item.approver || '' : ''
const compareOutcomes = (item.compareOutcomes || [])
.slice(0, 4)
.filter((outcome) => outcome.label?.trim())
@ -504,7 +505,7 @@ export function criteriaSummary(item: WorkflowCriteriaDto) {
if (item.kind === 'Approval' || item.kind === 'Inform') {
return `${item.title} ${item.approver ? `- ${item.approver}` : ''}`
}
return `${item.title} ${item.approver ? `- ${item.approver}` : ''}`
return item.title
}
export function targetTitle(criteria: WorkflowCriteriaDto[], id?: string | null) {

View file

@ -296,10 +296,12 @@ export function FormTabWorkflow(
}
const schema = object().shape({
approvalUserFieldName: string().required(),
approvalStatusFieldName: string().required(),
approvalDateFieldName: string(),
approvalDescriptionFieldName: string(),
workflowDto: object().shape({
approvalUserFieldName: string().required(),
approvalStatusFieldName: string().required(),
approvalDateFieldName: string(),
approvalDescriptionFieldName: string(),
}),
})
const initialValues = useStoreState((s) => s.admin.lists.values)
@ -423,7 +425,7 @@ export function FormTabWorkflow(
</FormItem>
</div>
<Button block variant="solid" loading={isSubmitting}>
<Button block variant="solid" type="submit" loading={isSubmitting}>
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</Card>

View file

@ -2,7 +2,7 @@ import { Button, Checkbox, FormItem, Input, Select } from '@/components/ui'
import { SelectCommandTypeEnum } from '@/proxy/form/models'
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
import { SelectBoxOption } from '@/types/shared'
import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
import { Field, FieldProps, FormikErrors, FormikTouched, useFormikContext } from 'formik'
import { useState } from 'react'
import CreatableSelect from 'react-select/creatable'
import { FaArrowLeft, FaArrowRight, FaPlus } from 'react-icons/fa'
@ -68,6 +68,16 @@ const WizardStep2 = ({
onNext,
}: WizardStep2Props) => {
const [showTableDesignerDialog, setShowTableDesignerDialog] = useState(false)
const formik = useFormikContext<ListFormWizardDto>()
const handleTableDeployed = async (table: { schemaName: string; tableName: string }) => {
await onDbObjectsRefresh(values.dataSourceCode)
formik.setFieldValue('selectCommand', table.tableName)
formik.setFieldValue('selectCommandType', SelectCommandTypeEnum.Table)
formik.setFieldValue('keyFieldName', '')
formik.setFieldTouched('keyFieldName', false)
onLoadColumns(values.dataSourceCode, table.schemaName || 'dbo', table.tableName)
}
const step2Missing = [
!values.listFormCode && translate('::App.Listform.ListformField.ListFormCode'),
@ -825,7 +835,7 @@ const WizardStep2 = ({
onClose={() => setShowTableDesignerDialog(false)}
dataSource={values.dataSourceCode}
initialTableData={null}
onDeployed={() => onDbObjectsRefresh(values.dataSourceCode)}
onDeployed={handleTableDeployed}
/>
</div>
)

View file

@ -16,6 +16,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'
import type { FormEvent } from 'react'
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
import { WorkflowDesigner } from '../workflow/WorkflowDesigner'
import { IdentityUserDto } from '@/proxy/admin/models'
type Props = {
listFormCode: string
@ -77,7 +78,7 @@ function WizardStep6({
useEffect(() => {
getUsers(0, 1000).then((response) => {
setUserList(
(response.data?.items ?? []).map((user: any) => ({
(response.data?.items ?? []).map((user: IdentityUserDto) => ({
value: user.userName,
label: `${user.userName} (${user.name} ${user.surname})`,
})),

View file

@ -476,9 +476,12 @@ const WizardStep7 = ({
{criteria.compareColumn} {criteria.compareOperator} {criteria.compareValue}
</div>
)}
<div className="text-[11px] text-gray-500 dark:text-gray-400">
Approver: {criteria.approver}
</div>
{(criteria.kind === 'Approval' || criteria.kind === 'Inform') &&
criteria.approver && (
<div className="text-[11px] text-gray-500 dark:text-gray-400">
Approver: {criteria.approver}
</div>
)}
</div>
))}
</div>

View file

@ -257,17 +257,19 @@ export function WorkflowCriteria({
onChange={(event) => setField('title', event.target.value)}
/>
</FormItem>
<FormItem
label={translate('::App.Listform.ListformField.Approver')}
asterisk={formValues.kind === 'Approval' || formValues.kind === 'Inform'}
>
<SelectField
required={formValues.kind === 'Approval' || formValues.kind === 'Inform'}
options={userList}
value={formValues.approver}
onChange={(value) => setField('approver', value)}
/>
</FormItem>
{(formValues.kind === 'Approval' || formValues.kind === 'Inform') && (
<FormItem
label={translate('::App.Listform.ListformField.Approver')}
asterisk
>
<SelectField
required
options={userList}
value={formValues.approver}
onChange={(value) => setField('approver', value)}
/>
</FormItem>
)}
{(formValues.kind === 'Start' || formValues.kind === 'Inform') && (
<FormItem

View file

@ -42,6 +42,7 @@ type SqlDataType =
| 'decimal'
| 'float'
| 'bit'
| 'datetime'
| 'datetime2'
| 'date'
| 'uniqueidentifier'
@ -68,7 +69,7 @@ interface TableDesignerDialogProps {
isOpen: boolean
onClose: () => void
dataSource: string | null
onDeployed?: () => void
onDeployed?: (table: { schemaName: string; tableName: string }) => void | Promise<void>
initialTableData?: { schemaName: string; tableName: string } | null
}
@ -98,6 +99,7 @@ const DATA_TYPES: { value: SqlDataType; label: string }[] = [
{ value: 'decimal', label: 'Decimal (decimal 18,4)' },
{ value: 'float', label: 'Float (float)' },
{ value: 'bit', label: 'Bool (bit)' },
{ value: 'datetime', label: 'DateTime (datetime)' },
{ value: 'datetime2', label: 'DateTime (datetime2)' },
{ value: 'date', label: 'Date (date)' },
{ value: 'uniqueidentifier', label: 'Guid (uniqueidentifier)' },
@ -226,6 +228,45 @@ const TENANT_COLUMN: ColumnDefinition = {
description: 'Tenant ID for multi-tenancy',
}
const WORKFLOW_COLUMNS: ColumnDefinition[] = [
{
id: '__ApprovalUserId',
columnName: 'ApprovalUserName',
dataType: 'nvarchar',
maxLength: '256',
isNullable: true,
defaultValue: '',
description: 'Workflow approval user name',
},
{
id: '__ApprovalStatus',
columnName: 'ApprovalStatus',
dataType: 'nvarchar',
maxLength: '50',
isNullable: true,
defaultValue: '',
description: 'Workflow approval status',
},
{
id: '__ApprovalDate',
columnName: 'ApprovalDate',
dataType: 'datetime',
maxLength: '',
isNullable: true,
defaultValue: '',
description: 'Workflow approval date',
},
{
id: '__ApprovalDescription',
columnName: 'ApprovalDescription',
dataType: 'nvarchar',
maxLength: '200',
isNullable: true,
defaultValue: '',
description: 'Workflow approval description',
},
]
const CREATE_TABLE_SCRIPT_STORAGE_KEY = 'sqlQueryManager.lastCreateTableScript'
// ─── T-SQL Generator ──────────────────────────────────────────────────────────
@ -405,6 +446,8 @@ function dbColToColumnDef(col: {
dataType = 'float'
} else if (dt === 'bit') {
dataType = 'bit'
} else if (dt === 'datetime') {
dataType = 'datetime'
} else if (dt.startsWith('datetime') || dt === 'smalldatetime') {
dataType = 'datetime2'
} else if (dt === 'date') {
@ -476,6 +519,7 @@ function mapSqlTypeToDesigner(dataTypeRaw: string, lengthRaw: string): {
if (dt === 'decimal' || dt === 'numeric') return { dataType: 'decimal', maxLength: '' }
if (dt === 'float' || dt === 'real') return { dataType: 'float', maxLength: '' }
if (dt === 'bit') return { dataType: 'bit', maxLength: '' }
if (dt === 'datetime') return { dataType: 'datetime', maxLength: '' }
if (dt.startsWith('datetime') || dt === 'smalldatetime' || dt === 'time') {
return { dataType: 'datetime2', maxLength: '' }
}
@ -1224,6 +1268,18 @@ const SqlTableDesignerDialog = ({
}
}
const addWorkflowColumns = () => {
const existingNames = new Set(columns.map((c) => c.columnName.trim().toLowerCase()))
const toAdd = WORKFLOW_COLUMNS.filter((c) => !existingNames.has(c.columnName.toLowerCase()))
if (toAdd.length > 0) {
setColumns((prev) => {
const nonEmpty = prev.filter((c) => c.columnName.trim() !== '')
return [...nonEmpty, ...toAdd.map((c) => ({ ...c })), createEmptyColumn()]
})
}
}
const importColumnsFromRememberedCreateTable = async () => {
let script = ''
@ -1591,7 +1647,10 @@ const SqlTableDesignerDialog = ({
} catch {
// Non-blocking: seed file save failure does not affect deploy success
}
onDeployed?.()
await onDeployed?.({
schemaName: initialTableData?.schemaName || 'dbo',
tableName: deployedTable,
})
handleClose()
} else {
toast.push(
@ -1690,6 +1749,9 @@ const SqlTableDesignerDialog = ({
<Button size="xs" variant="solid" color="green-600" onClick={addMultiTenantColumns}>
{translate('::App.SqlQueryManager.AddMultiTenantColumns')}
</Button>
<Button size="xs" variant="solid" color="indigo-600" onClick={addWorkflowColumns}>
{translate('::App.SqlQueryManager.AddWorkflowColumns')}
</Button>
<Button
size="xs"
variant="solid"
@ -2678,7 +2740,7 @@ const SqlTableDesignerDialog = ({
// ── Render ─────────────────────────────────────────────────────────────────
return (
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose} width={900}>
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose} width={1100}>
<Dialog.Body className="flex flex-col gap-2">
{/* Header */}
<div className="flex items-center gap-3 border-b pb-3 flex-shrink-0">

View file

@ -244,7 +244,7 @@ const Grid = (props: GridProps) => {
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
const { translate } = useLocalization()
const { smaller } = useResponsive()
const config = useStoreState((state) => state.abpConfig.config)
const currentUser = useStoreState((state) => state.auth.user)
const gridRef = useRef<DataGridRef>()
const refListFormCode = useRef('')
@ -374,10 +374,10 @@ const Grid = (props: GridProps) => {
grd,
gridDto?.gridOptions.workflowDto,
selectedRowsData ?? grd.getSelectedRowsData(),
config?.currentUser,
currentUser,
)
},
[config?.currentUser, gridDto],
[currentUser, gridDto],
)
const refreshData = useCallback(() => {
@ -997,7 +997,7 @@ const Grid = (props: GridProps) => {
// Kolonları oluştur - dil değiştiğinde güncelle
const memoizedColumns = useMemo(() => {
if (!gridDto || !config) return undefined
if (!gridDto) return undefined
const cols = getBandedColumns()
@ -1038,7 +1038,7 @@ const Grid = (props: GridProps) => {
})
return cols
}, [gridDto, config])
}, [gridDto])
useEffect(() => {
setColumnData(memoizedColumns)

View file

@ -232,6 +232,7 @@ const Tree = (props: TreeProps) => {
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
const { translate } = useLocalization()
const { smaller } = useResponsive()
const currentUser = useStoreState((state) => state.auth.user)
const gridRef = useRef<TreeListRef>()
const refListFormCode = useRef('')
@ -251,7 +252,6 @@ const Tree = (props: TreeProps) => {
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)
const [expandedRowKeys, setExpandedRowKeys] = useState<any[]>([])
const config = useStoreState((state) => state.abpConfig.config)
type EditorOptionsWithButtons = {
buttons?: any[]
@ -409,10 +409,10 @@ const Tree = (props: TreeProps) => {
tree,
gridDto?.gridOptions.workflowDto,
selectedRowsData ?? tree.getSelectedRowsData(),
config?.currentUser,
currentUser,
)
},
[config?.currentUser, gridDto],
[currentUser, gridDto],
)
const refreshData = useCallback(() => {
@ -900,7 +900,7 @@ const Tree = (props: TreeProps) => {
}, [gridDto])
useEffect(() => {
if (!gridDto || !config) return
if (!gridDto) return
const cols = getBandedColumns()
setColumnData(cols)
@ -913,7 +913,7 @@ const Tree = (props: TreeProps) => {
cols,
)
setTreeListDataSource(dataSource)
}, [gridDto, searchParams, config])
}, [gridDto, searchParams])
useEffect(() => {
const activeFilters = extraFilters.filter((f) => f.value)

View file

@ -48,7 +48,7 @@ const useToolbar = ({
const { translate } = useLocalization()
const { checkPermission } = usePermission()
const isPwaMode = usePWA()
const config = useStoreState((state) => state.abpConfig.config)
const currentUser = useStoreState((state) => state.auth.user)
const [toolbarData, setToolbarData] = useState<ToolbarItem[]>([])
const [toolbarModalData, setToolbarModalData] = useState<ToolbarModalData>()
@ -204,7 +204,7 @@ const useToolbar = ({
row,
workflowOptions,
criteria.title,
getCurrentUserWorkflowIdentities(config?.currentUser),
getCurrentUserWorkflowIdentities(currentUser),
),
)
@ -483,10 +483,9 @@ const useToolbar = ({
useEffect(() => {
if (!gridDto && !listFormCode) return
if (!config) return
getToolbarData()
}, [gridDto, listFormCode, config])
}, [gridDto, listFormCode, currentUser])
return {
toolbarData,