403Webshell
Server IP : 80.87.202.40  /  Your IP : 216.73.216.169
Web Server : Apache
System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64
User : bitrix ( 600)
PHP Version : 8.2.27
Disable Function : NONE
MySQL : OFF |  cURL : ON |  WGET : ON |  Perl : ON |  Python : OFF |  Sudo : ON |  Pkexec : ON
Directory :  /home/bitrix/ext_www/rospirotorg.ru/bitrix/js/bizproc/automation/src/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/rospirotorg.ru/bitrix/js/bizproc/automation/src/template.js
import { Type, Dom, Loc, Event, Runtime, Uri, Text, Tag, ajax } from 'main.core';
import { BaseEvent, EventEmitter } from 'main.core.events';
import {
	Context,
	SelectorContext,
	getGlobalContext,
	Tracker,
	Designer,
	ConditionGroupSelector,
	ConditionGroup,
	DelayIntervalSelector,
	SelectorManager, SelectorItemsManager, enrichFieldsWithModifiers,
} from 'bizproc.automation';
import { SaveButton, BaseButton, CancelButton } from 'ui.buttons';
import { UI } from 'ui.notification';
import { Robot } from './robot';
import { TrackingStatus } from './tracker/types';
import { UserOptions } from './user-options';
import { ViewMode } from './view-mode';
import { Helper } from './helper';
import { HelpHint } from './help-hint';
import { DelayInterval } from './delay-interval';
import { Popup } from 'main.popup';

import 'ui.hint';
import showExecutionQueuePopup from './views/execution-queue-popup';

export class Template extends EventEmitter
{
	#context: Context;

	constants: Object<string, any>;
	variables: Object<string, any>;
	robotSettingsControls;

	#delayMinLimitM: number;
	#userOptions: UserOptions | null;
	#tracker: Tracker;
	#viewMode: ViewMode;

	#templateContainerNode: Element;
	#templateNode: ?Element;
	#listNode: Element | undefined;
	#buttonsNode: Element | undefined;

	#robots: Array<Robot>;
	#data: Object;

	constructor(params: {
		context: ?Context,
		templateContainerNode: Element,
		constants: Object<string, any>,
		variables: Object<string, any>,
		userOptions: ?UserOptions,
		delayMinLimitM: number,
	})
	{
		super();
		this.setEventNamespace('BX.Bizproc.Automation');

		this.#context = params.context ?? getGlobalContext();
		this.constants = params.constants;
		this.variables = params.variables;

		this.#templateContainerNode = params.templateContainerNode;
		this.#delayMinLimitM = params.delayMinLimitM;
		this.#userOptions = params.userOptions;
		this.#tracker = this.#context.tracker;
		this.#data = {};
		this.#robots = [];
		this.#viewMode = ViewMode.none();
	}

	init(data: Object, viewMode: number)
	{
		if (Type.isPlainObject(data))
		{
			this.#data = data;
			if (!Type.isPlainObject(this.#data.CONSTANTS))
			{
				this.#data.CONSTANTS = {};
			}

			if (!Type.isPlainObject(this.#data.PARAMETERS))
			{
				this.#data.PARAMETERS = {};
			}

			if (!Type.isPlainObject(this.#data.VARIABLES))
			{
				this.#data.VARIABLES = {};
			}

			if (!Type.isNil(this.#data.DOCUMENT_STATUS))
			{
				this.#data.DOCUMENT_STATUS = String(this.#data.DOCUMENT_STATUS);
			}

			this.markExternalModified(this.#data.IS_EXTERNAL_MODIFIED);
			this.markModified(false);
		}

		this.#viewMode = ViewMode.fromRaw(viewMode);

		if (!this.#viewMode.isNone())
		{
			this.#templateNode = this.#templateContainerNode.querySelector(
				`[data-role="automation-template"][data-status-id="${this.#data.DOCUMENT_STATUS}"]`,
			);
			this.#listNode = this.#templateNode.querySelector('[data-role="robot-list"]');
			this.#buttonsNode = this.#templateNode.querySelector('[data-role="buttons"]');

			this.initRobots();
			this.initButtons();

			if (!this.isExternalModified() && this.canEdit())
			{
				// register DD
				jsDD.registerDest(this.#templateNode, 10);
			}
			else
			{
				jsDD.unregisterDest(this.#templateNode);
			}
		}
	}

	reInit(data: Object, viewMode: number)
	{
		Dom.clean(this.#listNode);
		Dom.clean(this.#buttonsNode);

		this.destroy();
		this.init(data, viewMode);
	}

	destroy()
	{
		this.#robots.forEach((robot) => robot.destroy());
	}

	static copyRobotTo(dstTemplate: Template, robot: Robot, beforeRobot: ?Robot): Robot
	{
		const copiedRobot = robot.copyTo(dstTemplate, beforeRobot);
		dstTemplate.emit('Template:robot:add', { robot: copiedRobot });
	}

	canEdit(): boolean
	{
		return this.#context.canEdit;
	}

	initRobots()
	{
		this.#robots = [];

		if (Type.isArray(this.#data.ROBOTS))
		{
			for (let i = 0; i < this.#data.ROBOTS.length; ++i)
			{
				const robot = new Robot({
					document: this.#context.document,
					template: this,
					isFrameMode: this.#context.get('isFrameMode'),
					tracker: this.#tracker,
				});
				robot.init(this.#data.ROBOTS[i], this.#viewMode);
				this.insertRobotNode(robot.node);
				this.#robots.push(robot);
			}
		}
	}

	get robots(): Array<Robot>
	{
		return this.#robots;
	}

	get userOptions(): ?UserOptions
	{
		return this.#userOptions;
	}

	getSelectedRobotNames(): Array<Robot>
	{
		const selectedRobots = [];

		this.#robots.forEach((robot) => {
			if (robot.isSelected())
			{
				selectedRobots.push(robot.data.Name);
			}
		});

		return selectedRobots;
	}

	getActivatedRobotNames(): Array<string>
	{
		const activatedRobots = [];
		this.#robots.forEach((robot) => {
			if (robot.isActivated())
			{
				activatedRobots.push(robot.data.Name);
			}
		});

		return activatedRobots;
	}

	getDeactivatedRobotNames(): Array<string>
	{
		const deactivatedRobots = [];
		this.#robots.forEach((robot) => {
			if (!robot.isActivated())
			{
				deactivatedRobots.push(robot.data.Name);
			}
		});

		return deactivatedRobots;
	}

	getSerializedRobots(): []
	{
		const serialized = [];
		this.#robots.forEach((robot) => serialized.push(robot.serialize()));

		return serialized;
	}

	getId()
	{
		return this.#data.ID;
	}

	getStatusId(): ?string
	{
		return this.#data.DOCUMENT_STATUS;
	}

	getStatus(): ?{}
	{
		return this.#context.document.statusList.find((status) => String(status.STATUS_ID) === this.getStatusId());
	}

	getTemplateId(): number
	{
		const id = parseInt(this.#data.ID, 10);

		return Number.isNaN(id) ? 0 : id;
	}

	getDocumentType(): ?Array
	{
		return this.#data.DOCUMENT_TYPE;
	}

	initButtons()
	{
		if (this.isExternalModified())
		{
			this.createExternalLocker();
			this.createManageModeButton();
			this.createTerminateRobotsButton();
		}
		else if (this.#viewMode.isEdit() && this.getTemplateId() > 0)
		{
			this.createConstantsEditButton();
			this.createParametersEditButton();
			this.createExternalEditTemplateButton();
			this.createManageModeButton();
			this.createTerminateRobotsButton();
		}
	}

	enableManageMode(isActive: boolean)
	{
		if (this.#listNode)
		{
			this.#viewMode = ViewMode.manage().setProperty('isActive', isActive);

			if (isActive)
			{
				Dom.addClass(this.#listNode, '--multiselect-mode');
			}

			if (this.isExternalModified())
			{
				Dom.addClass(this.#listNode, '--locked-node');
			}
			else
			{
				this.#robots.forEach((robot) => {
					if (robot.isInvalid())
					{
						robot.enableManageMode(false);
					}
					else
					{
						robot.enableManageMode(isActive);
					}
				});
			}
		}
	}

	disableManageMode()
	{
		if (this.#listNode)
		{
			this.#viewMode = ViewMode.edit();
			Dom.removeClass(this.#listNode, '--multiselect-mode');
			if (this.isExternalModified())
			{
				Dom.removeClass(this.#listNode, '--locked-node');
			}
			else
			{
				this.#robots.forEach((robot) => {
					robot.disableManageMode();

					if (!robot.isInvalid())
					{
						const draggableNode = robot.node.querySelector('.bizproc-automation-robot-container-wrapper');
						if (draggableNode)
						{
							Dom.addClass(draggableNode, 'bizproc-automation-robot-container-wrapper-draggable');
						}
					}
				});
			}
		}
	}

	enableDragAndDrop()
	{
		this.#robots.forEach((robot) => {
			if (!robot.isInvalid())
			{
				robot.registerItem(robot.node);

				const draggableNode = robot.node.querySelector('.bizproc-automation-robot-container-wrapper');
				if (draggableNode)
				{
					Dom.addClass(draggableNode, 'bizproc-automation-robot-container-wrapper-draggable');
				}
			}
		});
	}

	disableDragAndDrop()
	{
		this.#robots.forEach((robot) => robot.unregisterItem(robot.node));

		this.#templateNode.querySelectorAll('.bizproc-automation-robot-container-wrapper').forEach((node) => {
			Dom.removeClass(node, 'bizproc-automation-robot-container-wrapper-draggable');
		});
	}

	createExternalEditTemplateButton(): undefined | boolean
	{
		if (Type.isNil(this.#context.bizprocEditorUrl))
		{
			return false;
		}

		const anchor = Tag.render`
			<a class="bizproc-automation-robot-btn-set" href="#" target="_top">
				${Loc.getMessage('BIZPROC_AUTOMATION_CMP_EXTERNAL_EDIT')}
			</a>
		`;
		Event.bind(anchor, 'click', (event) => {
			event.preventDefault();

			if (!this.#viewMode.isManage())
			{
				this.onExternalEditTemplateButtonClick(anchor);
			}
		});

		if (this.#context.bizprocEditorUrl.length === 0)
		{
			Dom.addClass(anchor, 'bizproc-automation-robot-btn-set-locked');
		}

		Dom.append(anchor, this.#buttonsNode);
	}

	createManageModeButton()
	{
		if (!this.#context.canManage)
		{
			return;
		}

		const manageButton = Tag.render`
			<a class="bizproc-automation-robot-btn-set" target="_top" style="cursor: pointer">
				${Loc.getMessage('BIZPROC_AUTOMATION_CMP_MANAGE_ROBOTS_1')}
			</a>
		`;
		Event.bind(manageButton, 'click', (event) => {
			event.preventDefault();
			this.onManageModeButtonClick(manageButton);
		});

		Dom.append(manageButton, this.#buttonsNode);
	}

	onManageModeButtonClick(manageButtonNode: HTMLElement)
	{
		if (this.canEdit())
		{
			this.emit('Template:enableManageMode', {
				documentStatus: this.#data.DOCUMENT_STATUS,
			});
		}
		else
		{
			HelpHint.showNoPermissionsHint(manageButtonNode);
		}
	}

	createTerminateRobotsButton()
	{
		if (!this.hasRunningRobots() && this.getRunningCustomRobots().length === 0)
		{
			return;
		}

		const terminateButton = Tag.render`
			<a class="bizproc-automation-robot-btn-set btn-pointer" target="_top">
				${Loc.getMessage('BIZPROC_JS_AUTOMATION_ROBOTS_TERMINATE')}
			</a>
		`;
		Event.bind(terminateButton, 'click', (event) => {
			event.preventDefault();
			this.onTerminateRobotsButtonClick(terminateButton);
		});

		Dom.append(terminateButton, this.#buttonsNode);
	}

	onTerminateRobotsButtonClick(terminateButton)
	{
		const templateId = this.getTemplateId();
		const signedDocument = this.#context.signedDocument;
		if (templateId > 0 && signedDocument)
		{
			Dom.addClass(terminateButton, '--disabled');
			ajax
				.runAction('bizproc.workflow.terminateByTemplate', {
					data: { templateId, signedDocument },
				})
				.then((response) => {
					this.notifyMessage(Loc.getMessage('BIZPROC_JS_AUTOMATION_ROBOTS_STOPPED'));
					this.stopTemplate();
				})
				.catch((response) => {
					response.errors.forEach((error) => {
						this.notifyMessage(error.message);
					});
				});
		}
	}

	stopTemplate()
	{
		const loaders = this.#templateNode.querySelectorAll('.bizproc-automation-robot-information.--loader');
		loaders.forEach((loader) => {
			Dom.removeClass(loader, '--loader');
		});
	}

	notifyMessage(message)
	{
		UI.Notification.Center.notify({
			content: message,
			autoHideDelay: 5000,
		});
	}

	hasRunningRobots(): boolean
	{
		return Boolean(this.#robots.some((robot) => robot.getLogStatus() === TrackingStatus.RUNNING));
	}

	getRunningCustomRobots(): []
	{
		return this.#data.CUSTOM_ROBOTS ?? [];
	}

	createConstantsEditButton(): boolean | undefined
	{
		if (Type.isNil(this.#context.constantsEditorUrl))
		{
			return false;
		}

		const url = (
			this.#viewMode.isManage()
				? '#'
				: this.#context.constantsEditorUrl.replace('#ID#', this.getTemplateId())
		);

		if (url.length === 0)
		{
			return false;
		}

		const anchor = Tag.render`
			<a class="bizproc-automation-robot-btn-set" href="${Text.encode(url)}">
				${Loc.getMessage('BIZPROC_AUTOMATION_CMP_CONSTANTS_EDIT')}
			</a>
		`;
		Dom.append(anchor, this.#buttonsNode);
	}

	createParametersEditButton(): boolean | undefined
	{
		if (Type.isNil(this.#context.parametersEditorUrl))
		{
			return false;
		}

		const url = this.#context.parametersEditorUrl.replace('#ID#', this.getTemplateId());

		if (url.length === 0 || this.#viewMode.isManage())
		{
			return false;
		}

		const anchor = Tag.render`
			<a class="bizproc-automation-robot-btn-set" href="${Text.encode(url)}">
				${Loc.getMessage('BIZPROC_AUTOMATION_CMP_PARAMETERS_EDIT')}
			</a>
		`;
		Dom.append(anchor, this.#buttonsNode);
	}

	createExternalLocker()
	{
		const { root, iconBlock } = Tag.render`
			<div class="bizproc-automation-robot-container">
				<div class="bizproc-automation-robot-container-wrapper bizproc-automation-robot-container-wrapper-lock">
					<div class="bizproc-automation-robot-deadline"></div>
					<div class="bizproc-automation-robot-title">
						${Loc.getMessage('BIZPROC_AUTOMATION_CMP_EXTERNAL_EDIT_TEXT')}
					</div>
					<div class="bizproc-automation-robot-information" ref="iconBlock"></div>
				</div>
			</div>
		`;

		if (this.getRunningCustomRobots().length > 0)
		{
			Dom.addClass(iconBlock, '--loader');
		}

		if (this.#viewMode.isEdit())
		{
			const settingsBtn = Tag.render`
				<div class="bizproc-automation-robot-btn-settings">
					${Loc.getMessage('BIZPROC_AUTOMATION_CMP_EDIT')}
				</div>
			`;
			Event.bind(root, 'click', (event) => {
				event.stopPropagation();
				if (!this.#viewMode.isManage())
				{
					this.onExternalEditTemplateButtonClick(root);
				}
			});
			Dom.append(settingsBtn, root);

			const deleteBtn = Tag.render`<span class="bizproc-automation-robot-btn-delete"></span>`;
			Event.bind(deleteBtn, 'click', (event) => {
				event.stopPropagation();
				if (!this.#viewMode.isManage())
				{
					this.onUnsetExternalModifiedClick(deleteBtn);
				}
			});
			Dom.append(deleteBtn, root.lastChild);
		}

		Dom.append(root, this.#listNode);
		this.#templateNode = root;
	}

	onSearch(event: BaseEvent)
	{
		if (this.isExternalModified())
		{
			this.onExternalModifiedSearch(event);
		}
		else
		{
			this.#robots.forEach((robot) => robot.onSearch(event));
		}
	}

	onExternalModifiedSearch(event)
	{
		if (this.#templateNode)
		{
			const query = event.getData().queryString;
			Dom[query ? 'addClass' : 'removeClass'](this.#templateNode, '--search-mismatch');
		}
	}

	onExternalEditTemplateButtonClick(button)
	{
		if (!this.canEdit())
		{
			HelpHint.showNoPermissionsHint(button);

			return;
		}

		if (this.#context.bizprocEditorUrl.length === 0)
		{
			if (top.BX.UI && top.BX.UI.InfoHelper)
			{
				top.BX.UI.InfoHelper.show('limit_office_bp_designer');
			}

			return;
		}

		const templateId = this.getTemplateId();
		if (templateId > 0)
		{
			this.openBizprocEditor(templateId);
		}
	}

	onUnsetExternalModifiedClick(button)
	{
		if (!this.canEdit())
		{
			HelpHint.showNoPermissionsHint(button);

			return;
		}

		this.#templateNode = null;

		this.markExternalModified(false);
		this.markModified();
		this.reInit(null, this.#viewMode.intoRaw());
	}

	openBizprocEditor(templateId)
	{
		top.window.location.href = this.#context.bizprocEditorUrl.replace('#ID#', templateId);
	}

	addRobot(robotData, callback)
	{
		const robot = new Robot({
			document: this.#context.document,
			template: this,
			isFrameMode: this.#context.get('isFrameMode'),
			tracker: this.#tracker,
		});
		const initData = {
			Type: robotData.CLASS,
			Properties: {
				Title: robotData.NAME,
			},
			DialogContext: robotData.DIALOG_CONTEXT,
		};

		if (this.#robots.length > 0)
		{
			const parentRobot = this.#robots[this.#robots.length - 1];
			if (!parentRobot.getDelayInterval().isNow() || parentRobot.isExecuteAfterPrevious())
			{
				initData.Delay = parentRobot.getDelayInterval().serialize();
				initData.ExecuteAfterPrevious = 1;
			}
		}

		robot.draft = true;
		robot.init(initData, this.#viewMode);

		this.insertRobot(robot);
		this.insertRobotNode(robot.node);
		this.emit('Template:robot:add', { robot });

		if (callback)
		{
			callback.call(this, robot);
		}
	}

	insertRobot(robot, beforeRobot)
	{
		if (beforeRobot)
		{
			for (let i = 0; i < this.#robots.length; ++i)
			{
				if (this.#robots[i] !== beforeRobot)
				{
					continue;
				}

				this.#robots.splice(i, 0, robot);
				break;
			}
		}
		else
		{
			this.#robots.push(robot);
		}

		this.markModified();
	}

	getNextRobot(robot)
	{
		for (let i = 0; i < this.#robots.length; ++i)
		{
			if (this.#robots[i] === robot)
			{
				return (this.#robots[i + 1] || null);
			}
		}

		return null;
	}

	deleteRobot(robot, callback)
	{
		for (let i = 0; i < this.#robots.length; ++i)
		{
			if (this.#robots[i].isEqual(robot))
			{
				this.#robots.splice(i, 1);

				if (callback)
				{
					callback(robot);
				}

				this.markModified();
				this.emit('Template:robot:delete', { robot });
				break;
			}
		}
	}

	insertRobotNode(robotNode, beforeNode)
	{
		if (beforeNode)
		{
			this.#listNode.insertBefore(robotNode, beforeNode);
		}
		else
		{
			Dom.append(robotNode, this.#listNode);
		}
	}

	openRobotSettingsDialog(robot: Robot, context?: Object, saveCallback: (Robot) => void)
	{
		if (!Type.isPlainObject(context))
		{
			context = {};
		}

		if (Designer.getInstance().getRobotSettingsDialog())
		{
			return;
		}

		const robotBrokenLinks = robot.getBrokenLinks();

		const formName = 'bizproc_automation_robot_dialog';
		const form = Tag.render`
			<form name="${formName}">
				${this.#renderExecutionQueue(robot)}
				${this.renderDelaySettings(robot)}
				${this.renderConditionSettings(robot)}
				${robotBrokenLinks.length > 0 ? this.renderBrokenLinkAlert(robotBrokenLinks) : ''}
			</form>
		`;

		Designer.getInstance().setRobotSettingsDialog({
			template: this,
			context,
			robot,
			form,
		});

		context.DOCUMENT_CATEGORY_ID = this.#context.document.getCategoryId();
		if (
			Type.isPlainObject(robot.data.DialogContext)
			&& !Type.isNil(robot.data.DialogContext.addMenuGroup)
		)
		{
			context.addMenuGroup = robot.data.DialogContext.addMenuGroup;
		}

		ajax({
			method: 'POST',
			dataType: 'html',
			url: Uri.addParam(
				this.#context.ajaxUrl,
				{
					analyticsLabel: `automation_robot${robot.draft ? '_draft' : ''}_settings_${robot.data.Type.toLowerCase()}`,
				},
			),
			data: {
				ajax_action: 'get_robot_dialog',
				document_signed: this.#context.signedDocument,
				document_status: this.#context.document.getCurrentStatusId(),
				context,
				robot_json: Helper.toJsonString(robot.serialize()),
				context_robots_json: Helper.toJsonString(
					this.#robots.filter((r) => r !== robot).map((r) => r.serialize()),
				),
				form_name: formName,
			},
			onsuccess: (html) => {
				if (html)
				{
					const dialogRows = Dom.create('div', { html });
					Dom.append(dialogRows, form);
				}

				this.showRobotSettingsPopup(robot, form, saveCallback);
			},
		});
	}

	showRobotSettingsPopup(robot: Robot, form: HTMLFormElement, saveCallback: (Robot) => void)
	{
		let popupMinWidth = 580;
		let popupWidth = popupMinWidth;

		if (this.#userOptions)
		{
			// TODO move from if?
			this.emit('Template:robot:showSettings');
			popupWidth = parseInt(
				this.#userOptions.get('defaults', 'robot_settings_popup_width', 580),
				10,
			);
		}

		this.initRobotSettingsControls(robot, form);

		if (
			robot.data.Type === 'CrmSendEmailActivity'
			|| robot.data.Type === 'MailActivity'
			|| robot.data.Type === 'RpaApproveActivity'
		)
		{
			popupMinWidth += 170;
			if (popupWidth < popupMinWidth)
			{
				popupWidth = popupMinWidth;
			}
		}

		let robotTitle = Loc.getMessage('BIZPROC_AUTOMATION_ROBOT_SETTINGS_TITLE');
		let descriptionTitle = Loc.getMessage('BIZPROC_AUTOMATION_ROBOT_SETTINGS_TITLE');

		if (robot.hasTitle())
		{
			robotTitle = robot.getTitle();
			descriptionTitle = robot.getDescriptionTitle();

			if (descriptionTitle === 'untitled')
			{
				descriptionTitle = robotTitle;
			}
		}

		const titleBarContent = Tag.render`
			<div class="popup-window-titlebar-text bizproc-automation-robot-settings-popup-titlebar">
				<span class="bizproc-automation-robot-settings-popup-titlebar-text">${Text.encode(robotTitle)}</span>
				<div class="ui-hint">
					<span class="ui-hint-icon" data-text="${Text.encode(descriptionTitle)}"></span>
				</div>
			</div>
		`;
		HelpHint.bindAll(titleBarContent);

		const popup = new Popup({
			id: Helper.generateUniqueId(),
			bindElement: null,
			content: form,
			closeByEsc: true,
			buttons: [
				new SaveButton({
					onclick: (button: BaseButton) => {
						const isNewRobot = robot.draft;
						const callback = () => {
							popup.close();
							if (isNewRobot)
							{
								this.emit('Template:robot:add', { robot });
							}

							if (saveCallback)
							{
								saveCallback(robot);
							}
						};

						this.saveRobotSettings(form, robot, callback, button.getContainer());
					},
				}),
				new CancelButton({
					text: Loc.getMessage('BIZPROC_JS_AUTOMATION_CANCEL_BUTTON_CAPS'),
					onclick: () => {
						popup.close();
					},
				}),
			],
			width: popupWidth,
			minWidth: popupMinWidth,
			minHeight: 100,
			contentPadding: 12,
			resizable: true,
			closeIcon: true,
			events: {
				onPopupClose: () => {
					Designer.getInstance().setRobotSettingsDialog(null);
					this.destroyRobotSettingsControls();
					popup.destroy();
					this.emit('Template:robot:closeSettings');
				},
				onPopupResize: () => {
					this.onResizeRobotSettings();
				},
				onPopupResizeEnd: () => {
					if (this.#userOptions)
					{
						this.#userOptions.set('defaults', 'robot_settings_popup_width', popup.getWidth());
					}
				},
			},
			titleBar: {
				content: titleBarContent,
			},
			draggable: { restrict: false },
		});

		Designer.getInstance().getRobotSettingsDialog().popup = popup;
		popup.show();
	}

	initRobotSettingsControls(robot, node)
	{
		if (!Type.isArray(this.robotSettingsControls))
		{
			this.robotSettingsControls = [];
		}

		const controlNodes = node.querySelectorAll('[data-role]');
		for (const controlNode of controlNodes)
		{
			this.initRobotSettingsControl(robot, controlNode);
		}
	}

	initRobotSettingsControl(robot, controlNode)
	{
		if (!Type.isArray(this.robotSettingsControls))
		{
			this.robotSettingsControls = [];
		}

		const role = controlNode.getAttribute('data-role');

		const controlProps = {
			context: new SelectorContext({
				fields: Runtime.clone(this.#context.document.getFields()),
				useSwitcherMenu: this.#context.get('showTemplatePropertiesMenuOnSelecting'),
				rootGroupTitle: this.#context.document.title,
				userOptions: this.#context.userOptions,
			}),
			needSync: robot.draft,
			checkbox: controlNode,
		};

		if (role === SelectorManager.SELECTOR_ROLE_USER)
		{
			const fieldProperty = JSON.parse(controlNode.getAttribute('data-property'));
			controlProps.context.set('additionalUserFields', [
				...this.#getUserSelectorAdditionalFields(fieldProperty),
				...this.globalConstants.filter((constant) => constant.Type === 'user').map((constant) => ({
					id: constant.Expression,
					title: constant.Name,
				})),
				...this.globalVariables.filter((variable) => variable.Type === 'user').map((variable) => ({
					id: variable.Expression,
					title: variable.Name,
				})),
			]);
		}
		else if (role === SelectorManager.SELECTOR_ROLE_FILE)
		{
			this.robots.forEach((robot) => {
				controlProps.context.fields.push(
					...robot
						.getReturnFieldsDescription()
						.filter((field) => field.Type === 'file')
						.map((field) => ({
							Id: `{{~${robot.getId()}:${field.Id}}}`,
							Name: `${robot.getTitle()}: ${field.Name}`,
							Type: 'file',
							Expression: `{{~${robot.getId()}:${field.Id}}}`,
						})),
				);
			});
		}

		const control = SelectorManager.createSelectorByRole(role, controlProps);

		if (control && role !== SelectorManager.SELECTOR_ROLE_SAVE_STATE)
		{
			control.renderTo(controlNode);

			control.subscribe('onAskConstant', (event) => {
				const { fieldProperty } = event.getData();
				control.onFieldSelect(this.addConstant(fieldProperty));
			});
			control.subscribe('onAskParameter', (event) => {
				const { fieldProperty } = event.getData();
				control.onFieldSelect(this.addParameter(fieldProperty));
			});
			control.subscribe('onOpenFieldMenu', (event) => this.onOpenMenu(event, robot));
			control.subscribe('onOpenMenu', (event) => this.onOpenMenu(event, robot));
		}

		BX.UI.Hint.init(controlNode);

		if (control)
		{
			this.robotSettingsControls.push(control);
		}
	}

	#getUserSelectorAdditionalFields(fieldProperty): Array<object>
	{
		const additionalFields = (
			this
				.getRobotsWithReturnFields()
				.flatMap((robot) => (
					robot
						.getReturnFieldsDescription()
						.filter((field) => field.Type === 'user')
						.map((field) => ({
							id: `{{~${robot.getId()}:${field.Id}}}`,
							title: `${robot.getTitle()}: ${field.Name}`,
						}))
				))
		);

		if (this.#context.get('showTemplatePropertiesMenuOnSelecting') && fieldProperty)
		{
			const ask = this.addConstant(Runtime.clone(fieldProperty));

			additionalFields.push({
				id: ask.Expression,
				title: Loc.getMessage('BIZPROC_AUTOMATION_ASK_CONSTANT'),
				tabs: ['recents', 'bpuserroles'],
				sort: 1,
			});

			const param = this.addParameter(Runtime.clone(fieldProperty));

			additionalFields.push({
				id: param.Expression,
				title: Loc.getMessage('BIZPROC_AUTOMATION_ASK_PARAMETER'),
				tabs: ['recents', 'bpuserroles'],
				sort: 2,
			});
		}

		return additionalFields;
	}

	getRobotsWithReturnFields(skipRobot: ?Robot): Array<Robot>
	{
		const skipId = skipRobot?.getId() || '';

		return this
			.robots
			.filter((templateRobot) => (
				templateRobot.getId() !== skipId && templateRobot.hasReturnFields()
			))
		;
	}

	destroyRobotSettingsControls()
	{
		if (this.conditionSelector)
		{
			this.conditionSelector.destroy();
			this.conditionSelector = null;
		}

		if (Type.isArray(this.robotSettingsControls))
		{
			for (let i = 0; i < this.robotSettingsControls.length; ++i)
			{
				if (Type.isFunction(this.robotSettingsControls[i].destroy))
				{
					this.robotSettingsControls[i].destroy();
				}
			}
		}

		this.robotSettingsControls = null;
	}

	onBeforeSaveRobotSettings()
	{
		if (Type.isArray(this.robotSettingsControls))
		{
			for (let i = 0; i < this.robotSettingsControls.length; ++i)
			{
				if (Type.isFunction(this.robotSettingsControls[i].onBeforeSave))
				{
					this.robotSettingsControls[i].onBeforeSave();
				}
			}
		}
	}

	onResizeRobotSettings()
	{
		if (Type.isArray(this.robotSettingsControls))
		{
			for (let i = 0; i < this.robotSettingsControls.length; ++i)
			{
				if (Type.isFunction(this.robotSettingsControls[i].onPopupResize))
				{
					this.robotSettingsControls[i].onPopupResize();
				}
			}
		}
	}

	renderDelaySettings(robot)
	{
		const delay = robot.getDelayInterval().clone();

		const {
			root,
			delayTypeNode,
			delayValueNode,
			delayValueTypeNode,
			delayBasisNode,
			delayWorkTimeNode,
			delayWaitWorkDayNode,
			delayInTimeNode,
			delayIntervalLabelNode,
		} = Tag.render`
			<div class="bizproc-automation-popup-settings">
				<div class="bizproc-automation-popup-settings-block">
					<span class="bizproc-automation-popup-settings-title-wrapper">
						<input
							ref="delayTypeNode"
							type="hidden"
							name="delay_type"
							value="${Text.encode(delay.type)}"
						/>
						<input
							ref="delayValueNode"
							type="hidden"
							name="delay_value"
							value="${Text.encode(delay.value)}"
						/>
						<input
							ref="delayValueTypeNode"
							type="hidden"
							name="delay_value_type"
							value="${Text.encode(delay.valueType)}"
						/>
						<input
							ref="delayBasisNode"
							type="hidden"
							name="delay_basis"
							value="${Text.encode(delay.basis)}"
						/>
						<input 
							ref="delayWorkTimeNode"
							type="hidden"
							name="delay_worktime"
							value="${delay.workTime ? 1 : 0}"
						/>
						<input
							ref="delayWaitWorkDayNode"
							type="hidden"
							name="delay_wait_workday"
							value="${delay.waitWorkDay ? 1 : 0}"
						/>
						<input
							ref="delayInTimeNode"
							type="hidden"
							name="delay_in_time"
							value="${Text.encode(delay.inTimeString)}"
						/>
						<span
							class="bizproc-automation-popup-settings-title bizproc-automation-popup-settings-title-left"
						>
							${Loc.getMessage('BIZPROC_JS_AUTOMATION_TO_EXECUTE_TITLE')}
						</span>
						<span
							ref="delayIntervalLabelNode"
							class="bizproc-automation-popup-settings-link bizproc-automation-delay-interval-basis"
						></span>
					</span>
				</div>
			</div>
		`;

		const basisFields = [];

		const docFields = this.#context.document.getFields();
		const minLimitM = this.#delayMinLimitM;

		if (Type.isArray(docFields))
		{
			for (const field of docFields)
			{
				if (field.Type === 'date' || field.Type === 'datetime')
				{
					basisFields.push(field);
				}
			}
		}

		const delayIntervalSelector = new DelayIntervalSelector({
			labelNode: delayIntervalLabelNode,
			onchange(delay) {
				delayTypeNode.value = delay.type;
				delayValueNode.value = delay.value;
				delayValueTypeNode.value = delay.valueType;
				delayBasisNode.value = delay.basis;
				delayWorkTimeNode.value = delay.workTime ? 1 : 0;
				delayWaitWorkDayNode.value = delay.waitWorkDay ? 1 : 0;
				delayInTimeNode.value = delay.inTimeString;
			},
			basisFields,
			minLimitM,
			useAfterBasis: true,
			showWaitWorkDay: true,
		});
		delayIntervalSelector.init(delay);

		return root;
	}

	setDelaySettingsFromForm(formFields, robot)
	{
		const delay = new DelayInterval();
		delay.setType(formFields.delay_type);
		delay.setValue(formFields.delay_value);
		delay.setValueType(formFields.delay_value_type);
		delay.setBasis(formFields.delay_basis);
		delay.setWorkTime(formFields.delay_worktime === '1');
		delay.setWaitWorkDay(formFields.delay_wait_workday === '1');
		delay.setInTime(formFields.delay_in_time ? formFields.delay_in_time.split(':') : null);
		robot.setDelayInterval(delay);

		if (robot.hasTemplate())
		{
			robot.setExecuteAfterPrevious(
				formFields.execute_after_previous && (formFields.execute_after_previous) === '1',
			);
		}

		return this;
	}

	renderConditionSettings(robot)
	{
		const conditionGroup = robot.getCondition();
		this.conditionSelector = new ConditionGroupSelector(conditionGroup, {
			fields: this.#context.document.getFields(),
			onOpenFieldMenu: (event) => this.onOpenMenu(event, robot),
			onOpenMenu: (event) => this.onOpenMenu(event, robot),
			caption: {
				head: Loc.getMessage('BIZPROC_JS_AUTOMATION_ROBOT_CONDITION_TITLE'),
			},
			isExpanded: this.#userOptions?.get('defaults', 'isConditionGroupExpanded', 'N') === 'Y',
		});

		this.conditionSelector.subscribe('onToggleGroupViewClick', (event: BaseEvent) => {
			const data = event.getData();
			this.#userOptions.set('defaults', 'isConditionGroupExpanded', data.isExpanded ? 'Y' : 'N');
		});

		return this.conditionSelector.createNode();
	}

	#renderExecutionQueue(robot): HTMLDivElement
	{
		const title = (
			robot.isExecuteAfterPrevious()
				? Loc.getMessage('BIZPROC_JS_AUTOMATION_EXECUTION_QUEUE_AFTER_PREVIOUS_TITLE')
				: Loc.getMessage('BIZPROC_JS_AUTOMATION_EXECUTION_QUEUE_PARALLEL_TITLE')
		);
		const value = robot.isExecuteAfterPrevious() ? '1' : '0';

		const { root, executionQueueLink, input } = Tag.render`
			<div class="bizproc-automation-popup-settings">
				<div class="bizproc-automation-popup-settings-block">
					<span class="bizproc-automation-popup-settings-title">
						${Loc.getMessage('BIZPROC_JS_AUTOMATION_EXECUTION_QUEUE_TITLE')}
					</span>
					<span class="bizproc-automation-popup-settings-link-wrapper">
						<a ref="executionQueueLink" class="bizproc-automation-popup-settings-link">${title}</a>
					</span>
					<input ref="input" type="hidden" value="${value}" name="execute_after_previous"/>
				</div>
			</div>
		`;
		Event.bind(executionQueueLink, 'click', () => {
			showExecutionQueuePopup({
				bindElement: executionQueueLink,
				currentValue: input.value,
				onSubmitButtonClick: (formData: FormData) => {
					const afterPrevious = formData.get('execution') === 'afterPrevious';
					Dom.adjust(input, { attrs: { value: afterPrevious ? '1' : '0' } });
					Dom.adjust(
						executionQueueLink,
						{
							text: (
								afterPrevious
									? Loc.getMessage('BIZPROC_JS_AUTOMATION_EXECUTION_QUEUE_AFTER_PREVIOUS_TITLE')
									: Loc.getMessage('BIZPROC_JS_AUTOMATION_EXECUTION_QUEUE_PARALLEL_TITLE')
							),
						},
					);
				},
			});
		});

		return root;
	}

	onOpenMenu(event: BaseEvent, robot: Robot): void
	{
		const selector = event.getData().selector;
		const isMixedCondition = event.getData().isMixedCondition;

		const needAddGroups = !(Type.isBoolean(isMixedCondition) && !isMixedCondition);
		if (needAddGroups)
		{
			const selectorManager = new SelectorItemsManager({
				activityResultFields: this.#getRobotResultFieldForSelector(robot),
				constants: this.getConstants(),
				// variables: this.getVariables(),
				globalConstants: this.globalConstants,
				globalVariables: this.globalVariables,
			});

			selectorManager.groupsWithChildren.forEach((group) => {
				selector.addGroup(group.id, group);
			});
		}

		this.emit(
			'Template:onSelectorMenuOpen',
			{
				template: this,
				robot,
				...event.getData(),
			},
		);
	}

	#getRobotResultFieldForSelector(skipRobot): Array<{id: string, title: string, fields: Array<Field>}>
	{
		return (
			this.getRobotsWithReturnFields(skipRobot)
				.map((robotWithReturnFields) => {
					return {
						id: robotWithReturnFields.getId(),
						title: robotWithReturnFields.getTitle(),
						fields: enrichFieldsWithModifiers(
							robotWithReturnFields.getReturnFieldsDescription(),
							robotWithReturnFields.getId(),
							{
								friendly: false,
								printable: false,
								server: false,
								responsible: false,
								shortLink: true,
							},
						),
					};
				})
		);
	}

	setConditionSettingsFromForm(formFields, robot)
	{
		robot.setCondition(ConditionGroup.createFromForm(formFields));

		return this;
	}

	renderBrokenLinkAlert(brokenLinks: [] = []): HTMLDivElement
	{
		const moreInfoNode = Tag.render`
			<div class="bizproc-automation-robot-broken-link-full-info">
				${brokenLinks
					.map((value) => Text.encode(value))
					.join('<br>')
				}
			</div>
		`;

		const showMoreLabel = Tag.render`
			<span class="bizproc-automation-robot-broken-link-show-more">
				${Loc.getMessage('JS_BIZPROC_AUTOMATION_BROKEN_LINK_MESSAGE_ERROR_MORE_INFO')}
			</span>
		`;
		Event.bindOnce(showMoreLabel, 'click', () => {
			Dom.style(moreInfoNode, 'height', `${moreInfoNode.scrollHeight}px`);
			Dom.remove(showMoreLabel);
		});

		const closeBtn = Tag.render`<span class="ui-alert-close-btn"></span>`;

		const alert = Tag.render`
			<div class="ui-alert ui-alert-warning ui-alert-icon-info">
				<div class="ui-alert-message">
					<div>
						<span>${Loc.getMessage('BIZPROC_AUTOMATION_BROKEN_LINK_MESSAGE_ERROR')}</span>
						${showMoreLabel}
					</div>
					${moreInfoNode}
				</div>
				${closeBtn}
			</div>
		`;

		Event.bindOnce(closeBtn, 'click', () => {
			Dom.remove(alert);
		});

		return alert;
	}

	saveRobotSettings(form, robot, callback, btnNode)
	{
		if (btnNode)
		{
			Dom.addClass(btnNode, 'ui-btn-wait');
		}

		this.onBeforeSaveRobotSettings();
		const formData = BX.ajax.prepareForm(form);
		const robotData = robot.onBeforeSaveRobotSettings(formData);

		const ajaxUrl = this.#context.ajaxUrl;
		const documentSigned = this.#context.signedDocument;
		ajax({
			method: 'POST',
			dataType: 'json',
			url: Uri.addParam(
				ajaxUrl,
				{
					analyticsLabel: `automation_robot${robot.draft ? '_draft' : ''}_save_${robot.data.Type.toLowerCase()}`,
				},
			),
			data: {
				ajax_action: 'save_robot_settings',
				document_signed: documentSigned,
				robot_json: Helper.toJsonString(robot.serialize()),
				form_data_json: Helper.toJsonString({ ...formData.data, ...robotData }),
				form_data: formData.data, /** @bug 0135641 */
			},
			onsuccess: (response) => {
				if (btnNode)
				{
					Dom.removeClass(btnNode, 'ui-btn-wait');
				}

				if (response.SUCCESS)
				{
					robot.updateData(response.DATA.robot);
					this.setDelaySettingsFromForm(formData.data, robot);
					this.setConditionSettingsFromForm(formData.data, robot);

					robot.draft = false;

					robot.reInit();
					this.markModified();
					if (callback)
					{
						callback(response.DATA);
					}
				}
				else
				{
					alert(response.ERRORS[0]);
				}
			},
		});
	}

	serialize()
	{
		const data = Runtime.clone(this.#data);
		data.IS_EXTERNAL_MODIFIED = this.isExternalModified() ? 1 : 0;
		data.ROBOTS = [];

		for (let i = 0; i < this.#robots.length; ++i)
		{
			data.ROBOTS.push(this.#robots[i].serialize());
		}

		return data;
	}

	isExternalModified()
	{
		return (this.externalModified === true);
	}

	markExternalModified(modified)
	{
		this.externalModified = modified !== false;
	}

	getRobotById(id)
	{
		return this.#robots.find((robot) => robot.getId() === id);
	}

	isModified()
	{
		return this.modified;
	}

	markModified(modified)
	{
		this.modified = modified !== false;

		if (this.modified)
		{
			this.emit('Template:modified');
		}
	}

	getConstants(): []
	{
		const constants = [];

		Object.keys(this.#data.CONSTANTS).forEach((id) => {
			const constant = Runtime.clone(this.#data.CONSTANTS[id]);

			constant.Id = id;
			constant.ObjectId = 'Constant';
			constant.SystemExpression = `{=Constant:${id}}`;
			constant.Expression = `{{~&:${id}}}`;
			constant.SuperTitle = Loc.getMessage('BIZPROC_AUTOMATION_CMP_TEMPLATE_CONSTANTS_LIST');

			constants.push(constant);
		});

		return constants;
	}

	getConstant(id)
	{
		const constants = this.getConstants();

		for (const constant of constants)
		{
			if (constant.Id === id)
			{
				return constant;
			}
		}

		return null;
	}

	addConstant(property)
	{
		const id = property.Id || this.generatePropertyId('Constant', this.#data.CONSTANTS);

		if (this.#data.CONSTANTS[id])
		{
			throw `Constant with id "${id}" is already exists`;
		}

		this.#data.CONSTANTS[id] = property;

		this.emit('Template:constant:add');
		// if (this.component)
		// {
		// 	BX.onCustomEvent(this.component, 'onTemplateConstantAdd', [this, this.getConstant(id)]);
		// }

		return this.getConstant(id);
	}

	updateConstant(id, property)
	{
		if (!this.#data.CONSTANTS[id])
		{
			throw `Constant with id "${id}" does not exists`;
		}

		//TODO: only Description yet.
		this.#data.CONSTANTS[id].Description = property.Description;

		this.emit('Template:constant:update', { constant: this.getConstant(id) });
		// if (this.component)
		// {
		// 	BX.onCustomEvent(this.component, 'onTemplateConstantUpdate', [this, this.getConstant(id)]);
		// }

		return this.getConstant(id);
	}

	deleteConstant(id)
	{
		delete this.#data.CONSTANTS[id];

		return true;
	}

	setConstantValue(id, value)
	{
		if (this.#data.CONSTANTS[id])
		{
			this.#data.CONSTANTS[id].Default = value;

			return true;
		}

		return false;
	}

	getParameters()
	{
		const params = [];

		Object.keys(this.#data.PARAMETERS).forEach((id) => {
			const param = Runtime.clone(this.#data.PARAMETERS[id]);

			param.Id = id;
			param.ObjectId = 'Template';
			param.SystemExpression = `{=Template:${id}}`;
			param.Expression = `{{~*:${id}}}`;

			params.push(param);
		});

		return params;
	}

	getParameter(id)
	{
		const params = this.getParameters();

		for (const param of params)
		{
			if (param.Id === id)
			{
				return param;
			}
		}

		return null;
	}

	addParameter(property)
	{
		const id = property.Id || this.generatePropertyId('Parameter', this.#data.PARAMETERS);

		if (this.#data.PARAMETERS[id])
		{
			throw `Parameter with id "${id}" is already exists`;
		}

		this.#data.PARAMETERS[id] = property;

		this.emit('Template:parameter:add', { parameter: this.getParameter(id) });
		// if (this.component)
		// {
		// 	BX.onCustomEvent(this.component, 'onTemplateParameterAdd', [this, this.getParameter(id)]);
		// }

		return this.getParameter(id);
	}

	updateParameter(id, property)
	{
		if (!this.#data.PARAMETERS[id])
		{
			throw `Parameter with id "${id}" does not exists`;
		}

		// TODO: only Description yet.
		this.#data.PARAMETERS[id].Description = property.Description;

		this.emit('Template:parameter:update', { parameter: this.getParameter(id) });
		// if (this.component)
		// {
		// 	BX.onCustomEvent(this.component, 'onTemplateParameterUpdate', [this, this.getParameter(id)]);
		// }

		return this.getParameter(id);
	}

	deleteParameter(id)
	{
		delete this.#data.PARAMETERS[id];

		return true;
	}

	setParameterValue(id, value)
	{
		if (this.#data.PARAMETERS[id])
		{
			this.#data.PARAMETERS[id].Default = value;

			return true;
		}

		return false;
	}

	getVariables(): []
	{
		const variables = [];

		Object.keys(this.#data.VARIABLES).forEach((id) => {
			const variable = Runtime.clone(this.#data.VARIABLES[id]);

			variable.Id = id;
			variable.ObjectId = 'Variable';
			variable.SystemExpression = `{=Variable:${id}}`;
			variable.Expression = `{=Variable:${id}}`;

			variables.push(variable);
		});

		return variables;
	}

	generatePropertyId(prefix, existsList)
	{
		let index;
		for (index = 1; index <= 1000; ++index)
		{
			if (!existsList[prefix + index])
			{
				break; // found
			}
		}

		return prefix + index;
	}

	collectUsages()
	{
		const usages = {
			Document: new Set(),
			Constant: new Set(),
			Variable: new Set(),
			Parameter: new Set(),
			GlobalConstant: new Set(),
			GlobalVariable: new Set(),
			Activity: new Set(),
		};

		this.#robots.forEach((robot) => {
			const robotUsages = robot.collectUsages();

			Object.keys(usages).forEach((key) => {
				robotUsages[key].forEach((usage) => {
					if (!usages[key].has(usage))
					{
						usages[key].add(usage);
					}
				});
			});
		});

		return usages;
	}

	subscribeRobotEvents(eventName: string, listener: (BaseEvent) => void): this
	{
		this.#robots.forEach((robot) => robot.subscribe(eventName, listener));

		return this;
	}

	unsubscribeRobotEvents(eventName: string, listener: (BaseEvent) => void)
	{
		this.#robots.forEach((robot) => robot.unsubscribe(eventName, listener));

		return this;
	}

	getRobotDescription(type: string): ?object
	{
		return this.#context.availableRobots.find((item) => item.CLASS === type);
	}

	get globalConstants(): []
	{
		return this.#context.automationGlobals ? this.#context.automationGlobals.globalConstants : [];
	}

	get globalVariables(): []
	{
		return this.#context.automationGlobals ? this.#context.automationGlobals.globalVariables : [];
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit