import { MockServer, defineActions, defineModels, fields, models, onRpc, serverState, webModels, } from "@web/../tests/web_test_helpers"; import { mailModels } from "@mail/../tests/mail_test_helpers"; import { RPCError } from "@web/core/network/rpc"; /** * @typedef {object} ServerData * @property {object} [models] * @property {object} [views] * @property {object} [menus] * @property {object} [actions] */ /** * Get a basic arch for a pivot, which is compatible with the data given by * getBasicData(). * * Here is the pivot created: * A B C D E F * 1 1 2 12 17 Total * 2 Proba Proba Proba Proba Proba * 3 false 15 15 * 4 true 11 10 95 116 * 5 Total 11 15 10 95 131 */ export function getBasicPivotArch() { return /* xml */ ` `; } /** * Get a basic arch for a list, which is compatible with the data given by * getBasicData(). * * Here is the list created: * A B C D * 1 Foo bar Date Product * 2 12 True 2016-04-14 xphone * 3 1 True 2016-10-26 xpad * 4 17 True 2016-12-15 xpad * 5 2 False 2016-12-11 xpad */ export function getBasicListArch() { return /* xml */ ` `; } export function getBasicGraphArch() { return /* xml */ ` `; } /** * @returns {ServerData} */ export function getBasicServerData() { return { models: getBasicData(), views: {}, }; } /** * * @param {string} model * @param {Array} columns * @param {{name: string, asc: boolean}[]} orderBy * * @returns { {definition: Object, columns: Array}} */ export function generateListDefinition(model, columns, actionXmlId, orderBy = []) { const cols = []; for (const name of columns) { const PyModel = Object.values(SpreadsheetModels).find((m) => m._name === model); cols.push({ name, type: PyModel._fields[name].type, }); } return { definition: { metaData: { resModel: model, columns, }, searchParams: { domain: [], context: {}, orderBy, }, name: "List", actionXmlId, }, columns: cols, }; } export function getBasicListArchs() { return { "partner,false,list": getBasicListArch(), }; } function mockSpreadsheetDataController(_request, { res_model, res_id }) { const [record] = this.env[res_model].search_read([["id", "=", parseInt(res_id)]]); if (!record) { const error = new RPCError(`Spreadsheet ${res_id} does not exist`); error.data = {}; throw error; } return { data: JSON.parse(record.spreadsheet_data), name: record.name, revisions: [], isReadonly: false, writable_rec_name_field: "name", }; } onRpc("/spreadsheet/data//", mockSpreadsheetDataController); export function defineSpreadsheetModels() { defineModels(SpreadsheetModels); } export function defineSpreadsheetActions() { defineActions([ { id: 1, name: "partner Action", res_model: "partner", xml_id: "spreadsheet.partner_action", views: [ [false, "list"], [false, "pivot"], [false, "graph"], [false, "search"], [false, "form"], ], }, ]); } export class IrModel extends webModels.IrModel { display_name_for(models) { const records = this.env["ir.model"].search_read( [["model", "in", models]], ["name", "model"] ); const result = []; for (const model of models) { const record = records.find((record) => record.model === model); if (record) { result.push({ model: model, display_name: record.name }); } else { result.push({ model: model, display_name: model }); } } return result; } /** * @param {string[]} modelNames */ has_searchable_parent_relation(modelNames) { return Object.fromEntries(modelNames.map((modelName) => [modelName, false])); } get_available_models() { return this.env["ir.model"].search_read([], ["display_name", "model"]); } _records = [ { id: 37, name: "Product", model: "product", }, { id: 40, name: "Partner", model: "partner", }, { id: 55, name: "Users", model: "res.users", }, { id: 56, name: "Currency", model: "res.currency", }, { id: 57, name: "Tag", model: "tag", }, ]; } export class IrUIMenu extends models.Model { _name = "ir.ui.menu"; name = fields.Char({ string: "Name" }); action = fields.Char({ string: "Action" }); group_ids = fields.Many2many({ string: "Groups", relation: "res.group" }); } export class IrActions extends models.Model { _name = "ir.actions"; } export class ResGroup extends models.Model { _name = "res.group"; name = fields.Char({ string: "Name" }); } export class ResUsers extends mailModels.ResUsers { _name = "res.users"; name = fields.Char({ string: "Name" }); group_ids = fields.Many2many({ string: "Groups", relation: "res.group" }); } export class SpreadsheetMixin extends models.Model { _name = "spreadsheet.mixin"; spreadsheet_binary_data = fields.Binary({ string: "Spreadsheet file" }); spreadsheet_data = fields.Text(); display_thumbnail = fields.Binary(); get_display_names_for_spreadsheet(args) { const result = []; for (const { model, id } of args) { const record = this.env[model].search_read([["id", "=", id]])[0]; result.push(record?.display_name ?? null); } return result; } get_selector_spreadsheet_models() { return [ { model: "documents.document", display_name: "Spreadsheets", allow_create: true, }, ]; } } export class ResCurrency extends models.Model { _name = "res.currency"; name = fields.Char({ string: "Code" }); symbol = fields.Char({ string: "Symbol" }); position = fields.Selection({ string: "Position", selection: [ ["after", "A"], ["before", "B"], ], }); decimal_places = fields.Integer({ string: "decimal" }); get_company_currency_for_spreadsheet() { return { code: "EUR", symbol: "€", position: "after", decimalPlaces: 2, }; } _records = [ { id: 1, name: "EUR", symbol: "€", position: "after", decimal_places: 2, }, { id: 2, name: "USD", symbol: "$", position: "before", decimal_places: 2, }, ]; } export class ResCountry extends webModels.ResCountry { _name = "res.country"; name = fields.Char({ string: "Country" }); code = fields.Char({ string: "Code" }); _records = [ { id: 1, name: "Belgium", code: "BE" }, { id: 2, name: "France", code: "FR" }, { id: 3, name: "United States", code: "US" }, ]; } export class ResCountryState extends models.Model { _name = "res.country.state"; name = fields.Char({ string: "Name" }); code = fields.Char({ string: "Code" }); country_id = fields.Many2one({ relation: "res.country" }); display_name = fields.Char({ string: "Display Name" }); _records = [ { id: 1, name: "California", code: "CA", country_id: 3, display_name: "California (US)" }, { id: 2, name: "New York", code: "NY", country_id: 3, display_name: "New York (US)" }, { id: 3, name: "Texas", code: "TX", country_id: 3, display_name: "Texas (US)" }, ]; } export class Partner extends models.Model { _name = "partner"; foo = fields.Integer({ string: "Foo", store: true, searchable: true, aggregator: "sum", groupable: false, }); bar = fields.Boolean({ string: "Bar", store: true, sortable: true, groupable: true, searchable: true, }); name = fields.Char({ string: "name", store: true, sortable: true, groupable: true, searchable: true, }); date = fields.Date({ string: "Date", store: true, sortable: true, groupable: true, searchable: true, }); create_date = fields.Datetime({ string: "Creation Date", store: true, sortable: true, groupable: true, }); active = fields.Boolean({ string: "Active", default: true, searchable: true, groupable: false, }); product_id = fields.Many2one({ string: "Product", relation: "product", store: true, sortable: true, groupable: true, searchable: true, }); tag_ids = fields.Many2many({ string: "Tags", relation: "tag", store: true, sortable: true, groupable: true, searchable: true, }); probability = fields.Float({ string: "Probability", searchable: true, store: true, aggregator: "avg", groupable: false, }); field_with_array_agg = fields.Integer({ string: "field_with_array_agg", searchable: true, groupable: false, aggregator: "array_agg", }); currency_id = fields.Many2one({ string: "Currency", relation: "res.currency", store: true, sortable: true, groupable: true, searchable: true, }); pognon = fields.Monetary({ string: "Money!", currency_field: "currency_id", store: true, sortable: true, aggregator: "avg", groupable: true, searchable: true, }); partner_properties = fields.Properties({ string: "Properties", store: true, sortable: true, groupable: true, searchable: true, definition_record: "product_id", definition_record_field: "properties_definitions", }); jsonField = fields.Json({ string: "Json Field", store: true, groupable: false }); user_ids = fields.Many2many({ relation: "res.users", string: "Users", searchable: true, groupable: false, }); _records = [ { id: 1, foo: 12, bar: true, name: "Raoul", date: "2016-04-14", create_date: "2016-04-03 00:00:00", product_id: 37, probability: 10, field_with_array_agg: 1, tag_ids: [42, 67], currency_id: 1, pognon: 74.4, }, { id: 2, foo: 1, bar: true, name: "Steven", date: "2016-10-26", create_date: "2014-04-03 00:05:32", product_id: 41, probability: 11, field_with_array_agg: 2, tag_ids: [42, 67], currency_id: 2, pognon: 74.8, }, { id: 3, foo: 17, bar: true, name: "Taylor", date: "2016-12-15", create_date: "2006-01-03 11:30:50", product_id: 41, probability: 95, field_with_array_agg: 3, tag_ids: [], currency_id: 1, pognon: 4, }, { id: 4, foo: 2, bar: false, name: "Zara", date: "2016-12-11", create_date: "2016-12-10 21:59:59", product_id: 41, probability: 15, field_with_array_agg: 4, tag_ids: [42], currency_id: 2, pognon: 1000, }, ]; // TODO: check which views are actually needed in the tests _views = { list: getBasicListArch(), pivot: getBasicPivotArch(), graph: getBasicGraphArch(), }; } export class Product extends models.Model { _name = "product"; name = fields.Char({ string: "Product Name" }); display_name = fields.Char({ string: "Product Name" }); active = fields.Boolean({ string: "Active", default: true }); template_id = fields.Many2one({ string: "Template", relation: "product", store: true, sortable: true, groupable: true, searchable: true, }); properties_definitions = fields.PropertiesDefinition(); pognon = fields.Monetary({ string: "Money!", currency_field: "currency_id", store: true, sortable: true, aggregator: "avg", groupable: true, searchable: true, }); currency_id = fields.Many2one({ string: "Currency", relation: "res.currency", store: true, sortable: true, groupable: true, searchable: true, }); _records = [ { id: 37, display_name: "xphone", name: "xphone", currency_id: 2, pognon: 699.99, }, { id: 41, display_name: "xpad", template_id: 37, name: "xpad", currency_id: 2, pognon: 599.99, }, ]; } export class Tag extends models.Model { _name = "tag"; name = fields.Char({ string: "Tag Name" }); _records = [ { id: 42, name: "isCool", }, { id: 67, name: "Growing", }, ]; } export function getBasicData() { return { "documents.document": {}, "ir.model": {}, "ir.embedded.actions": {}, "documents.tag": {}, "spreadsheet.template": {}, "res.currency": {}, "res.users": {}, partner: {}, product: {}, tag: {}, }; } export const SpreadsheetModels = { ...webModels, ...mailModels, IrModel, IrUIMenu, IrActions, ResGroup, ResUsers, ResCountry, ResCountryState, SpreadsheetMixin, ResCurrency, Partner, Product, Tag, }; /** * Add the records inside serverData in the MockServer * * @param {ServerData} serverData */ export function addRecordsFromServerData(serverData) { for (const modelName of Object.keys(serverData.models)) { const records = serverData.models[modelName].records; if (!records) { continue; } const PyModel = getSpreadsheetModel(modelName); if (!PyModel) { throw new Error(`Model ${modelName} not found inside SpreadsheetModels`); } checkRecordsValidity(modelName, records); PyModel._records = records; } } /** * Add the views inside serverData in the MockServer * * @param {ServerData} serverData * * @example * addViewsFromServerData({ "partner,false,search": "" }); * Will set the default search view for the partner model */ export function addViewsFromServerData(serverData) { for (const fullViewKey of Object.keys(serverData.views)) { const viewArch = serverData.views[fullViewKey]; const splitted = fullViewKey.split(","); const modelName = splitted[0]; const viewType = splitted[2]; const recordId = splitted[1]; const PyModel = getSpreadsheetModel(modelName); if (!PyModel) { throw new Error(`Model ${modelName} not found inside SpreadsheetModels`); } const viewKey = viewType + "," + recordId; PyModel._views[viewKey] = viewArch; } } /** * Check if the records are valid. * This is mainly to avoid the mail's service crashing if the res.users are not correctly set. */ function checkRecordsValidity(modelName, records) { if (modelName === "res.users") { const serverUserId = serverState.userId; const currentUser = records.find((record) => record.id === serverUserId); if (!currentUser) { throw new Error( `The current user (${serverUserId}) is not in the records. did you forget to set serverState.userId ?` ); } if (!currentUser.active) { throw new Error(`The current user (${serverUserId}) is not active`); } if (!currentUser.partner_id) { throw new Error( `The current user (${serverUserId}) has no partner_id. It should be set to serverState.partnerId` ); } } } export function getPyEnv() { const mockServer = MockServer.current; if (!mockServer) { throw new Error("No mock server found"); } return mockServer.env; } export function getSpreadsheetModel(modelName) { return Object.values(SpreadsheetModels).find((model) => model._name === modelName); }