const globalConfig = require('@config/global');
const Route = require('@core/abstract_routes/route');
const enums_radios = require('@core/utils/enum_radio');

// Select2 pagination size
const SELECT_PAGE_SIZE = 10;

// Models
const models = require('@app/models/');

/**
 * An object providing association function and its parameter.
 * @memberof CoreEntity
 * @typedef {object} associationObject
 * @property {string} func - Sequelize alias function (Ex: setR_user) used to create association in database
 * @property {number|number[]} value - Parameter of alias function. Id or array of ids to associate
 * @example
 * const association = {func: 'setR_user', value: [42, 84]};
 * await entityInstance[association.func](association.value);
 */

/**
 * File object built from multer data in `model_builder.parseBody()`
 * @memberof CoreEntity
 * @typedef {object} fileObject
 * @property {boolean} isPicture - If file is for a picture field
 * @property {boolean} isModified - True when any modification occur client side. Used to know if a previous version need to be deleted
 * @property {string} attribute - Field attribute of the file
 * @property {string} finalPath - Secured and formated filepath
 * @property {buffer} [buffer] - If there is no buffer, the file as been removed from form data and needs to be deleted on disk
 * @property {function} [func=undefined] - Function called in `res.success()` with the fileObject as parameter. Default function that write or remove file to/from disk is added in '/create' and '/update' routes. Provide your own to change how file is handled
 */

/**
 * <p>Abstract class extended by entity route classes found in /app/routes</p>
 * <p>CoreEntity methods present in the `registeredRoutes` array and `additionalRoutes` parameter will be provided as route definition to expressjs</p>
 * <p>Behavior of route methods can be altered by using its hooks from the child class, or by overriding the method</p>
 */

class CoreEntity extends Route {

	/**
	 * @constructor
	 * @param {string} e_entity - The name of the entity.
	 * @param {object} attributes - The models attributes of the entity.
	 * @param {array} options - The models options of the entity.
	 * @param {object} helpers - Helpers modules found in `/_core/helpers`.
	 * @param {array} [additionalRoutes] - Additional routes implemented in CoreEntity child class.
	 */
	constructor(e_entity, attributes, options, helpers, additionalRoutes = []) {
		const registeredRoutes = [
			'list',
			'datalist',
			'subdatalist',
			'show',
			'create_form',
			'create',
			'update_form',
			'update',
			'loadtab',
			'set_status',
			'search',
			'fieldset_remove',
			'fieldset_add',
			'destroy',
			...additionalRoutes
		];
		super(registeredRoutes);

		if (!e_entity)
			throw new Error("No entity name specified");

		this.entity = e_entity.substring(2);
		this.e_entity = e_entity;
		this.E_entity = e_entity.capitalizeFirstLetter();

		this.attributes = attributes;
		this.options = options;
		this.helpers = helpers;

		this.fileFields = [];
		for (const fieldName in this.attributes) {
			const field = this.attributes[fieldName];
			if (['file', 'picture'].includes(field.nodeaType))
				this.fileFields.push({name: fieldName, maxCount: field.maxCount || 1});
		}

		this.defaultMiddlewares.push(
			helpers.middlewares.isLoggedIn,
			helpers.middlewares.entityAccess(this.entity)
		);
	}

	//
	// Routes
	//

	/**
	 * GET - Render the entity's list file
	 * @namespace CoreEntity#list
	 */
	list() {
		this.router.get('/list', ...this.middlewares.list, this.asyncRoute(async(data) => {
			data.tableUrl = `/${this.entity}/datalist`;
			data.renderFile = `${this.e_entity}/list`;

			/**
		     * Called at route start
		     * @function CoreEntity#list#start
		     * @memberof CoreEntity#list
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
		     * @param {string} data.tableUrl - Url for the ajax datalist
		     * @param {string} data.renderFile - Dust file to render
		     */
			if (await this.getHook('list', 'start', data) === false)
				return;

			/**
		     * Called before rendering
		     * @function CoreEntity#list#beforeRender
		     * @memberof CoreEntity#list
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
		     * @param {string} data.tableUrl - Url for the ajax datalist
		     * @param {string} data.renderFile - Dust file to render
		     */
			if (await this.getHook('list', 'beforeRender', data) === false)
				return;

			data.res.success(_ => data.res.render(data.renderFile, data));
		}));
	}

	/**
	 * POST - Ajax route use by the datalist
	 * @namespace CoreEntity#datalist
	 */
	datalist() {
		this.router.post('/datalist', ...this.middlewares.datalist, this.asyncRoute(async(data) => {
			data.speInclude = null;
			data.speWhere = null;
			data.tableInfo = data.req.body;

			/**
		     * Called at route start
		     * @function CoreEntity#datalist#start
		     * @memberof CoreEntity#datalist
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
		     * @param {object} [data.speWhere] - Specific `where` case added to datatable's request
		     * @param {string[]} [data.speInclude] - Specific elements to `include` to datatable's request. An array of field path compatible with `helpers.model_builder.getIncludeFromFields()` is expected
		     * @param {object} data.tableInfo - Table information from client
		     */
			if (await this.getHook('datalist', 'start', data) === false)
				return;

			/**
		     * Called before datatable query build and execution
		     * @function CoreEntity#datalist#beforeDatatableQuery
		     * @memberof CoreEntity#datalist
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
		     * @param {object} [data.speWhere] - Specific `where` case added to datatable's request
		     * @param {string[]} [data.speInclude] - Specific elements to `include` to datatable's request. An array of field path compatible with `helpers.model_builder.getIncludeFromFields()` is expected
		     * @param {object} data.tableInfo - Table information from client
		     */
			if (await this.getHook('datalist', 'beforeDatatableQuery', data) === false)
				return;

			data.rawData = await this.helpers.datatable(this.E_entity, data.tableInfo, data.speInclude, data.speWhere);

			/**
		     * Called after datatable query execution, before post processing of results
		     * @function CoreEntity#datalist#afterDatatableQuery
		     * @memberof CoreEntity#datalist
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
		     * @param {object} [data.speWhere] - Specific `where` case added to datatable's request
		     * @param {string[]} [data.speInclude] - Specific elements to `include` to datatable's request. An array of field path compatible with `helpers.model_builder.getIncludeFromFields()` is expected
		     * @param {object} data.tableInfo - Table information from client
		     * @param {string} data.rawData - Result of the datatable query as raw data
		     */
			if (await this.getHook('datalist', 'afterDatatableQuery', data) === false)
				return;

			data.preparedData = await this.helpers.entity.prepareDatalistResult(this.e_entity, this.attributes, this.options, data.rawData, data.req.session.lang_user)

			/**
		     * Called before json response
		     * @function CoreEntity#datalist#beforeResponse
		     * @memberof CoreEntity#datalist
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
		     * @param {object} [data.speWhere] - Specific `where` case added to datatable's request
		     * @param {string[]} [data.speInclude] - Specific elements to `include` to datatable's request. An array of field path compatible with `helpers.model_builder.getIncludeFromFields()` is expected
		     * @param {object} data.tableInfo - Table information from client
		     * @param {string} data.rawData - Result of the datatable query as raw data
		     * @param {string} data.preparedData - Post processed data of datatable query results. This is the data sent as response
		     */
			if (await this.getHook('datalist', 'beforeResponse', data) === false)
				return;

			data.res.success(_ => data.res.send(data.preparedData).end());
		}));
	}

	/**
	 * POST - Ajax route use by the datalist in tab (like has many tab)
	 * @namespace CoreEntity#subdatalist
	 */
	subdatalist() {
		this.router.post('/subdatalist', ...this.middlewares.subdatalist, this.asyncRoute(async(data) => {
			data.speWhere = [];
			data.speInclude = [];
			data.tableInfo = {...data.req.body, ...data.req.query};

			/**
		     * Called at route start
		     * @function Core#CoreEntity#subdatalist#start
		     * @memberof CoreEntity#subdatalist
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
		     * @param {string} [data.speWhere] - Specific `where` case added to datatable's request
		     * @param {string} [data.speInclude] - Specific elements to `include` to datatable's request. An array of field path compatible with `helpers.model_builder.getIncludeFromFields()` is expected
		     * @param {object} data.tableInfo - Table information from client
		     */
			if (await this.getHook('subdatalist', 'start', data) === false)
				return;

			/**
		     * Called before datatable query build and execution
		     * @function CoreEntity#subdatalist#beforeDatatableQuery
		     * @memberof CoreEntity#subdatalist
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
		     * @param {string} [data.speWhere] - Specific `where` case added to datatable's request
		     * @param {string} [data.speInclude] - Specific elements to `include` to datatable's request. An array of field path compatible with `helpers.model_builder.getIncludeFromFields()` is expected
		     * @param {object} data.tableInfo - Table information from client
		     */
			if (await this.getHook('subdatalist', 'beforeDatatableQuery', data) === false)
				return;

			data.rawData = await this.helpers.datatable(this.E_entity, data.tableInfo, data.speInclude, data.speWhere, true);

			/**
		     * Called after datatable query execution, before post processing of results
		     * @function CoreEntity#subdatalist#afterDatatableQuery
		     * @memberof CoreEntity#subdatalist
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
		     * @param {object} [data.speWhere] - Specific `where` case added to datatable's request
		     * @param {string[]} [data.speInclude] - Specific elements to `include` to datatable's request. An array of field path compatible with `helpers.model_builder.getIncludeFromFields()` is expected
		     * @param {object} data.tableInfo - Table information from client
		     * @param {object} data.rawData - Result of the datatable query as raw data
		     */
			if (await this.getHook('subdatalist', 'afterDatatableQuery', data) === false)
				return;

			data.preparedData = await this.helpers.entity.prepareDatalistResult(data.req.query.subentityModel, this.attributes, this.options, data.rawData, data.req.session.lang_user);

			/**
		     * Called before json response
		     * @function CoreEntity#subdatalist#beforeResponse
		     * @memberof CoreEntity#subdatalist
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
		     * @param {object} [data.speWhere] - Specific `where` case added to datatable's request
		     * @param {string[]} [data.speInclude] - Specific elements to `include` to datatable's request. An array of field path compatible with `helpers.model_builder.getIncludeFromFields()` is expected
		     * @param {object} data.tableInfo - Table information from client
		     * @param {object} data.rawData - Result of the datatable query as raw data
		     * @param {object} data.preparedData - Post processed data of datatable query results. This is the data sent as response
		     */
			if (await this.getHook('subdatalist', 'beforeResponse', data) === false)
				return;

			data.res.success(_ => data.res.send(data.preparedData).end());
		}));
	}

	/**
	 * GET - Route that display row information of an entity
	 * @namespace CoreEntity#show
	 */
	show() {
		this.router.get('/show', ...this.middlewares.show, this.asyncRoute(async(data) => {
			data.idEntity = data.req.query.id;
			data.enum_radio = enums_radios.translated(this.e_entity, data.req.session.lang_user, this.options);
			data.renderFile = this.e_entity + '/show';
			// TODO: Check if hideButton is still useful
			/* If we arrive from an associated tab, hide the create and the list button */
			data.hideButton = data.req.query.hideButton !== 'undefined';

			/**
		     * Called at route start
		     * @function CoreEntity#show#start
		     * @memberof CoreEntity#show
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {object} data.enum_radio - Entity enum fields translations
			 * @param {string} data.renderFile - Dust file to render
			 * @param {number} data.idEntity - Id of entity to show
			 * @param {boolean} data.hideButton - Wether to hide buttons or not
			 */
			if (await this.getHook('show', 'start', data) === false)
				return;

			/**
		     * Called before querying data of entity to show
		     * @function CoreEntity#show#beforeEntityQuery
		     * @memberof CoreEntity#show
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {object} data.enum_radio - Entity enum fields translations
			 * @param {string} data.renderFile - Dust file to render
			 * @param {number} data.idEntity - Id of entity to show
			 * @param {boolean} data.hideButton - Wether to hide buttons or not
			 */
			if (await this.getHook('show', 'beforeEntityQuery', data) === false)
				return;

			// TODO: use data.entityData instead of data[this.e_entity] to normalize content of data object between entities
			data[this.e_entity] = await this.helpers.entity.optimizedFindOne(this.E_entity, data.idEntity, this.options);

			/**
		     * Called after querying data of entity to show
		     * @function CoreEntity#show#afterEntityQuery
		     * @memberof CoreEntity#show
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {object} data.enum_radio - Entity enum fields translations
			 * @param {string} data.renderFile - Dust file to render
			 * @param {number} data.idEntity - Id of entity to show
			 * @param {boolean} data.hideButton - Wether to hide buttons or not
			 * @param {object} data."e_entity" - Entity row to show
			 */
			if (await this.getHook('show', 'afterEntityQuery', data) === false)
				return;

			if (!data[this.e_entity])
				return data.res.error(_ => data.res.render('common/404', {
					message: 'Entity row not found'
				}));

			await this.helpers.entity.getPicturesBuffers(data[this.e_entity], this.e_entity);
			this.helpers.status.translate(data[this.e_entity], this.attributes, data.req.session.lang_user);
			data.componentAddressConfig = this.helpers.address.getMapsConfigIfComponentAddressExists(this.e_entity);
			enums_radios.translateUsingField(data[this.e_entity], this.options, data.enum_radio);

			// Get association data that needed to be load directly here (to do so set loadOnStart param to true in options).
			await this.helpers.entity.getLoadOnStartData(data, this.options);

			if (data.req.query.ajax) {
				data.renderFile = this.helpers.entity.getOverlayFile(data.req.query.associationSource, data.req.query.associationAlias, 'show');
				data.subentity = this.entity;
				data.e_subentity = this.e_entity;
				data.entityData = data[this.e_entity].get({plain: true});
			}

			/**
		     * Called before show file rendering
		     * @function CoreEntity#show#beforeRender
		     * @memberof CoreEntity#show
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {object} data.enum_radio - Entity enum fields translations
			 * @param {string} data.renderFile - Dust file to render
			 * @param {number} data.idEntity - Id of entity to show
			 * @param {boolean} data.hideButton - Wether to hide buttons or not
			 * @param {object} data."e_entity" - Entity row to show
			 * @param {object} data.entityData - ajax request only - Equals to data."e_entity" whitout sequelize wrapping
			 * @param {string} [data.subentity] - ajax request only - entity name without prefix
			 * @param {string} [data.e_subentity] - ajax request only - entity name with prefix
			 */
			if (await this.getHook('show', 'beforeRender', data) === false)
				return;

			data.res.success(_ => data.res.render(data.renderFile, data));
		}));
	}

	/**
	 * GET - Display the creation form of an entity
	 * @namespace CoreEntity#create_form
	 */
	create_form() {
		this.router.get('/create_form', ...this.middlewares.create_form, this.asyncRoute(async(data) => {
			data.enum_radio = enums_radios.translated(this.e_entity, data.req.session.lang_user, this.options);
			data.renderFile = `${this.e_entity}/create`;

			/**
		     * Called at route start
		     * @function CoreEntity#create_form#start
		     * @memberof CoreEntity#create_form
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {object} data.enum_radio - Entity enum fields translations
			 * @param {string} data.renderFile - Dust file to render
			 */
			if (await this.getHook('create_form', 'start', data) === false)
				return;

			// Get association data that needed to be load directly here (to do so set loadOnStart param to true in options).
			await this.helpers.entity.getLoadOnStartData(data, this.options);

			if (typeof data.req.query.associationFlag !== 'undefined') {
				data.associationFlag = data.req.query.associationFlag;
				data.associationSource = data.req.query.associationSource;
				data.associationUrl = data.req.query.associationUrl;
				data.associationForeignKey = data.req.query.associationForeignKey;
				data.associationAlias = data.req.query.associationAlias;
				if (data.req.query.ajax) {
					data.renderFile = this.helpers.entity.getOverlayFile(data.associationSource, data.associationAlias, 'create_form');
					data.subentity = this.entity;
					data.action = `/${this.entity}/create`;
					data.method = 'post';
					data.fieldsFile = `${this.e_entity}/create_fields`;
				}

				/**
			     * In case the creation form was called from an entity tab, it means that we've got to associate the created entity to a parent
			     * @function CoreEntity#create_form#ifFromAssociation
			     * @memberof CoreEntity#create_form
			     * @param {object} data
				 * @param {object} data.req - Request - See expressjs definition
				 * @param {object} data.res - Response - See expressjs definition
				 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
				 * @param {object} data.enum_radio - Entity enum fields translations
				 * @param {string} data.renderFile - Dust file to render
			     * @param {integer} data.associationFlag - ID of the entity that ask for a creation formulaire of the current entity
			     * @param {string} data.associationSource - Entity name that ask for a creation formulaire of the current entity
			     * @param {string} data.associationUrl - The url string of the entity source
			     * @param {integer} data.associationForeignKey - The concerned foreign key between the two entities
			     * @param {string} data.associationAlias - Alias that represent the relation between the two entities
 				 * @param {string} data.subentity=this.entity - ajax request only - Name of entity without prefix
				 * @param {string} data.action=/this.entity/create - ajax request only - Action of create form
				 * @param {string} data.method=post - ajax request only - Method of create form
				 * @param {string} data.fieldsFile=this.e_entity/create_fields - ajax request only - Fields file to insert into the form
			     */
				if (await this.getHook('create_form', 'ifFromAssociation', data) === false)
					return;
			}

			/**
		     * Called before render of data.renderFile
		     * @function CoreEntity#create_form#beforeRender
		     * @memberof CoreEntity#create_form
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {object} data.enum_radio - Entity enum fields translations
			 * @param {string} data.renderFile - Dust file to render
		     * @param {integer} data.associationFlag - ID of the entity that ask for a creation formulaire of the current entity
		     * @param {string} data.associationSource - Entity name that ask for a creation formulaire of the current entity
		     * @param {string} data.associationUrl - The url string of the entity source
		     * @param {integer} data.associationForeignKey - The concerned foreign key between the two entities
		     * @param {string} data.associationAlias - Alias that represent the relation between the two entities
			 * @param {string} data.subentity=this.entity - ajax request only - Name of entity without prefix
			 * @param {string} data.action=/this.entity/create - ajax request only - Action of create form
			 * @param {string} data.method=post - ajax request only - Method of create form
			 * @param {string} data.fieldsFile=this.e_entity/create_fields - ajax request only - Fields file to insert into the form
		     */
			if (await this.getHook('create_form', 'beforeRender', data) === false)
				return;

			data.res.success(_ => data.res.render(data.renderFile, data));
		}));
	}

	/**
	 * POST - Creation of an entity row
	 * @namespace CoreEntity#create
	 */
	create() {
		this.router.post('/create', ...this.middlewares.create, this.asyncRoute(async(data) => {
			data.transaction = await models.sequelize.transaction();

			/**
		     * Called at route start
		     * @function CoreEntity#create#start
		     * @memberof CoreEntity#create
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} data.transaction - Database transaction. Use this transaction in your hooks. Commit and rollback are handled through res.success() / res.error()
			 */
			if (await this.getHook('create', 'start', data) === false)
				return;

			const [createObject, createAssociations, createFiles] = this.helpers.model_builder.parseBody(this.e_entity, this.attributes, this.options, data.req.body, data.req.files);
			data.createObject = createObject;
			data.createAssociations = createAssociations;
			data.files = createFiles;

			/**
		     * Called before entity creation in database
		     * @function CoreEntity#create#beforeCreateQuery
		     * @memberof CoreEntity#create
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} data.transaction - Database transaction. Use this transaction in your hooks. Commit and rollback are handled through res.success() / res.error()
			 * @param {object} data.createObject - Parsed form values used to create row in database
			 * @param {CoreEntity.associationObject[]} data.createAssociations - Associations array
			 * @param {CoreEntity.fileObject[]} data.files - Array of files parsed from body
			 */
			if (await this.getHook('create', 'beforeCreateQuery', data) === false)
				return;

			data.createdRow = await models[this.E_entity].create(data.createObject, {
				user: data.req.user,
				transaction: data.transaction
			});

			data.redirect = '/' + this.entity + '/show?id=' + data.createdRow.id;
			data.req.session.toastr = [{
				message: 'message.create.success',
				level: "success"
			}];

			// If created from an association, register created row on source entity
			if (typeof data.req.query.associationFlag !== 'undefined' && data.req.query.associationFlag !== "") {
				data.redirect = '/' + data.req.query.associationUrl + '/show?id=' + data.req.query.associationFlag + '#' + data.req.query.associationAlias;

				const association = await models[data.req.query.associationSource.capitalizeFirstLetter()].findOne({
					where: { id: data.req.query.associationFlag }
				});

				if (!association)
					throw new Error("Association not found.");

				const modelName = data.req.query.associationAlias.capitalizeFirstLetter();
				if (typeof association['add' + modelName] !== 'undefined') {
					await association['add' + modelName](data.createdRow.id, {transaction: data.transaction});

					if (globalConfig.env == "tablet") {
						// Write add association to synchro journal
						this.helpers.entity.synchro.writeJournal({
							verb: "associate",
							id: data.req.query.associationFlag,
							target: this.e_entity,
							entityName: data.req.query.associationSource,
							func: 'add' + modelName,
							ids: data.createdRow.id
						});
					}
				} else {
					const obj = {};
					obj[data.req.query.associationForeignKey] = data.createdRow.id;
					await association.update(obj, {
						user: data.req.user,
						transaction: data.transaction
					});
				}
			}

			// Add default write to disk function to file if none set through hooks
			// These functions will be executed on route success before transaction commit
			for (const file of data.files)
				if (!file.func && file.buffer)
					file.func = async file => {
						if (file.isPicture)
							await this.helpers.file.writePicture(file.finalPath, file.buffer);
						else
							await this.helpers.file.write(file.finalPath, file.buffer);
					}
			// Add associations
			await Promise.all(data.createAssociations.map(asso => data.createdRow[asso.func](asso.value, {transaction: data.transaction})));

			await this.helpers.address.setAddressIfComponentExists(data.createdRow, this.options, data.req.body, data.transaction);
			const statusToastrs = await this.helpers.status.setInitialStatus(data.createdRow, this.E_entity, this.attributes, {transaction: data.transaction, user: data.req.user}) || [];

			if (statusToastrs.length)
				data.req.session.toastr = [...data.req.session.toastr, ...statusToastrs];

			/**
		     * Called before redirection to data.redirect
		     * @function CoreEntity#create#beforeRedirect
		     * @memberof CoreEntity#create
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} data.transaction - Database transaction. Use this transaction in your hooks. Commit and rollback are handled through res.success() / res.error()
			 * @param {object} data.createObject - Parsed form values used to create row in database
			 * @param {CoreEntity.associationObject[]} data.createAssociations - Associations array
			 * @param {CoreEntity.fileObject[]} data.files - Array of files parsed from body
			 */
			if (await this.getHook('create', 'beforeRedirect', data) === false)
				return;

			data.res.success(_ => data.res.redirect(data.redirect));
		}));
	}

	/**
	 * GET - Display the update form of an entity
	 * @namespace CoreEntity#update_form
	 */
	update_form() {
		this.router.get('/update_form', ...this.middlewares.update_form, this.asyncRoute(async(data) => {
			data.idEntity = data.req.query.id;
			data.enum_radio = enums_radios.translated(this.e_entity, data.req.session.lang_user, this.options);
			data.renderFile = `${this.e_entity}/update`;

			/**
		     * Called at route start
		     * @function CoreEntity#update_form#start
		     * @memberof CoreEntity#update_form
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.idEntity - Id of entity to update
			 * @param {object} data.enum_radio - Entity enum fields translations
			 * @param {string} data.renderFile - Dust file to render
			 */
			if (await this.getHook('update_form', 'start', data) === false)
				return;

			data[this.e_entity] = await this.helpers.entity.optimizedFindOne(this.E_entity, data.idEntity, this.options);
			if (!data[this.e_entity])
				return data.res.error(_ => {
					data.req.session.toastr = [{level: 'error', message: 'error.404.title'}];
					data.res.render('common/404', {
						message: 'Entity row not found'
					});
				});

			/**
		     * Called before querying entity to update
		     * @function CoreEntity#update_form#afterEntityQuery
		     * @memberof CoreEntity#update_form
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.idEntity - Id of entity to update
			 * @param {object} data.enum_radio - Entity enum fields translations
			 * @param {string} data.renderFile - Dust file to render
			 * @param {object} data."e_entity" - Entity row to update
			 */
			if (await this.getHook('update_form', 'afterEntityQuery', data) === false)
				return;

			if (typeof data.req.query.associationFlag !== 'undefined') {
				data.associationFlag = data.req.query.associationFlag;
				data.associationSource = data.req.query.associationSource;
				data.associationForeignKey = data.req.query.associationForeignKey;
				data.associationAlias = data.req.query.associationAlias;
				data.associationUrl = data.req.query.associationUrl;
				if (data.req.query.ajax) {
					data.renderFile = this.helpers.entity.getOverlayFile(data.associationSource, data.associationAlias, 'update_form');
					data.tabType = 'update_form';
					data.subentity = this.entity;
					data.e_subentity = this.e_entity;
					data.action = `/${this.entity}/update`;
					data.method = 'post';
					data.entityData = data[this.e_entity].get({plain: true});
				}
				if (await this.getHook('update_form', 'ifFromAssociation', data) === false)
					return;
			}

			data[this.e_entity].dataValues.enum_radio = data.enum_radio;
			enums_radios.translateUsingField(data[this.e_entity], this.options, data.enum_radio);

			// Update some data before show, e.g get picture binary
			// TODO: No picture preview in update_form at the moment
			// await this.helpers.entity.getPicturesBuffers(data[this.e_entity], this.e_entity, false);

			// Get association data that needed to be load directly here (to do so set loadOnStart param to true in options).
			await this.helpers.entity.getLoadOnStartData(data.req.query.ajax ? data[this.e_entity].dataValues : data, this.options);

			/**
		     * Called before render of data.renderFile
		     * @function CoreEntity#update_form#beforeRender
		     * @memberof CoreEntity#update_form
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.idEntity - Id of entity to update
			 * @param {object} data.enum_radio - Entity enum fields translations
			 * @param {string} data.renderFile - Dust file to render
			 * @param {object} data."e_entity" - Entity row to update
			 */
			if (await this.getHook('update_form', 'beforeRender', data) === false)
				return;

			data.res.success(_ => data.res.render(data.renderFile, data));
		}));
	}

	/**
	 * POST - Update of an entity row
	 * @namespace CoreEntity#update
	 */
	update() {
		this.router.post('/update', ...this.middlewares.update, this.asyncRoute(async(data) => {
			data.transaction = await models.sequelize.transaction();
			data.idEntity = parseInt(data.req.body.id);

			/**
		     * Called at route start
		     * @function CoreEntity#update#start
		     * @memberof CoreEntity#update
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} data.transaction - Database transaction. Use this transaction in your hooks. Commit and rollback are handled through res.success() / res.error()
			 * @param {number} data.idEntity - Id of entity to update
			 */
			if (await this.getHook('update', 'start', data) === false)
				return;

			const [updateObject, updateAssociations, updateFiles] = this.helpers.model_builder.parseBody(this.e_entity, this.attributes, this.options, data.req.body, data.req.files);
			data.updateObject = updateObject;
			data.updateAssociations = updateAssociations;
			data.files = data.req.files = updateFiles;

			data.updateRow = await models[this.E_entity].findOne({
				where: { id: data.idEntity },
				transaction: data.transaction
			});

			if (!data.updateRow)
				return data.res.error(_ => data.res.render('common/404', {
					message: 'Entity row not found'
				}));

			for (const file of data.files || []) {
				// Store old path of modified file before entity update
				if (file.isModified && data.updateRow[file.attribute])
					file.previousPath = data.updateRow[file.attribute];
				file.func = async file => {
					// New file
					if (file.buffer) {
						if (file.isPicture)
							await this.helpers.file.writePicture(file.finalPath, file.buffer);
						else
							await this.helpers.file.write(file.finalPath, file.buffer);
					}
					// Replaced or removed file
					if (file.previousPath) {
						if (file.isPicture)
							await this.helpers.file.removePicture(file.previousPath);
						else
							await this.helpers.file.remove(file.previousPath);
					}
				}
			}

			this.helpers.address.updateAddressIfComponentExists(data.updateRow, this.options, data.req.body, data.transaction);

			data.updateObject.version = data.updateRow.version;
			if(typeof data.updateRow.version === 'undefined' || !data.updateRow.version)
				data.updateObject.version = 0;
			data.updateObject.version++;

			/**
		     * Called before entity update in database
		     * @function CoreEntity#update#beforeUpdate
		     * @memberof CoreEntity#update
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} data.transaction - Database transaction. Use this transaction in your hooks. Commit and rollback are handled through res.success() / res.error()
			 * @param {object} data.updateObject - Parsed form values used to update row in database
			 * @param {CoreEntity.associationObject[]} data.updateAssociations - Associations array
			 * @param {CoreEntity.fileObject[]} data.files - Array of files parsed from body
			 */
			if (await this.getHook('update', 'beforeUpdate', data) === false)
				return;

			await data.updateRow.update(data.updateObject, {user: data.req.user, transaction: data.transaction});

			// Add associations
			await Promise.all(data.updateAssociations.map(asso => data.updateRow[asso.func](asso.value, {transaction: data.transaction})));

			data.redirect = '/' + this.entity + '/show?id=' + data.idEntity;
			if (typeof data.req.query.associationFlag !== 'undefined')
				data.redirect = '/' + data.req.query.associationUrl + '/show?id=' + data.req.query.associationFlag + '#' + data.req.query.associationAlias;

			data.req.session.toastr = [{
				message: 'message.update.success',
				level: "success"
			}];

			/**
		     * Called before redirecting to data.redirect
		     * @function CoreEntity#update#beforeRedirect
		     * @memberof CoreEntity#update
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} data.transaction - Database transaction. Use this transaction in your hooks. Commit and rollback are handled through res.success() / res.error()
			 * @param {object} data.updateObject - Parsed form values used to update row in database
			 * @param {CoreEntity.associationObject[]} data.updateAssociations - Associations array
			 * @param {CoreEntity.fileObject[]} data.files - Array of files parsed from body
			 */
			if (await this.getHook('update', 'beforeRedirect', data) === false)
				return;

			data.res.success(_ => data.res.redirect(data.redirect));
		}));
	}

	/**
	 * GET - Default route called to load entity tabs from show page
	 * @namespace CoreEntity#loadtab
	 */
	loadtab() {
		this.router.get('/loadtab/:id/:alias', ...this.middlewares.loadtab, this.asyncRoute(async(data) => {
			data.alias = data.req.params.alias;
			data.id = data.req.params.id;
			data.isAllowed = false;

			/**
		     * Called at route start
		     * @function CoreEntity#loadtab#start
		     * @memberof CoreEntity#loadtab
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.idEntity - Id of source entity
			 * @param {string} data.alias - Alias of relation between source entity and tab entity
			 * @param {boolean} data.isAllowed=false - Boolean to block tab loading. Set it to true to skip default verifications
			 */
			if (await this.getHook('loadtab', 'start', data) === false)
				return;

			// Find tab option
			for (let i = 0; i < this.options.length; i++)
				if (this.options[i].as == data.alias) {
					data.option = this.options[i];
					break;
				}
			if (!data.option)
				return data.res.error(_ => data.res.status(404).end());
			data.renderFile = `${__appPath}/views/${this.e_entity}/${data.option.as}/tab`;

			/**
		     * Called before checking access rights to this tab for logged user
		     * @function CoreEntity#loadtab#beforeValidityCheck
		     * @memberof CoreEntity#loadtab
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.idEntity - Id of source entity
			 * @param {string} data.alias - Alias of relation between source entity and tab entity
			 * @param {boolean} data.isAllowed=false - Boolean to block tab loading. Set it to true to skip default access rights verifications
			 */
			if (await this.getHook('loadtab', 'beforeValidityCheck', data) === false)
				return;

			// Check access rights to subentity
			if (!data.isAllowed && !this.helpers.access.entityAccess(data.req.user.r_group, data.option.target.substring(2)))
				return data.res.error(_ => data.res.status(403).end());
			data.isAllowed = true;

			/**
		     * Called after checking access rights, if allowed
		     * @function CoreEntity#loadtab#afterValidityCheck
		     * @memberof CoreEntity#loadtab
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.idEntity - Id of source entity
			 * @param {string} data.alias - Alias of relation between source entity and tab entity
			 * @param {boolean} data.isAllowed - Now true because we're after validity check
			 */
			if (await this.getHook('loadtab', 'afterValidityCheck', data) === false)
				return;

			if (typeof data.req.query.associationFlag !== 'undefined') {
				const association = {
					associationFlag: data.req.query.associationFlag,
					associationSource: data.req.query.associationSource,
					associationForeignKey: data.req.query.associationForeignKey,
					associationAlias: data.req.query.associationAlias,
					associationUrl: data.req.query.associationUrl
				}
				data.associationHref = Object.entries(association).map(asso => `${asso[0]}=${asso[1]}`).join('&');
			}

			data.E_entity = this.E_entity;
			data.e_entity = this.e_entity;
			data.entity = this.entity;
			data.dustData = null;

			/**
		     * Called before querying data of tab
		     * @function CoreEntity#loadtab#beforeDataQuery
		     * @memberof CoreEntity#loadtab
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.idEntity - Id of source entity
			 * @param {string} data.alias - Alias of relation between source entity and tab entity
			 * @param {boolean} data.isAllowed - Now true because we're after validity check
			 * @param {string} data.E_entity - Entity model name
			 * @param {string} data.e_entity - Prefixed entity name
			 * @param {string} data.entity - Entity name
			 * @param {object} [data.dustData] - Data provided to rendered data.renderFile. If data.dustData as no value, tab's default value will be loaded using `helpers.entity.getTabData()`. Set your data to avoid execution of default.
			 */
			if (await this.getHook('loadtab', 'beforeDataQuery', data) === false)
				return;

			if (!data.dustData)
				data.dustData = await this.helpers.entity.getTabData(data);

			/**
		     * Called before rendering data.renderFile
		     * @function CoreEntity#loadtab#beforeRender
		     * @memberof CoreEntity#loadtab
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.idEntity - Id of source entity
			 * @param {string} data.alias - Alias of relation between source entity and tab entity
			 * @param {boolean} data.isAllowed - Now true because we're after validity check
			 * @param {string} data.E_entity - Entity model name
			 * @param {string} data.e_entity - Prefixed entity name
			 * @param {string} data.entity - Entity name
			 * @param {object} [data.dustData] - Data provided to rendered data.renderFile.
			 */
			if (await this.getHook('loadtab', 'beforeRender', data) === false)
				return;

			data.res.success(_ => data.res.render(data.renderFile, data.dustData));
		}));
	}

	/**
	 * GET - Change the status of an entity
	 * @namespace CoreEntity#set_status
	 */
	set_status() {
		this.router.get('/set_status/:entity_id/:status/:id_new_status', ...this.middlewares.set_status, this.asyncRoute(async(data) => {
			data.redirect = data.req.headers.referer;
			data.idEntity = data.req.params.entity_id;
			data.statusName = data.req.params.status.substring(2); // TODO: no need for s_ prefix from client
			data.idNewStatus = data.req.params.id_new_status;
			data.isAllowed = false;

			/**
		     * Called at route start
		     * @function CoreEntity#set_status#start
		     * @memberof CoreEntity#set_status
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.redirect - Redirection route when status is set
			 * @param {number} data.idEntity - Id of source entity
			 * @param {number} data.idNewStatus - Id of target status
			 * @param {string} data.statusName - Status's name
			 * @param {boolean} data.isAllowed=false - Boolean to block status change. Set it to true to skip default verifications
			 */
			if (await this.getHook('set_status', 'start', data) === false)
				return;

			data.entity = await models[this.E_entity].findOne({
				where: {id: data.idEntity},
				include: {
					model: models.E_status,
					as: 'r_'+data.statusName
				}
			});
			if (!data.entity) {
				data.req.session.toastr = [{level: 'error', message: 'error.404.title'}];
				return data.res.error(_ => data.res.redirect(data.redirect));
			}

			/**
		     * Called before calling `helpers.status.isAllowed()`
		     * @function CoreEntity#set_status#beforeAllowedCheck
		     * @memberof CoreEntity#set_status
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.redirect - Redirection route when status is set
			 * @param {number} data.idEntity - Id of source entity
			 * @param {number} data.idNewStatus - Id of target status
			 * @param {string} data.statusName - Status's name
			 * @param {boolean} data.isAllowed=false - Boolean to block status change. Set it to true to skip default verifications
			 * @param {object} data.entity - Entity on which status is to be set, with its current status included
			 */
			if (await this.getHook('set_status', 'beforeAllowedCheck', data) === false)
				return;

			const currentStatusId = data.entity['fk_id_status_'+data.statusName];
			if (data.isAllowed === false && await this.helpers.status.isAllowed(currentStatusId, data.idNewStatus) === false) {
				data.req.session.toastr = [{level: 'error', message: 'component.status.error.illegal_status'}];
				return data.res.error(_ => data.res.redirect(data.redirect));
			}

			data.actions = await this.helpers.status.getActions(data.idNewStatus);

			/**
		     * Called before executing target status actions. Alter `data.actions` to add/remove actions
		     * @function CoreEntity#set_status#beforeActionsExecution
		     * @memberof CoreEntity#set_status
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.redirect - Redirection route when status is set
			 * @param {number} data.idEntity - Id of source entity
			 * @param {number} data.idNewStatus - Id of target status
			 * @param {string} data.statusName - Status's name
			 * @param {boolean} data.isAllowed=false - Boolean to block status change. Set it to true to skip default verifications
			 * @param {object} data.entity - Entity on which status is to be set, with its current status included
			 * @param {object[]} data.actions - Target status actions fetched from `helpers.status.getActions()`
			 */
			if (await this.getHook('set_status', 'beforeActionsExecution', data) === false)
				return;

			await this.helpers.status.executeActions(this.E_entity, data.idEntity, data.actions, data.transaction);

			/**
		     * Called before setting target status using `helpers.status.setStatus()`
		     * @function CoreEntity#set_status#beforeSetStatus
		     * @memberof CoreEntity#set_status
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.redirect - Redirection route when status is set
			 * @param {number} data.idEntity - Id of source entity
			 * @param {number} data.idNewStatus - Id of target status
			 * @param {string} data.statusName - Status's name
			 * @param {boolean} data.isAllowed=false - Boolean to block status change. Set it to true to skip default verifications
			 * @param {object} data.entity - Entity on which status is to be set, with its current status included
			 * @param {object[]} data.actions - Target status actions fetched from `helpers.status.getActions()`
			 */
			if (await this.getHook('set_status', 'beforeSetStatus', data) === false)
				return;

			await this.helpers.status.setStatus(this.e_entity, data.idEntity, data.statusName, data.idNewStatus, {
				user: data.req.user,
				comment: data.req.query.comment,
				transaction: data.transaction
			});

			/**
		     * Called before redirecting to `data.redirect`
		     * @function CoreEntity#set_status#beforeRedirect
		     * @memberof CoreEntity#set_status
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {number} data.redirect - Redirection route when status is set
			 * @param {number} data.idEntity - Id of source entity
			 * @param {number} data.idNewStatus - Id of target status
			 * @param {string} data.statusName - Status's name
			 * @param {boolean} data.isAllowed=false - Boolean to block status change. Set it to true to skip default verifications
			 * @param {object} data.entity - Entity on which status is to be set, with its current status included
			 * @param {object[]} data.actions - Target status actions fetched from `helpers.status.getActions()`
			 */
			if (await this.getHook('set_status', 'beforeRedirect', data) === false)
				return;

			data.res.success(_ => data.res.redirect(data.redirect));
		}));
	}

	/**
	 * POST - Search route of entity. Mainly used by ajax selects
	 * @namespace CoreEntity#search
	 */
	search() {
		this.router.post('/search', ...this.middlewares.search, this.asyncRoute(async(data) => {
			data.search = '%' + (data.req.body.search || '') + '%';
			data.searchField = data.req.body.searchField;
			data.limit = SELECT_PAGE_SIZE;
			data.offset = (data.req.body.page - 1) * data.limit;

			/**
			 * Called to search entity results paginated and / or filtered. This route is used by ajax select
			 * @function CoreEntity#search#start
			 * @memberof CoreEntity#search
			 * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {string} data.search - The search filter wrapped in '%' char. If no filter is provided `data.search` will equals to '%%'
			 * @param {string[]} data.searchField - The field on which the search filter is to be applied
			 * @param {number} data.limit=SELECT_PAGE_SIZE - Limit the number of results. Defaults to `SELECT_PAGE_SIZE` global
			 * @param {number} data.offset - The offset of search results. Equals to page number * `data.limit`
			 */
			if (await this.getHook('search', 'start', data) === false)
				return;

			// ID is always needed
			if (data.searchField.indexOf("id") == -1)
				data.searchField.push('id');

			data.query = {
				raw: true,
				attributes: data.req.body.searchField,
				offset: data.offset,
				limit: data.limit,
				where: {}
			};

			data.query.where = this.helpers.entity.search.generateWhere(data.search, data.searchField);

			/**
			 * Before the Sequelize query, usefull to customize default query behaviour
			 * @function CoreEntity#search#beforeQuery
			 * @memberof CoreEntity#search
			 * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {object} data.query - Query object that will be used in Sequelize query, customizable
			 */
			if (await this.getHook('search', 'beforeQuery', data) === false)
				return;

			// If you need to show fields in the select that are in an other associate entity, you have to include those entity here
			// query.include = [{model: models.E_myentity, as: "r_myentity"}]
			data.results = await models[this.E_entity].findAndCountAll(data.query);

			data.results.more = data.results.count > data.req.body.page * SELECT_PAGE_SIZE;

			// Format value like date / datetime / etc...
			this.helpers.entity.search.formatValue(this.attributes, data.results, data.req.session.lang_user, this.e_entity);

			/**
		     * Called before json response
		     * @function CoreEntity#search#beforeResponse
		     * @memberof CoreEntity#search
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {string} data.search - The search filter wrapped in '%' char. If no filter is provided `data.search` will equals to '%%'
			 * @param {string[]} data.searchField - The field on which the search filter is to be applied
			 * @param {number} data.limit=SELECT_PAGE_SIZE - Limit the number of results. Defaults to `SELECT_PAGE_SIZE` global
			 * @param {number} data.offset - The offset of search results. Equals to page number * `data.limit`
			 * @param {object} data.results - Search result sent to response
			 */
			if (await this.getHook('search', 'beforeResponse', data) === false)
				return;

			data.res.success(_ => data.res.json(data.results));
		}));
	}

	fieldset_add() {
		this.router.post('/fieldset/:alias/add', ...this.middlewares.fieldset_add, this.asyncRoute(async(data) => {
			data.alias = data.req.params.alias;
			data.idEntity = parseInt(data.req.body.idEntity);

			if (await this.getHook('fieldset_add', 'start', data) === false)
				return;

			const entity = await models[this.E_entity].findOne({
				where: {id: data.idEntity},
				transaction: data.transaction
			});
			if (!entity)
				return data.res.error(_ => data.res.status(404).end());

			let toAdd;
			if (typeof(toAdd = data.req.body.ids) === 'undefined') {
				data.req.session.toastr.push({
					message: 'message.create.failure',
					level: "error"
				});
				return data.res.redirect('/' + this.entity + '/show?id=' + data.idEntity + "#" + data.alias);
			}

			await entity['add' + data.alias.capitalizeFirstLetter()](toAdd, {transaction: data.transaction});

			if (await this.getHook('fieldset_add', 'beforeResponse', data) === false)
				return;

			data.res.success(_ => data.res.sendStatus(200).end());
		}));
	}

	fieldset_remove() {
		this.router.post('/fieldset/:alias/remove', ...this.middlewares.fieldset_remove, this.asyncRoute(async(data) => {
			data.alias = data.req.params.alias;
			data.idEntity = parseInt(data.req.body.idEntity);
			data.id_to_remove = parseInt(data.req.body.idRemove);

			if (await this.getHook('fieldset_remove', 'start', data) === false)
				return;

			const entity = await models[this.E_entity].findOne({
				where: {id: data.idEntity},
				transaction: data.transaction
			});
			if (!entity)
				return data.res.error(_ => data.res.status(404).end());

			// Get all associations
			await entity['remove' + data.alias.capitalizeFirstLetter()](data.id_to_remove, {transaction: data.transaction});

			if(globalConfig.env == "tablet"){
				let target = "";
				for (let i = 0; i < this.options.length; i++)
					if (this.options[i].as == data.alias)
					{target = this.options[i].target; break;}

				this.helpers.entity.synchro.writeJournal({
					verb: "associate",
					id: data.idEntity,
					target: target,
					entityName: this.e_entity,
					func: 'remove' + data.alias.capitalizeFirstLetter(),
					ids: data.id_to_remove
				});
			}

			if (await this.getHook('fieldset_remove', 'beforeResponse', data) === false)
				return;

			data.res.success(_ => data.res.sendStatus(200).end());
		}));
	}

	/**
	 * POST - Destroying an entity row
	 * @namespace CoreEntity#destroy
	 */
	destroy() {
		this.router.post('/delete', ...this.middlewares.destroy, this.asyncRoute(async(data) => {
			data.idEntity = parseInt(data.req.body.id);

			/**
		     * Called to delete an entity row in database
		     * @function CoreEntity#destroy#start
		     * @memberof CoreEntity#destroy
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {string} data.idEntity - Id of entity to delete
			 */
			if (await this.getHook('destroy', 'start', data) === false)
				return;

			/**
		     * Called before fetching row to delete
		     * @function CoreEntity#destroy#beforeEntityQuery
		     * @memberof CoreEntity#destroy
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {string} data.idEntity - Id of entity to delete
			 */
			if (await this.getHook('destroy', 'beforeEntityQuery', data) === false)
				return;

			data.deleteObject = await models[this.E_entity].findOne({
				where: {id: data.idEntity}
			});

			if (!data.deleteObject)
				return data.res.error(_ => data.res.render('common/404', {
					message: 'Entity row not found'
				}));

			/**
		     * Called before deleting row in database
		     * @function CoreEntity#destroy#beforeEntityQuery
		     * @memberof CoreEntity#destroy
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {string} data.idEntity - Id of entity to delete
			 * @param {object} data.deleteObject - Instance of entity to delete
			 */
			if (await this.getHook('destroy', 'beforeDestroy', data) === false)
				return;

			await data.deleteObject.destroy({transaction: data.transaction});

			data.req.session.toastr = [{
				message: 'message.delete.success',
				level: "success"
			}];

			data.redirect = '/' + this.entity + '/list';
			if (typeof data.req.query.associationFlag !== 'undefined')
				data.redirect = '/' + data.req.query.associationUrl + '/show?id=' + data.req.query.associationFlag + '#' + data.req.query.associationAlias;

			await this.helpers.entity.removeFiles(data.deleteObject, this.attributes);

			/**
		     * Called before redirecting to `data.redirect`
		     * @function CoreEntity#destroy#beforeEntityQuery
		     * @memberof CoreEntity#destroy
		     * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} [data.transaction] - Database transaction. undefined by default, provide your own when necessary
			 * @param {string} data.idEntity - Id of entity to delete
			 * @param {object} data.deleteObject - Instance of entity to delete
			 * @param {string} data.redirect - Where to redirect the response
			 */
			if (await this.getHook('destroy', 'beforeRedirect', data) === false)
				return;

			data.res.success(_ => data.res.redirect(data.redirect));
		}));
	}
}

module.exports = CoreEntity;