toDo的一些设计与实现
This commit is contained in:
parent
dd0c750a2f
commit
37eaf35998
|
|
@ -1,2 +1,2 @@
|
|||
declare const checkers: (import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "token", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "mobile", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "message", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "parasite", import("..").RuntimeCxt>)[];
|
||||
declare const checkers: (import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "message", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "mobile", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "token", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").RuntimeCxt> | import("oak-domain").Checker<import("../oak-app-domain").EntityDict, "parasite", import("..").RuntimeCxt>)[];
|
||||
export default checkers;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Style } from '../../../../types/Style';
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../../oak-app-domain").EntityDict, keyof import("../../../../oak-app-domain").EntityDict, false, {
|
||||
style: Style;
|
||||
entity: "system" | "application" | "platform";
|
||||
entity: "application" | "system" | "platform";
|
||||
entityId: string;
|
||||
name: string;
|
||||
}>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
|
|||
type?: ButtonProps['type'] | AmButtonProps['type'];
|
||||
executeText?: string | undefined;
|
||||
buttonProps?: (ButtonProps & {
|
||||
color?: "primary" | "success" | "warning" | "default" | "danger" | undefined;
|
||||
color?: "default" | "success" | "warning" | "primary" | "danger" | undefined;
|
||||
fill?: "none" | "solid" | "outline" | undefined;
|
||||
size?: "small" | "large" | "middle" | "mini" | undefined;
|
||||
block?: boolean | undefined;
|
||||
|
|
@ -24,9 +24,9 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
|
|||
type?: "button" | "reset" | "submit" | undefined;
|
||||
shape?: "default" | "rounded" | "rectangular" | undefined;
|
||||
children?: import("react").ReactNode;
|
||||
} & Pick<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement>, "id" | "onMouseUp" | "onMouseDown" | "onTouchStart" | "onTouchEnd"> & {
|
||||
} & Pick<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement>, "id" | "onMouseDown" | "onMouseUp" | "onTouchEnd" | "onTouchStart"> & {
|
||||
className?: string | undefined;
|
||||
style?: (import("react").CSSProperties & Partial<Record<"--text-color" | "--background-color" | "--border-radius" | "--border-width" | "--border-style" | "--border-color", string>>) | undefined;
|
||||
style?: (import("react").CSSProperties & Partial<Record<"--background-color" | "--border-color" | "--text-color" | "--border-radius" | "--border-width" | "--border-style", string>>) | undefined;
|
||||
tabIndex?: number | undefined;
|
||||
} & import("react").AriaAttributes) | undefined;
|
||||
afterCommit?: AfterCommit;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,14 @@
|
|||
import { String, Text } from 'oak-domain/lib/types/DataType';
|
||||
import { EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
export type RedirectToProps = {
|
||||
pathname: string;
|
||||
props?: Record<string, any>;
|
||||
state?: Record<string, any>;
|
||||
};
|
||||
export type Condition = {
|
||||
condition?: any;
|
||||
batchPath: string;
|
||||
singlePath?: string;
|
||||
};
|
||||
export interface Schema extends EntityShape {
|
||||
title: Text;
|
||||
description?: Text;
|
||||
targetEntity: String<32>;
|
||||
targetEntityId?: String<64>;
|
||||
condition?: Condition;
|
||||
targetFilter: Object;
|
||||
action: String<32>;
|
||||
redirectTo: RedirectToProps;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
;
|
||||
const IActionDef = {
|
||||
stm: {
|
||||
done: ['active', 'done'],
|
||||
complete: ['active', 'done'],
|
||||
},
|
||||
};
|
||||
const entityDesc = {
|
||||
|
|
@ -13,8 +13,7 @@ const entityDesc = {
|
|||
title: '标题',
|
||||
description: '描述',
|
||||
targetEntity: '对象实体',
|
||||
targetEntityId: '对象实体Id',
|
||||
condition: '过滤条件',
|
||||
targetFilter: '过滤条件',
|
||||
action: '动作',
|
||||
redirectTo: '重定向页面',
|
||||
},
|
||||
|
|
@ -22,7 +21,7 @@ const entityDesc = {
|
|||
collaborator: '协作者',
|
||||
},
|
||||
action: {
|
||||
done: '完成',
|
||||
complete: '完成',
|
||||
},
|
||||
v: {
|
||||
iState: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Q_DateValue, Q_StringValue, NodeId, MakeFilter, ExprOp, ExpressionKey } from "oak-domain/lib/types/Demand";
|
||||
import { OneOf } from "oak-domain/lib/types/Polyfill";
|
||||
import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction } from "oak-domain/lib/types/Entity";
|
||||
import { ReadOnlyAction } from "oak-domain/lib/actions/action";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
import { String } from "oak-domain/lib/types/DataType";
|
||||
import { EntityShape } from "oak-domain/lib/types/Entity";
|
||||
export type OpSchema = EntityShape & {
|
||||
|
|
@ -91,7 +91,7 @@ export type I18nIdSubQuery = Selection<I18nIdProjection>;
|
|||
export type EntityDef = {
|
||||
Schema: Schema;
|
||||
OpSchema: OpSchema;
|
||||
Action: OakMakeAction<ReadOnlyAction> | string;
|
||||
Action: OakMakeAction<GenericAction> | string;
|
||||
Selection: Selection;
|
||||
Aggregation: Aggregation;
|
||||
Operation: Operation;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { readOnlyActions as actions } from "oak-domain/lib/actions/action";
|
||||
import { genericActions as actions } from "oak-domain/lib/actions/action";
|
||||
export const desc = {
|
||||
attributes: {
|
||||
module: {
|
||||
|
|
@ -35,7 +35,7 @@ export const desc = {
|
|||
}
|
||||
},
|
||||
static: true,
|
||||
actionType: "readOnly",
|
||||
actionType: "crud",
|
||||
actions,
|
||||
indexes: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ActionDef } from "oak-domain/lib/types/Action";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
export type IState = 'active' | 'done' | string;
|
||||
export type IAction = 'done' | string;
|
||||
export type IAction = 'complete' | string;
|
||||
export type ParticularAction = IAction;
|
||||
export declare const actions: string[];
|
||||
export type Action = GenericAction | ParticularAction | string;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
const IActionDef = {
|
||||
stm: {
|
||||
done: ['active', 'done'],
|
||||
complete: ['active', 'done'],
|
||||
},
|
||||
};
|
||||
export const actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "done"];
|
||||
export const actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "complete"];
|
||||
export const ActionDefDict = {
|
||||
iState: IActionDef
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,19 +9,14 @@ import { EntityShape } from "oak-domain/lib/types/Entity";
|
|||
import * as Relation from "../Relation/Schema";
|
||||
import * as UserRelation from "../UserRelation/Schema";
|
||||
export type RedirectToProps = {
|
||||
pathname: string;
|
||||
props?: Record<string, any>;
|
||||
state?: Record<string, any>;
|
||||
};
|
||||
export type Condition = {
|
||||
condition?: any;
|
||||
batchPath: string;
|
||||
singlePath?: string;
|
||||
};
|
||||
export type OpSchema = EntityShape & {
|
||||
title: Text;
|
||||
description?: Text | null;
|
||||
targetEntity: String<32>;
|
||||
targetEntityId?: String<64> | null;
|
||||
condition?: Condition | null;
|
||||
targetFilter: Object;
|
||||
action: String<32>;
|
||||
redirectTo: RedirectToProps;
|
||||
iState?: IState | null;
|
||||
|
|
@ -31,8 +26,7 @@ export type Schema = EntityShape & {
|
|||
title: Text;
|
||||
description?: Text | null;
|
||||
targetEntity: String<32>;
|
||||
targetEntityId?: String<64> | null;
|
||||
condition?: Condition | null;
|
||||
targetFilter: Object;
|
||||
action: String<32>;
|
||||
redirectTo: RedirectToProps;
|
||||
iState?: IState | null;
|
||||
|
|
@ -51,8 +45,7 @@ type AttrFilter = {
|
|||
title: Q_StringValue;
|
||||
description: Q_StringValue;
|
||||
targetEntity: Q_StringValue;
|
||||
targetEntityId: Q_StringValue;
|
||||
condition: JsonFilter<Condition>;
|
||||
targetFilter: Object;
|
||||
action: Q_StringValue;
|
||||
redirectTo: JsonFilter<RedirectToProps>;
|
||||
iState: Q_EnumValue<IState>;
|
||||
|
|
@ -70,8 +63,7 @@ export type Projection = {
|
|||
title?: number;
|
||||
description?: number;
|
||||
targetEntity?: number;
|
||||
targetEntityId?: number;
|
||||
condition?: number | JsonProjection<Condition>;
|
||||
targetFilter?: number | Object;
|
||||
action?: number;
|
||||
redirectTo?: number | JsonProjection<RedirectToProps>;
|
||||
iState?: number;
|
||||
|
|
@ -105,10 +97,6 @@ export type SortAttr = {
|
|||
description: number;
|
||||
} | {
|
||||
targetEntity: number;
|
||||
} | {
|
||||
targetEntityId: number;
|
||||
} | {
|
||||
condition: number;
|
||||
} | {
|
||||
action: number;
|
||||
} | {
|
||||
|
|
|
|||
|
|
@ -15,13 +15,8 @@ export const desc = {
|
|||
length: 32
|
||||
}
|
||||
},
|
||||
targetEntityId: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 64
|
||||
}
|
||||
},
|
||||
condition: {
|
||||
targetFilter: {
|
||||
notNull: true,
|
||||
type: "object"
|
||||
},
|
||||
action: {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{ "name": "待办", "attr": { "iState": "状态", "title": "标题", "description": "描述", "targetEntity": "对象实体", "targetEntityId": "对象实体Id", "condition": "过滤条件", "action": "动作", "redirectTo": "重定向页面" }, "r": { "collaborator": "协作者" }, "action": { "done": "完成" }, "v": { "iState": { "active": "待办", "done": "已完成" } } }
|
||||
{ "name": "待办", "attr": { "iState": "状态", "title": "标题", "description": "描述", "targetEntity": "对象实体", "targetFilter": "过滤条件", "action": "动作", "redirectTo": "重定向页面" }, "r": { "collaborator": "协作者" }, "action": { "complete": "完成" }, "v": { "iState": { "active": "待办", "done": "已完成" } } }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { EntityDict } from '../oak-app-domain';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
|
||||
/**
|
||||
* 创建todo例程,为entity对象上满足filter条件的,需要以action进行处理的行创建一个todo
|
||||
* @param entity
|
||||
* @param filter
|
||||
* @param action
|
||||
* @param userIds
|
||||
*/
|
||||
export declare function createToDo<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends BackendRuntimeContext<ED>>(entity: T, filter: ED[T]['Selection']['filter'], action: ED[T]['Action'], context: Cxt, data: {
|
||||
title: string;
|
||||
description?: string;
|
||||
redirectTo: EntityDict['toDo']['OpSchema']['redirectTo'];
|
||||
}, userIds?: string[]): Promise<1 | 0>;
|
||||
/**
|
||||
* 完成todo例程,当在entity对象上进行action操作时(操作条件是filter),将对应的todo完成
|
||||
* 必须在entity的action的后trigger中调用
|
||||
* @param entity
|
||||
* @param filter 传入的filter限定查询todo的范围,在todo中的targetFilter查找相同限制下的行,和创建toDo要保持一致(但要考虑action可能造成的数据变化)
|
||||
* @param action
|
||||
* @param context
|
||||
*/
|
||||
export declare function completeToDo<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends BackendRuntimeContext<ED>>(entity: T, filter: ED[T]['Selection']['filter'], action: ED[T]['Action'], context: Cxt): Promise<number>;
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import { uniq } from 'oak-domain/lib/utils/lodash';
|
||||
import { getUserRelationsByActions } from 'oak-domain/lib/store/RelationAuth';
|
||||
import { generateNewIdAsync } from 'oak-domain';
|
||||
import assert from 'assert';
|
||||
/**
|
||||
* 创建todo例程,为entity对象上满足filter条件的,需要以action进行处理的行创建一个todo
|
||||
* @param entity
|
||||
* @param filter
|
||||
* @param action
|
||||
* @param userIds
|
||||
*/
|
||||
export async function createToDo(entity, filter, action, context, data, userIds) {
|
||||
const count = await context.count('toDo', {
|
||||
filter: {
|
||||
targetEntity: entity,
|
||||
targetFilter: JSON.stringify(filter),
|
||||
action,
|
||||
iState: 'active',
|
||||
},
|
||||
count: 1
|
||||
}, {});
|
||||
// 已有则无需创建
|
||||
if (count > 0) {
|
||||
return 0;
|
||||
}
|
||||
let userIds2 = userIds;
|
||||
if (!userIds2) {
|
||||
// 为对此对象有此action的用户创建userRelation
|
||||
const { userRelations, userEntities } = await getUserRelationsByActions({
|
||||
entity,
|
||||
filter,
|
||||
actions: [action],
|
||||
}, context);
|
||||
userIds2 = uniq(userRelations.map(ele => ele.userId).concat(userEntities.map(ele => ele.userId)));
|
||||
}
|
||||
const [relation] = await context.select('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: 'toDo',
|
||||
name: 'collaborator',
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
await context.operate('toDo', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await generateNewIdAsync(),
|
||||
targetEntity: entity,
|
||||
targetFilter: filter,
|
||||
action,
|
||||
...data,
|
||||
userRelation$entity: await Promise.all(userIds2.map(async (userId) => ({
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await generateNewIdAsync(),
|
||||
userId,
|
||||
relationId: relation.id,
|
||||
}
|
||||
})))
|
||||
},
|
||||
}, { dontCollect: true });
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* 完成todo例程,当在entity对象上进行action操作时(操作条件是filter),将对应的todo完成
|
||||
* 必须在entity的action的后trigger中调用
|
||||
* @param entity
|
||||
* @param filter 传入的filter限定查询todo的范围,在todo中的targetFilter查找相同限制下的行,和创建toDo要保持一致(但要考虑action可能造成的数据变化)
|
||||
* @param action
|
||||
* @param context
|
||||
*/
|
||||
export async function completeToDo(entity, filter, action, context) {
|
||||
const toDos = await context.select('toDo', {
|
||||
data: {
|
||||
id: 1,
|
||||
iState: 1,
|
||||
targetEntity: 1,
|
||||
targetFilter: 1,
|
||||
action: 1,
|
||||
},
|
||||
filter: {
|
||||
targetEntity: entity,
|
||||
targetFilter: filter,
|
||||
action,
|
||||
}
|
||||
}, {});
|
||||
assert(toDos.length > 0, `对${entity}相关的todo进行完成操作时,找不到对应的数据。filter是${JSON.stringify(filter)}`);
|
||||
let completed = 0;
|
||||
for (const toDo of toDos) {
|
||||
const { id, targetEntity, targetFilter, action } = toDo;
|
||||
const count = await context.count(targetEntity, {
|
||||
filter: targetFilter,
|
||||
count: 1,
|
||||
}, {});
|
||||
if (count === 0) {
|
||||
await context.operate('toDo', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'complete',
|
||||
data: {},
|
||||
filter: {
|
||||
id: id,
|
||||
}
|
||||
}, {});
|
||||
completed++;
|
||||
}
|
||||
}
|
||||
return completed;
|
||||
}
|
||||
|
|
@ -1,19 +1,14 @@
|
|||
import { String, Text } from 'oak-domain/lib/types/DataType';
|
||||
import { EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
export type RedirectToProps = {
|
||||
pathname: string;
|
||||
props?: Record<string, any>;
|
||||
state?: Record<string, any>;
|
||||
};
|
||||
export type Condition = {
|
||||
condition?: any;
|
||||
batchPath: string;
|
||||
singlePath?: string;
|
||||
};
|
||||
export interface Schema extends EntityShape {
|
||||
title: Text;
|
||||
description?: Text;
|
||||
targetEntity: String<32>;
|
||||
targetEntityId?: String<64>;
|
||||
condition?: Condition;
|
||||
targetFilter: Object;
|
||||
action: String<32>;
|
||||
redirectTo: RedirectToProps;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
;
|
||||
const IActionDef = {
|
||||
stm: {
|
||||
done: ['active', 'done'],
|
||||
complete: ['active', 'done'],
|
||||
},
|
||||
};
|
||||
const entityDesc = {
|
||||
|
|
@ -15,8 +15,7 @@ const entityDesc = {
|
|||
title: '标题',
|
||||
description: '描述',
|
||||
targetEntity: '对象实体',
|
||||
targetEntityId: '对象实体Id',
|
||||
condition: '过滤条件',
|
||||
targetFilter: '过滤条件',
|
||||
action: '动作',
|
||||
redirectTo: '重定向页面',
|
||||
},
|
||||
|
|
@ -24,7 +23,7 @@ const entityDesc = {
|
|||
collaborator: '协作者',
|
||||
},
|
||||
action: {
|
||||
done: '完成',
|
||||
complete: '完成',
|
||||
},
|
||||
v: {
|
||||
iState: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Q_DateValue, Q_StringValue, NodeId, MakeFilter, ExprOp, ExpressionKey } from "oak-domain/lib/types/Demand";
|
||||
import { OneOf } from "oak-domain/lib/types/Polyfill";
|
||||
import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction } from "oak-domain/lib/types/Entity";
|
||||
import { ReadOnlyAction } from "oak-domain/lib/actions/action";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
import { String } from "oak-domain/lib/types/DataType";
|
||||
import { EntityShape } from "oak-domain/lib/types/Entity";
|
||||
export type OpSchema = EntityShape & {
|
||||
|
|
@ -91,7 +91,7 @@ export type I18nIdSubQuery = Selection<I18nIdProjection>;
|
|||
export type EntityDef = {
|
||||
Schema: Schema;
|
||||
OpSchema: OpSchema;
|
||||
Action: OakMakeAction<ReadOnlyAction> | string;
|
||||
Action: OakMakeAction<GenericAction> | string;
|
||||
Selection: Selection;
|
||||
Aggregation: Aggregation;
|
||||
Operation: Operation;
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ exports.desc = {
|
|||
}
|
||||
},
|
||||
static: true,
|
||||
actionType: "readOnly",
|
||||
actions: action_1.readOnlyActions,
|
||||
actionType: "crud",
|
||||
actions: action_1.genericActions,
|
||||
indexes: [
|
||||
{
|
||||
name: 'namespace_language',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ActionDef } from "oak-domain/lib/types/Action";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
export type IState = 'active' | 'done' | string;
|
||||
export type IAction = 'done' | string;
|
||||
export type IAction = 'complete' | string;
|
||||
export type ParticularAction = IAction;
|
||||
export declare const actions: string[];
|
||||
export type Action = GenericAction | ParticularAction | string;
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
exports.ActionDefDict = exports.actions = void 0;
|
||||
const IActionDef = {
|
||||
stm: {
|
||||
done: ['active', 'done'],
|
||||
complete: ['active', 'done'],
|
||||
},
|
||||
};
|
||||
exports.actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "done"];
|
||||
exports.actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "complete"];
|
||||
exports.ActionDefDict = {
|
||||
iState: IActionDef
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,19 +9,14 @@ import { EntityShape } from "oak-domain/lib/types/Entity";
|
|||
import * as Relation from "../Relation/Schema";
|
||||
import * as UserRelation from "../UserRelation/Schema";
|
||||
export type RedirectToProps = {
|
||||
pathname: string;
|
||||
props?: Record<string, any>;
|
||||
state?: Record<string, any>;
|
||||
};
|
||||
export type Condition = {
|
||||
condition?: any;
|
||||
batchPath: string;
|
||||
singlePath?: string;
|
||||
};
|
||||
export type OpSchema = EntityShape & {
|
||||
title: Text;
|
||||
description?: Text | null;
|
||||
targetEntity: String<32>;
|
||||
targetEntityId?: String<64> | null;
|
||||
condition?: Condition | null;
|
||||
targetFilter: Object;
|
||||
action: String<32>;
|
||||
redirectTo: RedirectToProps;
|
||||
iState?: IState | null;
|
||||
|
|
@ -31,8 +26,7 @@ export type Schema = EntityShape & {
|
|||
title: Text;
|
||||
description?: Text | null;
|
||||
targetEntity: String<32>;
|
||||
targetEntityId?: String<64> | null;
|
||||
condition?: Condition | null;
|
||||
targetFilter: Object;
|
||||
action: String<32>;
|
||||
redirectTo: RedirectToProps;
|
||||
iState?: IState | null;
|
||||
|
|
@ -51,8 +45,7 @@ type AttrFilter = {
|
|||
title: Q_StringValue;
|
||||
description: Q_StringValue;
|
||||
targetEntity: Q_StringValue;
|
||||
targetEntityId: Q_StringValue;
|
||||
condition: JsonFilter<Condition>;
|
||||
targetFilter: Object;
|
||||
action: Q_StringValue;
|
||||
redirectTo: JsonFilter<RedirectToProps>;
|
||||
iState: Q_EnumValue<IState>;
|
||||
|
|
@ -70,8 +63,7 @@ export type Projection = {
|
|||
title?: number;
|
||||
description?: number;
|
||||
targetEntity?: number;
|
||||
targetEntityId?: number;
|
||||
condition?: number | JsonProjection<Condition>;
|
||||
targetFilter?: number | Object;
|
||||
action?: number;
|
||||
redirectTo?: number | JsonProjection<RedirectToProps>;
|
||||
iState?: number;
|
||||
|
|
@ -105,10 +97,6 @@ export type SortAttr = {
|
|||
description: number;
|
||||
} | {
|
||||
targetEntity: number;
|
||||
} | {
|
||||
targetEntityId: number;
|
||||
} | {
|
||||
condition: number;
|
||||
} | {
|
||||
action: number;
|
||||
} | {
|
||||
|
|
|
|||
|
|
@ -18,13 +18,8 @@ exports.desc = {
|
|||
length: 32
|
||||
}
|
||||
},
|
||||
targetEntityId: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 64
|
||||
}
|
||||
},
|
||||
condition: {
|
||||
targetFilter: {
|
||||
notNull: true,
|
||||
type: "object"
|
||||
},
|
||||
action: {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{ "name": "待办", "attr": { "iState": "状态", "title": "标题", "description": "描述", "targetEntity": "对象实体", "targetEntityId": "对象实体Id", "condition": "过滤条件", "action": "动作", "redirectTo": "重定向页面" }, "r": { "collaborator": "协作者" }, "action": { "done": "完成" }, "v": { "iState": { "active": "待办", "done": "已完成" } } }
|
||||
{ "name": "待办", "attr": { "iState": "状态", "title": "标题", "description": "描述", "targetEntity": "对象实体", "targetFilter": "过滤条件", "action": "动作", "redirectTo": "重定向页面" }, "r": { "collaborator": "协作者" }, "action": { "complete": "完成" }, "v": { "iState": { "active": "待办", "done": "已完成" } } }
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
declare const _default: (import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "account", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").RuntimeCxt> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>>)[];
|
||||
declare const _default: (import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").RuntimeCxt> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain").Trigger<import("../oak-app-domain").EntityDict, "account", import("..").BackendRuntimeContext<import("../oak-app-domain").EntityDict>>)[];
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { EntityDict } from '../oak-app-domain';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
|
||||
/**
|
||||
* 创建todo例程,为entity对象上满足filter条件的,需要以action进行处理的行创建一个todo
|
||||
* @param entity
|
||||
* @param filter
|
||||
* @param action
|
||||
* @param userIds
|
||||
*/
|
||||
export declare function createToDo<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends BackendRuntimeContext<ED>>(entity: T, filter: ED[T]['Selection']['filter'], action: ED[T]['Action'], context: Cxt, data: {
|
||||
title: string;
|
||||
description?: string;
|
||||
redirectTo: EntityDict['toDo']['OpSchema']['redirectTo'];
|
||||
}, userIds?: string[]): Promise<0 | 1>;
|
||||
/**
|
||||
* 完成todo例程,当在entity对象上进行action操作时(操作条件是filter),将对应的todo完成
|
||||
* 必须在entity的action的后trigger中调用
|
||||
* @param entity
|
||||
* @param filter 传入的filter限定查询todo的范围,在todo中的targetFilter查找相同限制下的行,和创建toDo要保持一致(但要考虑action可能造成的数据变化)
|
||||
* @param action
|
||||
* @param context
|
||||
*/
|
||||
export declare function completeToDo<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends BackendRuntimeContext<ED>>(entity: T, filter: ED[T]['Selection']['filter'], action: ED[T]['Action'], context: Cxt): Promise<number>;
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.completeToDo = exports.createToDo = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const lodash_1 = require("oak-domain/lib/utils/lodash");
|
||||
const RelationAuth_1 = require("oak-domain/lib/store/RelationAuth");
|
||||
const oak_domain_1 = require("oak-domain");
|
||||
const assert_1 = tslib_1.__importDefault(require("assert"));
|
||||
/**
|
||||
* 创建todo例程,为entity对象上满足filter条件的,需要以action进行处理的行创建一个todo
|
||||
* @param entity
|
||||
* @param filter
|
||||
* @param action
|
||||
* @param userIds
|
||||
*/
|
||||
async function createToDo(entity, filter, action, context, data, userIds) {
|
||||
const count = await context.count('toDo', {
|
||||
filter: {
|
||||
targetEntity: entity,
|
||||
targetFilter: JSON.stringify(filter),
|
||||
action,
|
||||
iState: 'active',
|
||||
},
|
||||
count: 1
|
||||
}, {});
|
||||
// 已有则无需创建
|
||||
if (count > 0) {
|
||||
return 0;
|
||||
}
|
||||
let userIds2 = userIds;
|
||||
if (!userIds2) {
|
||||
// 为对此对象有此action的用户创建userRelation
|
||||
const { userRelations, userEntities } = await (0, RelationAuth_1.getUserRelationsByActions)({
|
||||
entity,
|
||||
filter,
|
||||
actions: [action],
|
||||
}, context);
|
||||
userIds2 = (0, lodash_1.uniq)(userRelations.map(ele => ele.userId).concat(userEntities.map(ele => ele.userId)));
|
||||
}
|
||||
const [relation] = await context.select('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: 'toDo',
|
||||
name: 'collaborator',
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
await context.operate('toDo', {
|
||||
id: await (0, oak_domain_1.generateNewIdAsync)(),
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await (0, oak_domain_1.generateNewIdAsync)(),
|
||||
targetEntity: entity,
|
||||
targetFilter: filter,
|
||||
action,
|
||||
...data,
|
||||
userRelation$entity: await Promise.all(userIds2.map(async (userId) => ({
|
||||
id: await (0, oak_domain_1.generateNewIdAsync)(),
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await (0, oak_domain_1.generateNewIdAsync)(),
|
||||
userId,
|
||||
relationId: relation.id,
|
||||
}
|
||||
})))
|
||||
},
|
||||
}, { dontCollect: true });
|
||||
return 1;
|
||||
}
|
||||
exports.createToDo = createToDo;
|
||||
/**
|
||||
* 完成todo例程,当在entity对象上进行action操作时(操作条件是filter),将对应的todo完成
|
||||
* 必须在entity的action的后trigger中调用
|
||||
* @param entity
|
||||
* @param filter 传入的filter限定查询todo的范围,在todo中的targetFilter查找相同限制下的行,和创建toDo要保持一致(但要考虑action可能造成的数据变化)
|
||||
* @param action
|
||||
* @param context
|
||||
*/
|
||||
async function completeToDo(entity, filter, action, context) {
|
||||
const toDos = await context.select('toDo', {
|
||||
data: {
|
||||
id: 1,
|
||||
iState: 1,
|
||||
targetEntity: 1,
|
||||
targetFilter: 1,
|
||||
action: 1,
|
||||
},
|
||||
filter: {
|
||||
targetEntity: entity,
|
||||
targetFilter: filter,
|
||||
action,
|
||||
}
|
||||
}, {});
|
||||
(0, assert_1.default)(toDos.length > 0, `对${entity}相关的todo进行完成操作时,找不到对应的数据。filter是${JSON.stringify(filter)}`);
|
||||
let completed = 0;
|
||||
for (const toDo of toDos) {
|
||||
const { id, targetEntity, targetFilter, action } = toDo;
|
||||
const count = await context.count(targetEntity, {
|
||||
filter: targetFilter,
|
||||
count: 1,
|
||||
}, {});
|
||||
if (count === 0) {
|
||||
await context.operate('toDo', {
|
||||
id: await (0, oak_domain_1.generateNewIdAsync)(),
|
||||
action: 'complete',
|
||||
data: {},
|
||||
filter: {
|
||||
id: id,
|
||||
}
|
||||
}, {});
|
||||
completed++;
|
||||
}
|
||||
}
|
||||
return completed;
|
||||
}
|
||||
exports.completeToDo = completeToDo;
|
||||
|
|
@ -6,35 +6,28 @@ import { ActionDef } from 'oak-domain/lib/types/Action';
|
|||
|
||||
|
||||
export type RedirectToProps = {
|
||||
pathname: string;
|
||||
props?: Record<string, any>;
|
||||
state?: Record<string, any>;
|
||||
};
|
||||
|
||||
export type Condition = {
|
||||
condition?: any;
|
||||
batchPath: string;
|
||||
singlePath?: string;
|
||||
};
|
||||
|
||||
export interface Schema extends EntityShape {
|
||||
title: Text;
|
||||
description?: Text;
|
||||
targetEntity: String<32>;
|
||||
targetEntityId?: String<64>;
|
||||
condition?: Condition;
|
||||
targetFilter: Object;
|
||||
action: String<32>;
|
||||
redirectTo: RedirectToProps;
|
||||
|
||||
};
|
||||
|
||||
type Relation = 'collaborator';
|
||||
type IState = 'active' | 'done'
|
||||
|
||||
type IAction = 'done'; //触发器执行
|
||||
type IAction = 'complete'; //触发器执行
|
||||
|
||||
|
||||
const IActionDef: ActionDef<IAction, IState> = {
|
||||
stm: {
|
||||
done: ['active', 'done'],
|
||||
complete: ['active', 'done'],
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -57,8 +50,7 @@ const entityDesc: EntityDesc<
|
|||
title: '标题',
|
||||
description: '描述',
|
||||
targetEntity: '对象实体',
|
||||
targetEntityId: '对象实体Id',
|
||||
condition: '过滤条件',
|
||||
targetFilter: '过滤条件',
|
||||
action: '动作',
|
||||
redirectTo: '重定向页面',
|
||||
},
|
||||
|
|
@ -66,7 +58,7 @@ const entityDesc: EntityDesc<
|
|||
collaborator: '协作者',
|
||||
},
|
||||
action: {
|
||||
done: '完成',
|
||||
complete: '完成',
|
||||
},
|
||||
v: {
|
||||
iState: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
import { EntityDict } from '../oak-app-domain';
|
||||
import { uniq } from 'oak-domain/lib/utils/lodash';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
|
||||
import { getUserRelationsByActions } from 'oak-domain/lib/store/RelationAuth';
|
||||
import { generateNewIdAsync } from 'oak-domain';
|
||||
import assert from 'assert';
|
||||
|
||||
/**
|
||||
* 创建todo例程,为entity对象上满足filter条件的,需要以action进行处理的行创建一个todo
|
||||
* @param entity
|
||||
* @param filter
|
||||
* @param action
|
||||
* @param userIds
|
||||
*/
|
||||
export async function createToDo<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends BackendRuntimeContext<ED>>(
|
||||
entity: T,
|
||||
filter: ED[T]['Selection']['filter'],
|
||||
action: ED[T]['Action'],
|
||||
context: Cxt,
|
||||
data: {
|
||||
title: string,
|
||||
description?: string,
|
||||
redirectTo: EntityDict['toDo']['OpSchema']['redirectTo'],
|
||||
},
|
||||
userIds?: string[]) {
|
||||
|
||||
const count = await context.count('toDo', {
|
||||
filter: {
|
||||
targetEntity: entity as string,
|
||||
targetFilter: JSON.stringify(filter),
|
||||
action,
|
||||
iState: 'active',
|
||||
},
|
||||
count: 1
|
||||
}, {});
|
||||
// 已有则无需创建
|
||||
if (count > 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let userIds2 = userIds;
|
||||
if (!userIds2) {
|
||||
// 为对此对象有此action的用户创建userRelation
|
||||
const { userRelations, userEntities } = await getUserRelationsByActions({
|
||||
entity,
|
||||
filter,
|
||||
actions: [action],
|
||||
}, context);
|
||||
|
||||
userIds2 = uniq(userRelations.map(ele => ele.userId).concat(userEntities.map(ele => ele.userId)));
|
||||
}
|
||||
|
||||
const [relation] = await context.select('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: 'toDo',
|
||||
name: 'collaborator',
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
|
||||
await context.operate('toDo', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await generateNewIdAsync(),
|
||||
targetEntity: entity as string,
|
||||
targetFilter: filter,
|
||||
action,
|
||||
...data,
|
||||
userRelation$entity:
|
||||
await Promise.all(userIds2!.map(
|
||||
async (userId) => ({
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await generateNewIdAsync(),
|
||||
userId,
|
||||
relationId: relation!.id,
|
||||
}
|
||||
}
|
||||
)
|
||||
))
|
||||
},
|
||||
}, { dontCollect: true });
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成todo例程,当在entity对象上进行action操作时(操作条件是filter),将对应的todo完成
|
||||
* 必须在entity的action的后trigger中调用
|
||||
* @param entity
|
||||
* @param filter 传入的filter限定查询todo的范围,在todo中的targetFilter查找相同限制下的行,和创建toDo要保持一致(但要考虑action可能造成的数据变化)
|
||||
* @param action
|
||||
* @param context
|
||||
*/
|
||||
export async function completeToDo<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends BackendRuntimeContext<ED>>(
|
||||
entity: T,
|
||||
filter: ED[T]['Selection']['filter'],
|
||||
action: ED[T]['Action'],
|
||||
context: Cxt) {
|
||||
const toDos = await context.select('toDo', {
|
||||
data: {
|
||||
id: 1,
|
||||
iState: 1,
|
||||
targetEntity: 1,
|
||||
targetFilter: 1,
|
||||
action: 1,
|
||||
},
|
||||
filter: {
|
||||
targetEntity: entity as string,
|
||||
targetFilter: filter,
|
||||
action,
|
||||
}
|
||||
}, {});
|
||||
assert(toDos.length > 0, `对${entity as string}相关的todo进行完成操作时,找不到对应的数据。filter是${JSON.stringify(filter)}`);
|
||||
|
||||
let completed = 0;
|
||||
for (const toDo of toDos) {
|
||||
const { id, targetEntity, targetFilter, action } = toDo;
|
||||
const count = await context.count(targetEntity!, {
|
||||
filter: targetFilter,
|
||||
count: 1,
|
||||
}, {});
|
||||
if (count === 0) {
|
||||
await context.operate('toDo', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'complete',
|
||||
data: {},
|
||||
filter: {
|
||||
id: id!,
|
||||
}
|
||||
}, {});
|
||||
completed ++;
|
||||
}
|
||||
}
|
||||
|
||||
return completed;
|
||||
}
|
||||
Loading…
Reference in New Issue