[MOV] spreadsheet_account: move module to community
The spreadsheet_account module is moved to the community version for the upcoming spreadsheet dashboards Task 2799682 Part-of: odoo/odoo#97606
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': "Spreadsheet Accounting Formulas",
|
||||
'version': '1.0',
|
||||
'category': 'Accounting',
|
||||
'summary': 'Spreadsheet Accounting formulas',
|
||||
'description': 'Spreadsheet Accounting formulas',
|
||||
'depends': ['spreadsheet', 'account'],
|
||||
'data': [],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
'license': 'LGPL-3',
|
||||
'assets': {
|
||||
'spreadsheet.o_spreadsheet': [
|
||||
(
|
||||
'after',
|
||||
'spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js',
|
||||
'spreadsheet_account/static/src/**/*.js'
|
||||
),
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'spreadsheet_account/static/tests/**/*.js',
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
from . import account
|
||||
from . import res_company
|
||||
@@ -0,0 +1,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import models, api, _
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import date_utils
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = "account.account"
|
||||
|
||||
@api.model
|
||||
def _get_date_period_boundaries(self, date_period, company):
|
||||
period_type = date_period["range_type"]
|
||||
year = date_period.get("year")
|
||||
month = date_period.get("month")
|
||||
quarter = date_period.get("quarter")
|
||||
day = date_period.get("day")
|
||||
if period_type == "year":
|
||||
fiscal_day = company.fiscalyear_last_day
|
||||
fiscal_month = int(company.fiscalyear_last_month)
|
||||
if not (fiscal_day == 31 and fiscal_month == 12):
|
||||
year += 1
|
||||
current = date(year, fiscal_month, fiscal_day)
|
||||
start, end = date_utils.get_fiscal_year(current, fiscal_day, fiscal_month)
|
||||
elif period_type == "month":
|
||||
start = date(year, month, 1)
|
||||
end = start + relativedelta(months=1, days=-1)
|
||||
elif period_type == "quarter":
|
||||
first_month = quarter * 3 - 2
|
||||
start = date(year, first_month, 1)
|
||||
end = start + relativedelta(months=3, days=-1)
|
||||
elif period_type == "day":
|
||||
fiscal_day = company.fiscalyear_last_day
|
||||
fiscal_month = int(company.fiscalyear_last_month)
|
||||
end = date(year, month, day)
|
||||
start, _ = date_utils.get_fiscal_year(end, fiscal_day, fiscal_month)
|
||||
return start, end
|
||||
|
||||
def _build_spreadsheet_formula_domain(self, formula_params):
|
||||
code = formula_params["code"]
|
||||
if not code:
|
||||
return expression.FALSE_DOMAIN
|
||||
company_id = formula_params["company_id"] or self.env.company.id
|
||||
company = self.env["res.company"].browse(company_id)
|
||||
start, end = self._get_date_period_boundaries(
|
||||
formula_params["date_range"], company
|
||||
)
|
||||
|
||||
base_domain = [
|
||||
("account_id.code", "=like", f"{code}%"),
|
||||
("company_id", "=", company_id),
|
||||
]
|
||||
if formula_params["include_unposted"]:
|
||||
base_domain = expression.AND(
|
||||
[base_domain, [("move_id.state", "!=", "cancel")]]
|
||||
)
|
||||
else:
|
||||
base_domain = expression.AND(
|
||||
[base_domain, [("move_id.state", "=", "posted")]]
|
||||
)
|
||||
|
||||
balance_domain = expression.AND(
|
||||
[
|
||||
base_domain,
|
||||
[
|
||||
("account_id.include_initial_balance", "=", True),
|
||||
("date", "<=", end),
|
||||
],
|
||||
]
|
||||
)
|
||||
result_domain = expression.AND(
|
||||
[
|
||||
base_domain,
|
||||
[
|
||||
("account_id.include_initial_balance", "=", False),
|
||||
("date", ">=", start),
|
||||
("date", "<=", end),
|
||||
],
|
||||
]
|
||||
)
|
||||
return expression.OR([balance_domain, result_domain])
|
||||
|
||||
@api.model
|
||||
def spreadsheet_move_line_action(self, args):
|
||||
domain = self._build_spreadsheet_formula_domain(args)
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "account.move.line",
|
||||
"view_mode": "list",
|
||||
"views": [[False, "list"]],
|
||||
"target": "current",
|
||||
"domain": domain,
|
||||
"name": _("Journal items for account prefix %s", args["code"]),
|
||||
}
|
||||
|
||||
@api.model
|
||||
def spreadsheet_fetch_debit_credit(self, args_list):
|
||||
"""Fetch data for ODOO.CREDIT, ODOO.DEBIT and ODOO.BALANCE formulas
|
||||
The input list looks like this:
|
||||
[{
|
||||
date_range: {
|
||||
range_type: "year"
|
||||
year: int
|
||||
},
|
||||
company_id: int
|
||||
code: str
|
||||
include_unposted: bool
|
||||
}]
|
||||
"""
|
||||
results = []
|
||||
for args in args_list:
|
||||
company_id = args["company_id"] or self.env.company.id
|
||||
domain = self._build_spreadsheet_formula_domain(args)
|
||||
# remove this when _search always returns a Query object
|
||||
if domain == expression.FALSE_DOMAIN:
|
||||
results.append({"credit": 0, "debit": 0})
|
||||
continue
|
||||
MoveLines = self.env["account.move.line"].with_company(company_id)
|
||||
query = MoveLines._search(domain)
|
||||
query.order = None
|
||||
query_str, params = query.select(
|
||||
"SUM(debit) AS debit", "SUM(credit) AS credit"
|
||||
)
|
||||
self.env.cr.execute(query_str, params)
|
||||
line_values = self.env.cr.dictfetchone()
|
||||
results.append(
|
||||
{
|
||||
"credit": line_values["credit"] or 0,
|
||||
"debit": line_values["debit"] or 0,
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
@api.model
|
||||
def get_account_group(self, account_types):
|
||||
data = self._read_group(
|
||||
[("account_type", "in", account_types), ("company_id", "=", self.env.company.id)],
|
||||
["code:array_agg"],
|
||||
["account_type"],
|
||||
)
|
||||
mapped = {group["account_type"]: group["code"] for group in data}
|
||||
return [mapped.get(account_type, []) for account_type in account_types]
|
||||
@@ -0,0 +1,29 @@
|
||||
from odoo import models, api, fields
|
||||
|
||||
from odoo.tools import date_utils
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
@api.model
|
||||
def get_fiscal_dates(self, payload):
|
||||
companies = self.env["res.company"].browse(
|
||||
data["company_id"] or self.env.company.id for data in payload
|
||||
)
|
||||
existing_companies = companies.exists()
|
||||
# prefetch both fields
|
||||
existing_companies.read(["fiscalyear_last_day", "fiscalyear_last_month"])
|
||||
results = []
|
||||
|
||||
for data, company in zip(payload, companies):
|
||||
if company not in existing_companies:
|
||||
results.append(False)
|
||||
continue
|
||||
start, end = date_utils.get_fiscal_year(
|
||||
fields.Date.to_date(data["date"]),
|
||||
day=company.fiscalyear_last_day,
|
||||
month=int(company.fiscalyear_last_month),
|
||||
)
|
||||
results.append({"start": start, "end": end})
|
||||
return results
|
||||
@@ -0,0 +1,135 @@
|
||||
/** @odoo-module */
|
||||
import { camelToSnakeObject, sum, toServerDateString } from "@spreadsheet/helpers/helpers";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
|
||||
import { ServerData } from "@spreadsheet/data_sources/server_data";
|
||||
|
||||
/**
|
||||
* @typedef {import("./accounting_functions").DateRange} DateRange
|
||||
*/
|
||||
|
||||
export class AccountingDataSource {
|
||||
constructor(services) {
|
||||
this.serverData = new ServerData(services.orm, {
|
||||
whenDataIsFetched: () => services.notify(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total credit for a given account code prefix
|
||||
* @param {string[]} codes prefixes of the accounts codes
|
||||
* @param {DateRange} dateRange start date of the period to look
|
||||
* @param {number} offset end date of the period to look
|
||||
* @param {number} companyId specific company to target
|
||||
* @param {boolean} includeUnposted wether or not select unposted entries
|
||||
* @returns {number | undefined}
|
||||
*/
|
||||
getCredit(codes, dateRange, offset, companyId, includeUnposted) {
|
||||
const data = this._fetchAccountData(codes, dateRange, offset, companyId, includeUnposted);
|
||||
return data.credit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total debit for a given account code prefix
|
||||
* @param {string[]} codes prefixes of the accounts codes
|
||||
* @param {DateRange} dateRange start date of the period to look
|
||||
* @param {number} offset end date of the period to look
|
||||
* @param {number} companyId specific company to target
|
||||
* @param {boolean} includeUnposted wether or not select unposted entries
|
||||
* @returns {number | undefined}
|
||||
*/
|
||||
getDebit(codes, dateRange, offset, companyId, includeUnposted) {
|
||||
const data = this._fetchAccountData(codes, dateRange, offset, companyId, includeUnposted);
|
||||
return data.debit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} date
|
||||
* @param {number | null} companyId
|
||||
* @returns {string}
|
||||
*/
|
||||
getFiscalStartDate(date, companyId) {
|
||||
return this._fetchCompanyData(date, companyId).start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} date
|
||||
* @param {number | null} companyId
|
||||
* @returns {string}
|
||||
*/
|
||||
getFiscalEndDate(date, companyId) {
|
||||
return this._fetchCompanyData(date, companyId).end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} accountType
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getAccountGroupCodes(accountType) {
|
||||
return this.serverData.batch.get("account.account", "get_account_group", accountType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the account information (credit/debit) for a given account code
|
||||
* @private
|
||||
* @param {string[]} codes prefix of the accounts' codes
|
||||
* @param {DateRange} dateRange start date of the period to look
|
||||
* @param {number} offset end date of the period to look
|
||||
* @param {number | null} companyId specific companyId to target
|
||||
* @param {boolean} includeUnposted wether or not select unposted entries
|
||||
* @returns {{ debit: number, credit: number }}
|
||||
*/
|
||||
_fetchAccountData(codes, dateRange, offset, companyId, includeUnposted) {
|
||||
dateRange.year += offset;
|
||||
// Excel dates start at 1899-12-30, we should not support date ranges
|
||||
// that do not cover dates prior to it.
|
||||
// Unfortunately, this check needs to be done right before the server
|
||||
// call as a date to low (year <= 1) can raise an error server side.
|
||||
if (dateRange.year < 1900) {
|
||||
throw new Error(sprintf(_t("%s is not a valid year."), dateRange.year));
|
||||
}
|
||||
const results = [];
|
||||
let error = undefined;
|
||||
// If some payload were to raise an error, we still need to process the others
|
||||
// to make sure they are part of the next batch call.
|
||||
for (const code of codes) {
|
||||
try {
|
||||
const result = this.serverData.batch.get(
|
||||
"account.account",
|
||||
"spreadsheet_fetch_debit_credit",
|
||||
camelToSnakeObject({ dateRange, code, companyId, includeUnposted })
|
||||
);
|
||||
results.push(result);
|
||||
} catch (err) {
|
||||
error = error || err;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
debit: sum(results.map((values) => values.debit)),
|
||||
credit: sum(results.map((values) => values.credit)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the start and end date of the fiscal year enclosing a given date
|
||||
* Defaults on the current user company if not provided
|
||||
* @private
|
||||
* @param {Date} date
|
||||
* @param {number | null} companyId
|
||||
* @returns {{start: string, end: string}}
|
||||
*/
|
||||
_fetchCompanyData(date, companyId) {
|
||||
const result = this.serverData.batch.get("res.company", "get_fiscal_dates", {
|
||||
date: toServerDateString(date),
|
||||
company_id: companyId,
|
||||
});
|
||||
if (result === false) {
|
||||
throw new Error(_t("The company fiscal year could not be found."));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
|
||||
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||
const { functionRegistry } = spreadsheet.registries;
|
||||
const { args, toBoolean, toString, toNumber, toJsDate } = spreadsheet.helpers;
|
||||
|
||||
const QuarterRegexp = /^q([1-4])\/(\d{4})$/i;
|
||||
const MonthRegexp = /^0?([1-9]|1[0-2])\/(\d{4})$/i;
|
||||
|
||||
/**
|
||||
* @typedef {Object} YearDateRange
|
||||
* @property {"year"} rangeType
|
||||
* @property {number} year
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} QuarterDateRange
|
||||
* @property {"quarter"} rangeType
|
||||
* @property {number} year
|
||||
* @property {number} quarter
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MonthDateRange
|
||||
* @property {"month"} rangeType
|
||||
* @property {number} year
|
||||
* @property {number} month
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} DayDateRange
|
||||
* @property {"day"} rangeType
|
||||
* @property {number} year
|
||||
* @property {number} month
|
||||
* @property {number} day
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {YearDateRange | QuarterDateRange | MonthDateRange | DayDateRange} DateRange
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} dateRange
|
||||
* @returns {QuarterDateRange | undefined}
|
||||
*/
|
||||
function parseAccountingQuarter(dateRange) {
|
||||
const found = dateRange.match(QuarterRegexp);
|
||||
return found
|
||||
? {
|
||||
rangeType: "quarter",
|
||||
year: toNumber(found[2]),
|
||||
quarter: toNumber(found[1]),
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dateRange
|
||||
* @returns {MonthDateRange | undefined}
|
||||
*/
|
||||
function parseAccountingMonth(dateRange) {
|
||||
const found = dateRange.match(MonthRegexp);
|
||||
return found
|
||||
? {
|
||||
rangeType: "month",
|
||||
year: toNumber(found[2]),
|
||||
month: toNumber(found[1]),
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dateRange
|
||||
* @returns {YearDateRange | undefined}
|
||||
*/
|
||||
function parseAccountingYear(dateRange) {
|
||||
const dateNumber = toNumber(dateRange);
|
||||
// This allows a bit of flexibility for the user if they were to input a
|
||||
// numeric value instead of a year.
|
||||
// Users won't need to fetch accounting info for year 3000 before a long time
|
||||
// And the numeric value 3000 corresponds to 18th march 1908, so it's not an
|
||||
//issue to prevent them from fetching accounting data prior to that date.
|
||||
if (dateNumber < 3000) {
|
||||
return { rangeType: "year", year: dateNumber };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dateRange
|
||||
* @returns {DayDateRange}
|
||||
*/
|
||||
function parseAccountingDay(dateRange) {
|
||||
const dateNumber = toNumber(dateRange);
|
||||
return {
|
||||
rangeType: "day",
|
||||
year: functionRegistry.get("YEAR").compute(dateNumber),
|
||||
month: functionRegistry.get("MONTH").compute(dateNumber),
|
||||
day: functionRegistry.get("DAY").compute(dateNumber),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number} dateRange
|
||||
* @returns {DateRange}
|
||||
*/
|
||||
export function parseAccountingDate(dateRange) {
|
||||
try {
|
||||
dateRange = toString(dateRange).trim();
|
||||
return (
|
||||
parseAccountingQuarter(dateRange) ||
|
||||
parseAccountingMonth(dateRange) ||
|
||||
parseAccountingYear(dateRange) ||
|
||||
parseAccountingDay(dateRange)
|
||||
);
|
||||
} catch (_) {
|
||||
throw new Error(sprintf(_t(`'%s' is not a valid period. Supported formats are "21/12/2022", "Q1/2022", "12/2022", and "2022".`), dateRange));
|
||||
}
|
||||
}
|
||||
|
||||
const ODOO_FIN_ARGS = `
|
||||
account_codes (string) ${_t("The prefix of the accounts.")}
|
||||
date_range (string, date) ${_t(`The date range. Supported formats are "21/12/2022", "Q1/2022", "12/2022", and "2022".`)}
|
||||
offset (number, default=0) ${_t("Year offset applied to date_range.")}
|
||||
company_id (number, optional) ${_t("The company to target (Advanced).")}
|
||||
include_unposted (boolean, default=TRUE) ${_t("Set to TRUE to include unposted entries.")}
|
||||
`
|
||||
|
||||
functionRegistry.add("ODOO.CREDIT", {
|
||||
description: _t("Get the total credit for the specified account(s) and period."),
|
||||
args: args(ODOO_FIN_ARGS),
|
||||
returns: ["NUMBER"],
|
||||
compute: function (
|
||||
accountCodes,
|
||||
dateRange,
|
||||
offset = 0,
|
||||
companyId = null,
|
||||
includeUnposted = true
|
||||
) {
|
||||
accountCodes = toString(accountCodes).split(",").sort();
|
||||
offset = toNumber(offset);
|
||||
dateRange = parseAccountingDate(dateRange);
|
||||
includeUnposted = toBoolean(includeUnposted);
|
||||
return this.getters.getAccountPrefixCredit(
|
||||
accountCodes,
|
||||
dateRange,
|
||||
offset,
|
||||
companyId,
|
||||
includeUnposted
|
||||
);
|
||||
},
|
||||
computeFormat: function (
|
||||
accountCodes,
|
||||
dateRange,
|
||||
offset = 0,
|
||||
companyId = null,
|
||||
includeUnposted = true
|
||||
) {
|
||||
return this.getters.getCompanyCurrencyFormat(companyId && companyId.value) || "#,##0.00";
|
||||
},
|
||||
});
|
||||
|
||||
functionRegistry.add("ODOO.DEBIT", {
|
||||
description: _t("Get the total debit for the specified account(s) and period."),
|
||||
args: args(ODOO_FIN_ARGS),
|
||||
returns: ["NUMBER"],
|
||||
compute: function (
|
||||
accountCodes,
|
||||
dateRange,
|
||||
offset = 0,
|
||||
companyId = null,
|
||||
includeUnposted = true
|
||||
) {
|
||||
accountCodes = toString(accountCodes).split(",").sort();
|
||||
offset = toNumber(offset);
|
||||
dateRange = parseAccountingDate(dateRange);
|
||||
includeUnposted = toBoolean(includeUnposted);
|
||||
return this.getters.getAccountPrefixDebit(
|
||||
accountCodes,
|
||||
dateRange,
|
||||
offset,
|
||||
companyId,
|
||||
includeUnposted
|
||||
);
|
||||
},
|
||||
computeFormat: function (
|
||||
accountCodes,
|
||||
dateRange,
|
||||
offset = 0,
|
||||
companyId = null,
|
||||
includeUnposted = true
|
||||
) {
|
||||
return this.getters.getCompanyCurrencyFormat(companyId && companyId.value) || "#,##0.00";
|
||||
},
|
||||
});
|
||||
|
||||
functionRegistry.add("ODOO.BALANCE", {
|
||||
description: _t("Get the total balance for the specified account(s) and period."),
|
||||
args: args(ODOO_FIN_ARGS),
|
||||
returns: ["NUMBER"],
|
||||
compute: function (
|
||||
accountCodes,
|
||||
dateRange,
|
||||
offset = 0,
|
||||
companyId = null,
|
||||
includeUnposted = true
|
||||
) {
|
||||
accountCodes = toString(accountCodes).split(",").sort();
|
||||
offset = toNumber(offset);
|
||||
dateRange = parseAccountingDate(dateRange);
|
||||
includeUnposted = toBoolean(includeUnposted);
|
||||
return (
|
||||
this.getters.getAccountPrefixDebit(
|
||||
accountCodes,
|
||||
dateRange,
|
||||
offset,
|
||||
companyId,
|
||||
includeUnposted
|
||||
) -
|
||||
this.getters.getAccountPrefixCredit(
|
||||
accountCodes,
|
||||
dateRange,
|
||||
offset,
|
||||
companyId,
|
||||
includeUnposted
|
||||
)
|
||||
);
|
||||
},
|
||||
computeFormat: function (
|
||||
accountCodes,
|
||||
dateRange,
|
||||
offset = 0,
|
||||
companyId = null,
|
||||
includeUnposted = true
|
||||
) {
|
||||
return this.getters.getCompanyCurrencyFormat(companyId && companyId.value) || "#,##0.00";
|
||||
},
|
||||
});
|
||||
|
||||
functionRegistry.add("ODOO.FISCALYEAR.START", {
|
||||
description: _t("Returns the starting date of the fiscal year encompassing the provided date."),
|
||||
args: args(`
|
||||
date (date) ${_t("Reference date.")}
|
||||
company_id (number, optional) ${_t("The company.")}
|
||||
`),
|
||||
returns: ["NUMBER"],
|
||||
computeFormat: () => "m/d/yyyy",
|
||||
compute: function (date, companyId = null) {
|
||||
const startDate = this.getters.getFiscalStartDate(
|
||||
toJsDate(date),
|
||||
companyId === null ? null : toNumber(companyId)
|
||||
);
|
||||
return toNumber(startDate);
|
||||
},
|
||||
});
|
||||
|
||||
functionRegistry.add("ODOO.FISCALYEAR.END", {
|
||||
description: _t("Returns the ending date of the fiscal year encompassing the provided date."),
|
||||
args: args(`
|
||||
date (date) ${_t("Reference date.")}
|
||||
company_id (number, optional) ${_t("The company.")}
|
||||
`),
|
||||
returns: ["NUMBER"],
|
||||
computeFormat: () => "m/d/yyyy",
|
||||
compute: function (date, companyId = null) {
|
||||
const endDate = this.getters.getFiscalEndDate(
|
||||
toJsDate(date),
|
||||
companyId === null ? null : toNumber(companyId)
|
||||
);
|
||||
return toNumber(endDate);
|
||||
},
|
||||
});
|
||||
|
||||
functionRegistry.add("ODOO.ACCOUNT.GROUP", {
|
||||
description: _t("Returns the account ids of a given group."),
|
||||
args: args(`
|
||||
type (string) ${_t("Account type.")}
|
||||
`),
|
||||
returns: ["NUMBER"],
|
||||
computeFormat: () => "m/d/yyyy",
|
||||
compute: function (accountType) {
|
||||
const accountTypes = this.getters.getAccountGroupCodes(toString(accountType));
|
||||
return accountTypes.join(",");
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||
import AccountingPlugin from "./plugins/accounting_plugin";
|
||||
import { getFirstAccountFunction, getNumberOfAccountFormulas } from "./utils";
|
||||
import { parseAccountingDate } from "./accounting_functions";
|
||||
import { camelToSnakeObject } from "@spreadsheet/helpers/helpers";
|
||||
|
||||
const { cellMenuRegistry, uiPluginRegistry } = spreadsheet.registries;
|
||||
const { astToFormula } = spreadsheet;
|
||||
const { toString, toBoolean } = spreadsheet.helpers;
|
||||
|
||||
uiPluginRegistry.add("odooAccountingAggregates", AccountingPlugin);
|
||||
|
||||
cellMenuRegistry.add("move_lines_see_records", {
|
||||
name: _lt("See records"),
|
||||
sequence: 176,
|
||||
async action(env) {
|
||||
const cell = env.model.getters.getActiveCell();
|
||||
const { args } = getFirstAccountFunction(cell.content);
|
||||
let [code, date_range, offset, companyId, includeUnposted] = args
|
||||
.map(astToFormula)
|
||||
.map((arg) => env.model.getters.evaluateFormula(arg));
|
||||
code = toString(code);
|
||||
const dateRange = parseAccountingDate(date_range);
|
||||
dateRange.year += offset || 0;
|
||||
companyId = companyId || null;
|
||||
includeUnposted = toBoolean(includeUnposted);
|
||||
|
||||
const action = await env.services.orm.call(
|
||||
"account.account",
|
||||
"spreadsheet_move_line_action",
|
||||
[camelToSnakeObject({ dateRange, companyId, code, includeUnposted })]
|
||||
);
|
||||
await env.services.action.doAction(action);
|
||||
},
|
||||
isVisible: (env) => {
|
||||
const cell = env.model.getters.getActiveCell();
|
||||
return (
|
||||
cell && !cell.evaluated.error &&
|
||||
cell.evaluated.value !== "" && getNumberOfAccountFormulas(cell.content) === 1
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||
import { AccountingDataSource } from "../accounting_datasource";
|
||||
const DATA_SOURCE_ID = "ACCOUNTING_AGGREGATES";
|
||||
|
||||
/**
|
||||
* @typedef {import("../accounting_functions").DateRange} DateRange
|
||||
*/
|
||||
|
||||
export default class AccountingPlugin extends spreadsheet.UIPlugin {
|
||||
constructor(getters, history, dispatch, config) {
|
||||
super(getters, history, dispatch, config);
|
||||
this.dataSources = config.dataSources;
|
||||
if (this.dataSources) {
|
||||
this.dataSources.add(DATA_SOURCE_ID, AccountingDataSource);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Getters
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets the total balance for given account code prefix
|
||||
* @param {string[]} codes prefixes of the accounts' codes
|
||||
* @param {DateRange} dateRange start date of the period to look
|
||||
* @param {number} offset end date of the period to look
|
||||
* @param {number | null} companyId specific company to target
|
||||
* @param {boolean} includeUnposted wether or not select unposted entries
|
||||
* @returns {number}
|
||||
*/
|
||||
getAccountPrefixCredit(codes, dateRange, offset, companyId, includeUnposted) {
|
||||
return (
|
||||
this.dataSources &&
|
||||
this.dataSources
|
||||
.get(DATA_SOURCE_ID)
|
||||
.getCredit(codes, dateRange, offset, companyId, includeUnposted)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total balance for a given account code prefix
|
||||
* @param {string[]} codes prefixes of the accounts codes
|
||||
* @param {DateRange} dateRange start date of the period to look
|
||||
* @param {number} offset end date of the period to look
|
||||
* @param {number | null} companyId specific company to target
|
||||
* @param {boolean} includeUnposted wether or not select unposted entries
|
||||
* @returns {number}
|
||||
*/
|
||||
getAccountPrefixDebit(codes, dateRange, offset, companyId, includeUnposted) {
|
||||
return (
|
||||
this.dataSources &&
|
||||
this.dataSources
|
||||
.get(DATA_SOURCE_ID)
|
||||
.getDebit(codes, dateRange, offset, companyId, includeUnposted)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} date Date included in the fiscal year
|
||||
* @param {number | null} companyId specific company to target
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
getFiscalStartDate(date, companyId) {
|
||||
return (
|
||||
this.dataSources &&
|
||||
this.dataSources.get(DATA_SOURCE_ID).getFiscalStartDate(date, companyId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} date Date included in the fiscal year
|
||||
* @param {number | undefined} companyId specific company to target
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
getFiscalEndDate(date, companyId) {
|
||||
return (
|
||||
this.dataSources &&
|
||||
this.dataSources.get(DATA_SOURCE_ID).getFiscalEndDate(date, companyId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} accountType
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getAccountGroupCodes(accountType) {
|
||||
return (
|
||||
this.dataSources &&
|
||||
this.dataSources.get(DATA_SOURCE_ID).getAccountGroupCodes(accountType)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AccountingPlugin.getters = [
|
||||
"getAccountPrefixCredit",
|
||||
"getAccountPrefixDebit",
|
||||
"getAccountGroupCodes",
|
||||
"getFiscalStartDate",
|
||||
"getFiscalEndDate",
|
||||
];
|
||||
@@ -0,0 +1,26 @@
|
||||
/** @odoo-module **/
|
||||
import { getOdooFunctions } from "@spreadsheet/helpers/odoo_functions_helpers";
|
||||
|
||||
/** @typedef {import("@spreadsheet/helpers/odoo_functions_helpers").OdooFunctionDescription} OdooFunctionDescription*/
|
||||
|
||||
/**
|
||||
* @param {string} formula
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getNumberOfAccountFormulas(formula) {
|
||||
return getOdooFunctions(formula, (functionName) =>
|
||||
["ODOO.BALANCE", "ODOO.CREDIT", "ODOO.DEBIT"].includes(functionName)
|
||||
).filter((fn) => fn.isMatched).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first Account function description of the given formula.
|
||||
*
|
||||
* @param {string} formula
|
||||
* @returns {OdooFunctionDescription | undefined}
|
||||
*/
|
||||
export function getFirstAccountFunction(formula) {
|
||||
return getOdooFunctions(formula, (functionName) =>
|
||||
["ODOO.BALANCE", "ODOO.CREDIT", "ODOO.DEBIT"].includes(functionName)
|
||||
).find((fn) => fn.isMatched);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { getBasicData } from "@spreadsheet/../tests/utils/data";
|
||||
|
||||
export function getAccountingData() {
|
||||
return {
|
||||
models: {
|
||||
...getBasicData(),
|
||||
"account.move.line": {
|
||||
fields: {
|
||||
account_id: { type: "many2one", relation: "account.account" },
|
||||
date: { string: "Date", type: "date" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, name: "line1", account_id: 1, date: "2022-06-01" },
|
||||
{ id: 2, name: "line2", account_id: 2, date: "2022-06-23" },
|
||||
],
|
||||
},
|
||||
"account.account": {
|
||||
fields: {
|
||||
code: { string: "Code", type: "string" },
|
||||
account_type: { string: "Account type", type: "string" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, code: "100104", account_type: "income" },
|
||||
{ id: 2, code: "100105", account_type: "income_other" },
|
||||
{ id: 3, code: "200104", account_type: "income" },
|
||||
],
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"account.move.line,false,list": /* xml */ `
|
||||
<tree string="Move Lines">
|
||||
<field name="id"/>
|
||||
<field name="account_id"/>
|
||||
<field name="date"/>
|
||||
</tree>
|
||||
`,
|
||||
"account.move.line,false,search": /* xml */ `<search/>`,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry
|
||||
.category("mock_server")
|
||||
.add("account.account/spreadsheet_fetch_debit_credit", function (route, args) {
|
||||
return new Array(args.args[0].length).fill({ credit: 0, debit: 0 });
|
||||
})
|
||||
.add("account.account/get_account_group", function (route, args, performRPC) {
|
||||
const accountTypes = args.args[0];
|
||||
const data = accountTypes.map((accountType) => {
|
||||
const records = this.mockSearchRead("account.account", [
|
||||
[["account_type", "=", accountType]],
|
||||
["code"],
|
||||
], {});
|
||||
return records.map((record) => record.code);
|
||||
});
|
||||
return data;
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { nextTick } from "@web/../tests/helpers/utils";
|
||||
import { setCellContent } from "@spreadsheet/../tests/utils/commands";
|
||||
import { createModelWithDataSource } from "@spreadsheet/../tests/utils/model";
|
||||
import { getCellValue } from "@spreadsheet/../tests/utils/getters";
|
||||
import { getAccountingData } from "../accounting_test_data";
|
||||
|
||||
let serverData;
|
||||
|
||||
function beforeEach() {
|
||||
serverData = getAccountingData();
|
||||
}
|
||||
|
||||
QUnit.module("spreadsheet_account > account groups", { beforeEach }, () => {
|
||||
QUnit.test("get no account", async (assert) => {
|
||||
const model = await createModelWithDataSource({ serverData });
|
||||
setCellContent(model, "A1", `=ODOO.ACCOUNT.GROUP("test")`);
|
||||
await nextTick();
|
||||
assert.equal(getCellValue(model, "A1"), "");
|
||||
});
|
||||
|
||||
QUnit.test("get one account", async (assert) => {
|
||||
const model = await createModelWithDataSource({ serverData });
|
||||
setCellContent(model, "A1", `=ODOO.ACCOUNT.GROUP("income_other")`);
|
||||
await nextTick();
|
||||
assert.equal(getCellValue(model, "A1"), "100105");
|
||||
});
|
||||
|
||||
QUnit.test("get multiple accounts", async (assert) => {
|
||||
const model = await createModelWithDataSource({ serverData });
|
||||
setCellContent(model, "A1", `=ODOO.ACCOUNT.GROUP("income")`);
|
||||
await nextTick();
|
||||
assert.equal(getCellValue(model, "A1"), "100104,200104");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,385 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { nextTick } from "@web/../tests/helpers/utils";
|
||||
import { setCellContent } from "@spreadsheet/../tests/utils/commands";
|
||||
import { createModelWithDataSource } from "@spreadsheet/../tests/utils/model";
|
||||
import { parseAccountingDate } from "../../src/accounting_functions";
|
||||
import { getCellValue, getCell } from "@spreadsheet/../tests/utils/getters";
|
||||
import { getAccountingData } from "../accounting_test_data";
|
||||
import { camelToSnakeObject } from "@spreadsheet/helpers/helpers";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
|
||||
let serverData;
|
||||
|
||||
function beforeEach() {
|
||||
serverData = getAccountingData();
|
||||
}
|
||||
|
||||
QUnit.module("spreadsheet_account > Accounting", { beforeEach }, () => {
|
||||
QUnit.module("Formulas");
|
||||
QUnit.test("Basic evaluation", async (assert) => {
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "spreadsheet_fetch_debit_credit") {
|
||||
assert.step("spreadsheet_fetch_debit_credit");
|
||||
return [{ debit: 42, credit: 16 }];
|
||||
}
|
||||
},
|
||||
});
|
||||
setCellContent(model, "A1", `=ODOO.CREDIT("100", "2022")`);
|
||||
setCellContent(model, "A2", `=ODOO.DEBIT("100", "2022")`);
|
||||
setCellContent(model, "A3", `=ODOO.BALANCE("100", "2022")`);
|
||||
await nextTick();
|
||||
assert.equal(getCellValue(model, "A1"), 16);
|
||||
assert.equal(getCellValue(model, "A2"), 42);
|
||||
assert.equal(getCellValue(model, "A3"), 26);
|
||||
assert.verifySteps(["spreadsheet_fetch_debit_credit"]);
|
||||
});
|
||||
|
||||
QUnit.test("Functions are correctly formatted", async (assert) => {
|
||||
const model = await createModelWithDataSource();
|
||||
setCellContent(model, "A1", `=ODOO.CREDIT("100", "2022")`);
|
||||
setCellContent(model, "A2", `=ODOO.DEBIT("100", "2022")`);
|
||||
setCellContent(model, "A3", `=ODOO.BALANCE("100", "2022")`);
|
||||
await nextTick();
|
||||
assert.strictEqual(getCell(model, "A1").evaluated.format, "#,##0.00[$€]");
|
||||
assert.strictEqual(getCell(model, "A2").evaluated.format, "#,##0.00[$€]");
|
||||
assert.strictEqual(getCell(model, "A3").evaluated.format, "#,##0.00[$€]");
|
||||
});
|
||||
|
||||
QUnit.test("Functions with a wrong company id is correctly in error", async (assert) => {
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "get_company_currency_for_spreadsheet") {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
setCellContent(model, "A1", `=ODOO.CREDIT("100", "2022", 0, 123456)`);
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
getCell(model, "A1").evaluated.error.message,
|
||||
"Currency not available for this company."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("formula with invalid date", async (assert) => {
|
||||
const model = await createModelWithDataSource();
|
||||
setCellContent(model, "A1", `=ODOO.CREDIT("100",)`);
|
||||
setCellContent(model, "A2", `=ODOO.DEBIT("100", 0)`);
|
||||
setCellContent(model, "A3", `=ODOO.BALANCE("100", -1)`);
|
||||
setCellContent(model, "A4", `=ODOO.BALANCE("100", "not a valid period")`);
|
||||
setCellContent(model, "A5", `=ODOO.BALANCE("100", 1900)`); // this should be ok
|
||||
setCellContent(model, "A6", `=ODOO.BALANCE("100", 1900, -1)`);
|
||||
setCellContent(model, "A7", `=ODOO.DEBIT("100", 1899)`);
|
||||
await nextTick();
|
||||
const errorMessage = `'%s' is not a valid period. Supported formats are "21/12/2022", "Q1/2022", "12/2022", and "2022".`
|
||||
assert.equal(getCell(model, "A1").evaluated.error.message, "0 is not a valid year.");
|
||||
assert.equal(getCell(model, "A2").evaluated.error.message, "0 is not a valid year.");
|
||||
assert.equal(getCell(model, "A3").evaluated.error.message, "-1 is not a valid year.");
|
||||
assert.equal(getCell(model, "A4").evaluated.error.message, sprintf(errorMessage, "not a valid period"));
|
||||
assert.equal(getCell(model, "A5").evaluated.value, 0);
|
||||
assert.equal(getCell(model, "A6").evaluated.error.message, "1899 is not a valid year.");
|
||||
assert.equal(getCell(model, "A7").evaluated.error.message, "1899 is not a valid year.");
|
||||
});
|
||||
|
||||
QUnit.test("Evaluation with multiple account codes", async (assert) => {
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "spreadsheet_fetch_debit_credit") {
|
||||
assert.step("spreadsheet_fetch_debit_credit");
|
||||
return [
|
||||
{ debit: 42, credit: 16 },
|
||||
{ debit: 100, credit: 10 },
|
||||
];
|
||||
}
|
||||
},
|
||||
});
|
||||
setCellContent(model, "A1", `=ODOO.CREDIT("100,200", "2022")`);
|
||||
setCellContent(model, "A2", `=ODOO.DEBIT("100,200", "2022")`);
|
||||
setCellContent(model, "A3", `=ODOO.BALANCE("100,200", "2022")`);
|
||||
await nextTick();
|
||||
assert.equal(getCellValue(model, "A1"), 26);
|
||||
assert.equal(getCellValue(model, "A2"), 142);
|
||||
assert.equal(getCellValue(model, "A3"), 116);
|
||||
assert.verifySteps(["spreadsheet_fetch_debit_credit"]);
|
||||
});
|
||||
|
||||
QUnit.test("Handle error evaluation", async (assert) => {
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "spreadsheet_fetch_debit_credit") {
|
||||
throw new Error("a nasty error");
|
||||
}
|
||||
},
|
||||
});
|
||||
setCellContent(model, "A1", `=ODOO.CREDIT("100", "2022")`);
|
||||
await nextTick();
|
||||
const cell = getCell(model, "A1");
|
||||
assert.equal(cell.evaluated.value, "#ERROR");
|
||||
assert.equal(cell.evaluated.error.message, "a nasty error");
|
||||
});
|
||||
|
||||
QUnit.test("Server requests", async (assert) => {
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "spreadsheet_fetch_debit_credit") {
|
||||
const blobs = args.args[0];
|
||||
for (let blob of blobs) {
|
||||
assert.step(JSON.stringify(blob));
|
||||
}
|
||||
}
|
||||
return [];
|
||||
},
|
||||
});
|
||||
setCellContent(model, "A1", `=ODOO.BALANCE("100", "2022")`);
|
||||
setCellContent(model, "A2", `=ODOO.CREDIT("100", "01/2022")`);
|
||||
setCellContent(model, "A3", `=ODOO.DEBIT("100","Q2/2022")`);
|
||||
setCellContent(model, "A4", `=ODOO.BALANCE("10", "2021")`);
|
||||
setCellContent(model, "A5", `=ODOO.CREDIT("10", "2022", -1)`); // same payload as A4: should only be called once
|
||||
setCellContent(model, "A6", `=ODOO.DEBIT("5", "2021", 0, 2)`);
|
||||
setCellContent(model, "A7", `=ODOO.DEBIT("5", "05/04/2021", 1)`);
|
||||
setCellContent(model, "A8", `=ODOO.BALANCE("5", "2022",,,FALSE)`);
|
||||
setCellContent(model, "A9", `=ODOO.BALANCE("100", "05/05/2022",,,TRUE)`);
|
||||
await nextTick();
|
||||
|
||||
assert.verifySteps([
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("2022"),
|
||||
code: "100",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("01/2022"),
|
||||
code: "100",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("Q2/2022"),
|
||||
code: "100",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("2021"),
|
||||
code: "10",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("2021"),
|
||||
code: "5",
|
||||
companyId: 2,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("05/04/2022"),
|
||||
code: "5",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("2022"),
|
||||
code: "5",
|
||||
companyId: null,
|
||||
includeUnposted: false,
|
||||
})
|
||||
),
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("05/05/2022"),
|
||||
code: "100",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("Server requests with multiple account codes", async (assert) => {
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "spreadsheet_fetch_debit_credit") {
|
||||
assert.step("spreadsheet_fetch_debit_credit");
|
||||
const blobs = args.args[0];
|
||||
for (let blob of blobs) {
|
||||
assert.step(JSON.stringify(blob));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
setCellContent(model, "A1", `=ODOO.BALANCE("100,200", "2022")`);
|
||||
setCellContent(model, "A2", `=ODOO.CREDIT("100,200", "2022")`);
|
||||
setCellContent(model, "A3", `=ODOO.DEBIT("100,200","2022")`);
|
||||
await nextTick();
|
||||
|
||||
assert.verifySteps([
|
||||
"spreadsheet_fetch_debit_credit",
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("2022"),
|
||||
code: "100",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("2022"),
|
||||
code: "200",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("account group formula as input to balance formula", async (assert) => {
|
||||
const model = await createModelWithDataSource({
|
||||
serverData,
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "spreadsheet_fetch_debit_credit") {
|
||||
assert.step("spreadsheet_fetch_debit_credit");
|
||||
const blobs = args.args[0];
|
||||
for (let blob of blobs) {
|
||||
assert.step(JSON.stringify(blob));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
setCellContent(model, "A1", `=ODOO.ACCOUNT.GROUP("income")`);
|
||||
setCellContent(model, "A2", `=ODOO.BALANCE(A1, 2022)`);
|
||||
assert.equal(getCellValue(model, "A1"), "Loading...");
|
||||
assert.equal(getCellValue(model, "A2"), "Loading...");
|
||||
await nextTick();
|
||||
assert.equal(getCellValue(model, "A1"), "100104,200104");
|
||||
assert.equal(getCellValue(model, "A2"), 0);
|
||||
assert.verifySteps([
|
||||
"spreadsheet_fetch_debit_credit",
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("2022"),
|
||||
code: "100104",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("2022"),
|
||||
code: "200104",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("two concurrent requests on different accounts", async (assert) => {
|
||||
const model = await createModelWithDataSource({
|
||||
serverData,
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "spreadsheet_fetch_debit_credit") {
|
||||
assert.step("spreadsheet_fetch_debit_credit");
|
||||
const blobs = args.args[0];
|
||||
for (let blob of blobs) {
|
||||
assert.step(JSON.stringify(blob));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
setCellContent(model, "A1", `=ODOO.ACCOUNT.GROUP("income")`);
|
||||
setCellContent(model, "A2", `=ODOO.BALANCE(A1, 2022)`); // batched only when A1 resolves
|
||||
setCellContent(model, "A3", `=ODOO.BALANCE("100", 2022)`); // batched directly
|
||||
assert.equal(getCellValue(model, "A1"), "Loading...");
|
||||
assert.equal(getCellValue(model, "A2"), "Loading...");
|
||||
assert.equal(getCellValue(model, "A3"), "Loading...");
|
||||
// A lot happens within the next tick.
|
||||
// Because cells are evaluated given their order in the sheet,
|
||||
// A1's request is done first, meaning it's also resolved first, which add A2 to the next batch (synchronously)
|
||||
// Only then A3 is resolved. => A2 is batched while A3 is pending
|
||||
await nextTick();
|
||||
assert.equal(getCellValue(model, "A1"), "100104,200104");
|
||||
assert.equal(getCellValue(model, "A2"), 0);
|
||||
assert.equal(getCellValue(model, "A3"), 0);
|
||||
assert.verifySteps([
|
||||
"spreadsheet_fetch_debit_credit",
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("2022"),
|
||||
code: "100",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
"spreadsheet_fetch_debit_credit",
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("2022"),
|
||||
code: "100104",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
JSON.stringify(
|
||||
camelToSnakeObject({
|
||||
dateRange: parseAccountingDate("2022"),
|
||||
code: "200104",
|
||||
companyId: null,
|
||||
includeUnposted: true,
|
||||
})
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parseAccountingDate", (assert) => {
|
||||
assert.deepEqual(parseAccountingDate("2022"), {
|
||||
rangeType: "year",
|
||||
year: 2022,
|
||||
});
|
||||
assert.deepEqual(parseAccountingDate("11/10/2022"), {
|
||||
rangeType: "day",
|
||||
year: 2022,
|
||||
month: 11,
|
||||
day: 10,
|
||||
});
|
||||
assert.deepEqual(parseAccountingDate("10/2022"), {
|
||||
rangeType: "month",
|
||||
year: 2022,
|
||||
month: 10,
|
||||
});
|
||||
assert.deepEqual(parseAccountingDate("Q1/2022"), {
|
||||
rangeType: "quarter",
|
||||
year: 2022,
|
||||
quarter: 1,
|
||||
});
|
||||
assert.deepEqual(parseAccountingDate("q4/2022"), {
|
||||
rangeType: "quarter",
|
||||
year: 2022,
|
||||
quarter: 4,
|
||||
});
|
||||
// A number below 3000 is interpreted as a year.
|
||||
// It's interpreted as a regular spreadsheet date otherwise
|
||||
assert.deepEqual(parseAccountingDate("3005"), {
|
||||
rangeType: "day",
|
||||
year: 1908,
|
||||
month: 3,
|
||||
day: 23,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
/** @odoo-module */
|
||||
import { nextTick } from "@web/../tests/helpers/utils";
|
||||
import { setCellContent } from "@spreadsheet/../tests/utils/commands";
|
||||
import { createModelWithDataSource } from "@spreadsheet/../tests/utils/model";
|
||||
import { getCell } from "@spreadsheet/../tests/utils/getters";
|
||||
import "@spreadsheet_account/index";
|
||||
|
||||
QUnit.module("spreadsheet_account > fiscal year", {}, () => {
|
||||
QUnit.test("Basic evaluation", async (assert) => {
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "get_fiscal_dates") {
|
||||
assert.step("get_fiscal_dates");
|
||||
assert.deepEqual(args.args, [
|
||||
[
|
||||
{
|
||||
date: "2020-11-11",
|
||||
company_id: null,
|
||||
},
|
||||
],
|
||||
]);
|
||||
return [{ start: "2020-01-01", end: "2020-12-31" }];
|
||||
}
|
||||
},
|
||||
});
|
||||
setCellContent(model, "A1", `=ODOO.FISCALYEAR.START("11/11/2020")`);
|
||||
setCellContent(model, "A2", `=ODOO.FISCALYEAR.END("11/11/2020")`);
|
||||
await nextTick();
|
||||
assert.verifySteps(["get_fiscal_dates"]);
|
||||
assert.equal(getCell(model, "A1").formattedValue, "1/1/2020");
|
||||
assert.equal(getCell(model, "A2").formattedValue, "12/31/2020");
|
||||
});
|
||||
|
||||
QUnit.test("with a given company id", async (assert) => {
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "get_fiscal_dates") {
|
||||
assert.step("get_fiscal_dates");
|
||||
assert.deepEqual(args.args, [
|
||||
[
|
||||
{
|
||||
date: "2020-11-11",
|
||||
company_id: 1,
|
||||
},
|
||||
],
|
||||
]);
|
||||
return [{ start: "2020-01-01", end: "2020-12-31" }];
|
||||
}
|
||||
},
|
||||
});
|
||||
setCellContent(model, "A1", `=ODOO.FISCALYEAR.START("11/11/2020", 1)`);
|
||||
setCellContent(model, "A2", `=ODOO.FISCALYEAR.END("11/11/2020", 1)`);
|
||||
await nextTick();
|
||||
assert.verifySteps(["get_fiscal_dates"]);
|
||||
assert.equal(getCell(model, "A1").formattedValue, "1/1/2020");
|
||||
assert.equal(getCell(model, "A2").formattedValue, "12/31/2020");
|
||||
});
|
||||
|
||||
QUnit.test("with a wrong company id", async (assert) => {
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "get_fiscal_dates") {
|
||||
assert.step("get_fiscal_dates");
|
||||
assert.deepEqual(args.args, [
|
||||
[
|
||||
{
|
||||
date: "2020-11-11",
|
||||
company_id: 100,
|
||||
},
|
||||
],
|
||||
]);
|
||||
return [false];
|
||||
}
|
||||
},
|
||||
});
|
||||
setCellContent(model, "A1", `=ODOO.FISCALYEAR.START("11/11/2020", 100)`);
|
||||
setCellContent(model, "A2", `=ODOO.FISCALYEAR.END("11/11/2020", 100)`);
|
||||
await nextTick();
|
||||
assert.verifySteps(["get_fiscal_dates"]);
|
||||
assert.equal(
|
||||
getCell(model, "A1").evaluated.error.message,
|
||||
"The company fiscal year could not be found."
|
||||
);
|
||||
assert.equal(
|
||||
getCell(model, "A2").evaluated.error.message,
|
||||
"The company fiscal year could not be found."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("with wrong input arguments", async (assert) => {
|
||||
const model = await createModelWithDataSource();
|
||||
setCellContent(model, "A1", `=ODOO.FISCALYEAR.START("not a number")`);
|
||||
setCellContent(model, "A2", `=ODOO.FISCALYEAR.END("11/11/2020", "not a number")`);
|
||||
assert.equal(
|
||||
getCell(model, "A1").evaluated.error.message,
|
||||
"The function ODOO.FISCALYEAR.START expects a number value, but 'not a number' is a string, and cannot be coerced to a number."
|
||||
);
|
||||
assert.equal(
|
||||
getCell(model, "A2").evaluated.error.message,
|
||||
"The function ODOO.FISCALYEAR.END expects a number value, but 'not a number' is a string, and cannot be coerced to a number."
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { nextTick } from "@web/../tests/helpers/utils";
|
||||
import { selectCell, setCellContent } from "@spreadsheet/../tests/utils/commands";
|
||||
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||
import { getAccountingData } from "../accounting_test_data";
|
||||
import { createModelWithDataSource } from "@spreadsheet/../tests/utils/model";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const { cellMenuRegistry } = spreadsheet.registries;
|
||||
|
||||
let serverData;
|
||||
|
||||
function beforeEach() {
|
||||
serverData = getAccountingData();
|
||||
}
|
||||
|
||||
QUnit.module("spreadsheet_account > Accounting Drill down", { beforeEach }, () => {
|
||||
QUnit.test("Create drill down domain", async (assert) => {
|
||||
const drillDownAction = {
|
||||
type: "ir.actions.act_window",
|
||||
res_model: "account.move.line",
|
||||
view_mode: "list",
|
||||
views: [[false, "list"]],
|
||||
target: "current",
|
||||
domain: [["account_id", "in", [1, 2]]],
|
||||
name: "my awesome action",
|
||||
};
|
||||
const fakeActionService = {
|
||||
name: "action",
|
||||
start() {
|
||||
return {
|
||||
async doAction(action, options) {
|
||||
assert.step("drill down action");
|
||||
assert.deepEqual(action, drillDownAction);
|
||||
assert.equal(options, undefined);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
registry.category("services").add("action", fakeActionService, { force: true });
|
||||
|
||||
const model = await createModelWithDataSource({
|
||||
serverData,
|
||||
mockRPC: async function (route, args) {
|
||||
if (args.method === "spreadsheet_move_line_action") {
|
||||
assert.deepEqual(args.args, [
|
||||
{
|
||||
code: "100",
|
||||
company_id: null,
|
||||
include_unposted: false,
|
||||
date_range: {
|
||||
range_type: "year",
|
||||
year: 2020,
|
||||
},
|
||||
},
|
||||
]);
|
||||
return drillDownAction;
|
||||
}
|
||||
},
|
||||
});
|
||||
const env = model.config.evalContext.env;
|
||||
env.model = model;
|
||||
setCellContent(model, "A1", `=ODOO.BALANCE("100", 2020)`);
|
||||
setCellContent(model, "A2", `=ODOO.BALANCE("100", 0)`);
|
||||
await nextTick();
|
||||
selectCell(model, "A1");
|
||||
const root = cellMenuRegistry.getAll().find((item) => item.id === "move_lines_see_records");
|
||||
assert.equal(root.isVisible(env), true);
|
||||
await root.action(env);
|
||||
assert.verifySteps(["drill down action"]);
|
||||
selectCell(model, "A2");
|
||||
assert.equal(root.isVisible(env), false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_account_group
|
||||
from . import test_debit_credit
|
||||
from . import test_company_fiscal_year
|
||||
@@ -0,0 +1,87 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class SpreadsheetAccountGroupTest(TransactionCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.env["account.account"].create(
|
||||
{
|
||||
"company_id": cls.env.user.company_id.id,
|
||||
"name": "spreadsheet revenue Company 1",
|
||||
"account_type": "income",
|
||||
"code": "123",
|
||||
}
|
||||
)
|
||||
|
||||
cls.env["account.account"].create(
|
||||
{
|
||||
"company_id": cls.env.user.company_id.id,
|
||||
"name": "spreadsheet expense Company 1",
|
||||
"account_type": "income_other",
|
||||
"code": "456",
|
||||
}
|
||||
)
|
||||
|
||||
cls.env["account.account"].create(
|
||||
{
|
||||
"company_id": cls.env.user.company_id.id,
|
||||
"name": "spreadsheet revenue Company 2",
|
||||
"account_type": "income",
|
||||
"code": "789",
|
||||
}
|
||||
)
|
||||
|
||||
def _unlink_default_accounts(self, account_ids):
|
||||
used_account_id = self.env["account.account"].search([("code", '=', '123')])
|
||||
aml_ids = self.env["account.move.line"].search([("account_id", "in", account_ids.ids), ("company_id", "=", self.env.company.id)])
|
||||
journal_ids = self.env["account.journal"].search([("default_account_id", "in", account_ids.ids), ("company_id", "=", self.env.company.id)])
|
||||
for aml_id in aml_ids:
|
||||
aml_id.account_id = used_account_id
|
||||
for journal_id in journal_ids:
|
||||
journal_id.default_account_id = used_account_id
|
||||
for account_id in account_ids:
|
||||
self.env["ir.property"]._set_default("property_account_income_categ_id", "product.category", used_account_id, self.env.company)
|
||||
account_id.unlink()
|
||||
|
||||
def test_fetch_account_no_group(self):
|
||||
self.assertEqual(self.env["account.account"].get_account_group([]), [])
|
||||
|
||||
def test_fetch_account_one_group(self):
|
||||
account_ids = self.env["account.account"].search([("account_type", "=", "income_other"), ("company_id", "=", self.env.company.id), ("code", "!=", "456")])
|
||||
self._unlink_default_accounts(account_ids)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].get_account_group(["income_other"]),
|
||||
[["456"]],
|
||||
)
|
||||
|
||||
def test_group_with_no_account(self):
|
||||
account_ids = self.env["account.account"].search([("account_type", "=", "income_other"), ("company_id", "=", self.env.company.id)])
|
||||
self._unlink_default_accounts(account_ids)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].get_account_group(["income_other"]), [[]]
|
||||
)
|
||||
|
||||
def test_with_wrong_account_type_id(self):
|
||||
self.assertEqual(self.env["account.account"].get_account_group([999999]), [[]])
|
||||
|
||||
def test_group_with_multiple_accounts(self):
|
||||
account_ids = self.env["account.account"].search([("account_type", "=", "income"), ("company_id", "=", self.env.company.id), ("code", "not in", ["123", "789"])])
|
||||
self._unlink_default_accounts(account_ids)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].get_account_group(["income"]),
|
||||
[["123", "789"]],
|
||||
)
|
||||
|
||||
def test_response_is_ordered(self):
|
||||
o1_codes_1, o1_codes_2 = self.env["account.account"].get_account_group(
|
||||
["income", "income_other"]
|
||||
)
|
||||
o2_codes_2, o2_codes_1 = self.env["account.account"].get_account_group(
|
||||
["income_other", "income"]
|
||||
)
|
||||
self.assertEqual(o1_codes_1, o2_codes_1)
|
||||
self.assertEqual(o1_codes_2, o2_codes_2)
|
||||
@@ -0,0 +1,98 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
from datetime import date
|
||||
|
||||
|
||||
class SpreadsheetFiscalYearTest(TransactionCase):
|
||||
def test_fiscal_year_reference(self):
|
||||
self.env.company.fiscalyear_last_day = 3
|
||||
self.env.company.fiscalyear_last_month = "2"
|
||||
|
||||
self.assertEqual(
|
||||
self.env["res.company"].get_fiscal_dates(
|
||||
[{"company_id": None, "date": "2020-03-05"}]
|
||||
),
|
||||
[
|
||||
{"start": date(2020, 2, 4), "end": date(2021, 2, 3)},
|
||||
],
|
||||
)
|
||||
|
||||
def test_fiscal_year_reference_last_day(self):
|
||||
self.env.company.fiscalyear_last_day = 3
|
||||
self.env.company.fiscalyear_last_month = "2"
|
||||
|
||||
self.assertEqual(
|
||||
self.env["res.company"].get_fiscal_dates(
|
||||
[{"company_id": None, "date": "2020-02-03"}]
|
||||
),
|
||||
[
|
||||
{"start": date(2019, 2, 4), "end": date(2020, 2, 3)},
|
||||
],
|
||||
)
|
||||
|
||||
def test_fiscal_year_reference_first_day(self):
|
||||
self.env.company.fiscalyear_last_day = 3
|
||||
self.env.company.fiscalyear_last_month = "2"
|
||||
|
||||
self.assertEqual(
|
||||
self.env["res.company"].get_fiscal_dates(
|
||||
[{"company_id": None, "date": "2020-02-04"}]
|
||||
),
|
||||
[{"start": date(2020, 2, 4), "end": date(2021, 2, 3)}],
|
||||
)
|
||||
|
||||
def test_fiscal_year_with_company_id(self):
|
||||
company = self.env["res.company"].create(
|
||||
{
|
||||
"name": "test company",
|
||||
"fiscalyear_last_day": 3,
|
||||
"fiscalyear_last_month": "2",
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["res.company"].get_fiscal_dates(
|
||||
[
|
||||
{"company_id": company.id, "date": "2020-02-04"},
|
||||
{"company_id": None, "date": "2020-02-04"},
|
||||
]
|
||||
),
|
||||
[
|
||||
{"start": date(2020, 2, 4), "end": date(2021, 2, 3)},
|
||||
{"start": date(2020, 1, 1), "end": date(2020, 12, 31)},
|
||||
],
|
||||
)
|
||||
|
||||
def test_result_order(self):
|
||||
company = self.env["res.company"].create(
|
||||
{
|
||||
"name": "test company",
|
||||
"fiscalyear_last_day": 3,
|
||||
"fiscalyear_last_month": "2",
|
||||
}
|
||||
)
|
||||
request1 = {"company_id": company.id, "date": "2020-02-04"}
|
||||
request2 = {"company_id": None, "date": "2020-02-04"}
|
||||
[o1_request1, o1_request2] = self.env["res.company"].get_fiscal_dates(
|
||||
[request1, request2]
|
||||
)
|
||||
[o2_request2, o2_request1] = self.env["res.company"].get_fiscal_dates(
|
||||
[request2, request1]
|
||||
)
|
||||
self.assertEqual(o1_request1, o2_request1)
|
||||
self.assertEqual(o1_request2, o2_request2)
|
||||
|
||||
def test_fiscal_year_with_wrong_company_id(self):
|
||||
self.assertEqual(
|
||||
self.env["res.company"].get_fiscal_dates(
|
||||
[
|
||||
{"company_id": 999, "date": "2020-02-04"},
|
||||
{"company_id": None, "date": "2020-02-04"},
|
||||
]
|
||||
),
|
||||
[
|
||||
False,
|
||||
{"start": date(2020, 1, 1), "end": date(2020, 12, 31)},
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,866 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import date
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class SpreadsheetAccountingFunctionsTest(AccountTestInvoicingCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
cls.account_revenue_c1 = cls.env["account.account"].create(
|
||||
{
|
||||
"company_id": cls.company_data["company"].id,
|
||||
"name": "spreadsheet revenue Company 1",
|
||||
"account_type": "income",
|
||||
"code": "sp1234566",
|
||||
}
|
||||
)
|
||||
|
||||
cls.account_expense_c1 = cls.env["account.account"].create(
|
||||
{
|
||||
"company_id": cls.company_data["company"].id,
|
||||
"name": "spreadsheet expense Company 1",
|
||||
"account_type": "expense",
|
||||
"code": "sp1234577",
|
||||
}
|
||||
)
|
||||
|
||||
cls.account_revenue_c2 = cls.env["account.account"].create(
|
||||
{
|
||||
"company_id": cls.company_data_2["company"].id,
|
||||
"name": "spreadsheet revenue Company 2",
|
||||
"account_type": "income",
|
||||
"code": "sp99887755",
|
||||
}
|
||||
)
|
||||
|
||||
cls.account_expense_c2 = cls.env["account.account"].create(
|
||||
{
|
||||
"company_id": cls.company_data_2["company"].id,
|
||||
"name": "spreadsheet expense Company 2",
|
||||
"account_type": "expense",
|
||||
"code": "sp99887766",
|
||||
}
|
||||
)
|
||||
|
||||
cls.env["account.move"].create(
|
||||
{
|
||||
"company_id": cls.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-04-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": cls.account_revenue_c1.id,
|
||||
"debit": 500,
|
||||
}
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": cls.account_expense_c1.id,
|
||||
"credit": 500,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
cls.env["account.move"].with_company(cls.company_data_2["company"]).create(
|
||||
{
|
||||
"company_id": cls.company_data_2["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-02-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c2",
|
||||
"account_id": cls.account_revenue_c2.id,
|
||||
"debit": 1500,
|
||||
"company_id": cls.company_data_2["company"].id,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c2",
|
||||
"account_id": cls.account_expense_c2.id,
|
||||
"credit": 1500,
|
||||
"company_id": cls.company_data_2["company"].id,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
def test_empty_payload(self):
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit([]), []
|
||||
)
|
||||
|
||||
def test_exact_code(self):
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2022,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[{"credit": 0.0, "debit": 500.0}],
|
||||
)
|
||||
|
||||
def test_response_order(self):
|
||||
request_1 = {
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2022,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
request_2 = {
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2020,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
[o1_res1, o1_res2] = self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[request_1, request_2]
|
||||
)
|
||||
[o2_res2, o2_res1] = self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[request_2, request_1]
|
||||
)
|
||||
self.assertEqual(o1_res1, o2_res1)
|
||||
self.assertEqual(o1_res2, o2_res2)
|
||||
|
||||
def test_prefix_code(self):
|
||||
code = "sp1234"
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2022,
|
||||
},
|
||||
"code": code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 500.0, "debit": 500.0},
|
||||
],
|
||||
)
|
||||
|
||||
def test_do_not_count_future_years(self):
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-04-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 1000,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_expense_c1.id,
|
||||
"credit": 1000,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2021,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 0, "debit": 0.0},
|
||||
],
|
||||
)
|
||||
|
||||
def test_year_date_period(self):
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2020-04-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 1000,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_expense_c1.id,
|
||||
"credit": 1000,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2021,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 0, "debit": 0.0},
|
||||
],
|
||||
)
|
||||
|
||||
def test_shifted_fiscal_year_date_period(self):
|
||||
self.company_data["company"].fiscalyear_last_day = 3
|
||||
self.company_data["company"].fiscalyear_last_month = "2"
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2023-01-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 1000,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_expense_c1.id,
|
||||
"credit": 1000,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2022,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[{"credit": 0, "debit": 1500.0}],
|
||||
)
|
||||
|
||||
def test_quarter_date_period(self):
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-07-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 777,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"credit": 777,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "quarter",
|
||||
"year": 2022,
|
||||
"quarter": 3,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[{"credit": 777, "debit": 777}],
|
||||
)
|
||||
|
||||
def test_month_date_period(self):
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-07-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 666,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"credit": 666,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {"range_type": "month", "year": 2022, "month": 7},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[{"credit": 666, "debit": 666}],
|
||||
)
|
||||
|
||||
def test_day_date_period(self):
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-07-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 555,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"credit": 555,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "day",
|
||||
"year": 2022,
|
||||
"month": 7,
|
||||
"day": 2,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[{"credit": 555, "debit": 1055}],
|
||||
)
|
||||
|
||||
def test_first_fiscal_day_date_period(self):
|
||||
self.company_data["company"].fiscalyear_last_day = 3
|
||||
self.company_data["company"].fiscalyear_last_month = "2"
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-02-03",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 111,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"credit": 111,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-02-04",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 423,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"credit": 423,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "day",
|
||||
"year": 2022,
|
||||
"month": 2,
|
||||
"day": 4,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 423, "debit": 423},
|
||||
],
|
||||
)
|
||||
|
||||
def test_balance_account_by_year(self):
|
||||
# On balance accounts, we sum the lines from the creation up to the last dat of date_range
|
||||
self.account_revenue_c1.sudo().include_initial_balance = True
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2025,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 0, "debit": 500},
|
||||
],
|
||||
)
|
||||
|
||||
def test_balance_quarter_date_period(self):
|
||||
self.account_revenue_c1.sudo().include_initial_balance = True
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-07-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 777,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"credit": 777,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "quarter",
|
||||
"year": 2022,
|
||||
"quarter": 3,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 777, "debit": 1277},
|
||||
],
|
||||
)
|
||||
|
||||
def test_balance_month_date_period(self):
|
||||
self.account_revenue_c1.sudo().include_initial_balance = True
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-07-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 777,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"credit": 777,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {"range_type": "month", "year": 2022, "month": 7},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 777, "debit": 1277},
|
||||
],
|
||||
)
|
||||
|
||||
def test_balance_day_date_period(self):
|
||||
self.account_revenue_c1.sudo().include_initial_balance = True
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-07-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 777,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"credit": 777,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "day",
|
||||
"year": 2022,
|
||||
"month": 7,
|
||||
"day": 2,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 777, "debit": 1277},
|
||||
],
|
||||
)
|
||||
|
||||
def test_move_state_ignore_cancel(self):
|
||||
self.account_revenue_c1.sudo().include_initial_balance = True
|
||||
self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-04-02",
|
||||
"state": "cancel",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 10000000,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"credit": 10000000,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2022,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": False,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 0, "debit": 0},
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2022,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 0, "debit": 500},
|
||||
],
|
||||
)
|
||||
|
||||
def test_move_state_unposted(self):
|
||||
self.account_revenue_c1.sudo().include_initial_balance = True
|
||||
move = self.env["account.move"].create(
|
||||
{
|
||||
"company_id": self.company_data["company"].id,
|
||||
"move_type": "entry",
|
||||
"date": "2022-04-02",
|
||||
"line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_debit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"debit": 888,
|
||||
},
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"name": "line_credit_c1",
|
||||
"account_id": self.account_revenue_c1.id,
|
||||
"credit": 888,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
move._post()
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2022,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": False,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 888, "debit": 888},
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2022,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 888, "debit": 1388},
|
||||
],
|
||||
)
|
||||
|
||||
def test_no_code(self):
|
||||
self.assertEqual(
|
||||
self.env["account.account"].spreadsheet_fetch_debit_credit(
|
||||
[
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2022,
|
||||
},
|
||||
"code": "",
|
||||
"company_id": None,
|
||||
"include_unposted": False,
|
||||
}
|
||||
]
|
||||
),
|
||||
[
|
||||
{"credit": 0, "debit": 0},
|
||||
],
|
||||
)
|
||||
|
||||
def test_see_records_action(self):
|
||||
action = self.env["account.account"].spreadsheet_move_line_action(
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2022,
|
||||
},
|
||||
"code": self.account_revenue_c1.code,
|
||||
"company_id": self.account_revenue_c1.company_id.id,
|
||||
"include_unposted": True,
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
action,
|
||||
{
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "account.move.line",
|
||||
"view_mode": "list",
|
||||
"views": [[False, "list"]],
|
||||
"target": "current",
|
||||
"domain": [
|
||||
"|",
|
||||
"&",
|
||||
"&",
|
||||
"&",
|
||||
("account_id.code", "=like", "sp1234566%"),
|
||||
("company_id", "=", self.account_revenue_c1.company_id.id),
|
||||
("move_id.state", "!=", "cancel"),
|
||||
"&",
|
||||
("account_id.include_initial_balance", "=", True),
|
||||
("date", "<=", date(2022, 12, 31)),
|
||||
"&",
|
||||
"&",
|
||||
"&",
|
||||
("account_id.code", "=like", "sp1234566%"),
|
||||
("company_id", "=", self.account_revenue_c1.company_id.id),
|
||||
("move_id.state", "!=", "cancel"),
|
||||
"&",
|
||||
"&",
|
||||
("account_id.include_initial_balance", "=", False),
|
||||
("date", ">=", date(2022, 1, 1)),
|
||||
("date", "<=", date(2022, 12, 31)),
|
||||
],
|
||||
"name": "Journal items for account prefix sp1234566",
|
||||
},
|
||||
)
|
||||
|
||||
def test_see_records_action_no_code(self):
|
||||
action = self.env["account.account"].spreadsheet_move_line_action(
|
||||
{
|
||||
"date_range": {
|
||||
"range_type": "year",
|
||||
"year": 2022,
|
||||
},
|
||||
"code": "",
|
||||
"company_id": None,
|
||||
"include_unposted": True,
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
action,
|
||||
{
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "account.move.line",
|
||||
"view_mode": "list",
|
||||
"views": [[False, "list"]],
|
||||
"target": "current",
|
||||
"domain": [(0, "=", 1)],
|
||||
"name": "Journal items for account prefix ",
|
||||
},
|
||||
)
|
||||
@@ -91,8 +91,8 @@ addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
|
||||
!spreadsheet_edition/**/*
|
||||
|
||||
# Whitelist spreadsheet_account
|
||||
!spreadsheet_account
|
||||
!spreadsheet_account/**/*
|
||||
!addons/spreadsheet_account
|
||||
!addons/spreadsheet_account/**/*
|
||||
|
||||
!addons/auth_password_policy
|
||||
!addons/auth_password_policy/**/*
|
||||
|
||||
@@ -90,9 +90,13 @@ addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
|
||||
!spreadsheet_edition
|
||||
!spreadsheet_edition/**/*
|
||||
|
||||
# Whitelist spreadsheet_edition
|
||||
!spreadsheet_edition
|
||||
!spreadsheet_edition/**/*
|
||||
|
||||
# Whitelist spreadsheet_account
|
||||
!spreadsheet_account
|
||||
!spreadsheet_account/**/*
|
||||
!addons/spreadsheet_account
|
||||
!addons/spreadsheet_account/**/*
|
||||
|
||||
!addons/auth_password_policy
|
||||
!addons/auth_password_policy/**/*
|
||||
|
||||
Reference in New Issue
Block a user