[MERGE] forward port branch saas-11.4 up to 57e387b645

This commit is contained in:
Christophe Simonis
2018-10-01 16:01:54 +02:00
85 changed files with 1010 additions and 201 deletions
+2
View File
@@ -203,6 +203,8 @@ class AccountAccount(models.Model):
@api.onchange('internal_type')
def onchange_internal_type(self):
self.reconcile = self.internal_type in ('receivable', 'payable')
if self.internal_type == 'liquidity':
self.reconcile = False
@api.onchange('code')
def onchange_code(self):
+1 -1
View File
@@ -1575,7 +1575,7 @@ class AccountInvoiceLine(models.Model):
ondelete='set null', index=True, oldname='uos_id')
product_id = fields.Many2one('product.product', string='Product',
ondelete='restrict', index=True)
product_image = fields.Binary('Product Image', related="product_id.image", store=False, readonly=False)
product_image = fields.Binary('Product Image', related="product_id.image", store=False, readonly=True)
account_id = fields.Many2one('account.account', string='Account', domain=[('deprecated', '=', False)],
default=_default_account,
help="The income or expense account related to the selected product.")
+3 -2
View File
@@ -396,7 +396,7 @@ class AccountMove(models.Model):
reversed_move = self.copy(default={
'date': date,
'journal_id': journal_id.id if journal_id else self.journal_id.id,
'ref': _('%sreversal of: ') % (_('Automatic ') if auto else '') + self.name,
'ref': (_('Automatic reversal of: %s') if auto else _('Reversal of: %s')) % (self.name),
'auto_reverse': False})
for acm_line in reversed_move.line_ids.with_context(check_move_validity=False):
acm_line.write({
@@ -1268,8 +1268,9 @@ class AccountMoveLine(models.Model):
"""
self.ensure_one()
amount = -self.balance * distribution.percentage / 100.0
default_name = self.name or (self.ref or '/' + ' -- ' + (self.partner_id and self.partner_id.name or '/'))
return {
'name': self.name,
'name': default_name,
'date': self.date,
'account_id': distribution.account_id.id,
'partner_id': self.partner_id.id,
+2 -2
View File
@@ -22,8 +22,8 @@
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
<field name="currency_id" options="{'no_create': True}" groups="base.group_multi_currency"/>
<field name="internal_type" invisible="1" readonly="1"/>
<label for="reconcile"/>
<div>
<label for="reconcile" attrs="{'invisible': [('internal_type','=','liquidity')]}"/>
<div attrs="{'invisible': [('internal_type','=','liquidity')]}">
<field name="reconcile"/>
<button name="action_open_reconcile" class="oe_link" type="object" string=" -> Reconcile" attrs="{'invisible': [('reconcile', '=', False)]}"/>
</div>
@@ -236,9 +236,10 @@ class CrossoveredBudgetLines(models.Model):
@api.constrains('general_budget_id', 'analytic_account_id')
def _must_have_analytical_or_budgetary_or_both(self):
if not self.analytic_account_id and not self.general_budget_id:
raise ValidationError(
_("You have to enter at least a budgetary position or analytic account on a budget line."))
for record in self:
if not record.analytic_account_id and not record.general_budget_id:
raise ValidationError(
_("You have to enter at least a budgetary position or analytic account on a budget line."))
@api.multi
def action_open_budget_entries(self):
@@ -28,7 +28,7 @@ class PaymentPortal(http.Controller):
except:
return False
if request.env.user == request.env.ref('base.public_user'):
if request.env.user._is_public():
save_token = False # we avoid to create a token for the public user
vals = {
'acquirer_id': acquirer_id,
@@ -69,7 +69,7 @@ class PaymentPortal(http.Controller):
token = request.env['payment.token'].sudo().browse(int(pm_id))
except (ValueError, TypeError):
token = False
token_owner = invoice_sudo.partner_id if request.env.user == request.env.ref('base.public_user') else request.env.user.partner_id
token_owner = invoice_sudo.partner_id if request.env.user._is_public() else request.env.user.partner_id
if not token or token.partner_id != token_owner:
params['error'] = 'pay_invoice_invalid_token'
return request.redirect(_build_url_w_params(error_url, params))
+1 -1
View File
@@ -11,7 +11,7 @@ class PortalAccount(PortalAccount):
values = super(PortalAccount, self)._invoice_get_page_view_values(invoice, access_token, **kwargs)
payment_inputs = request.env['payment.acquirer']._get_available_payment_input(company=invoice.company_id)
# if not connected (using public user), the method _get_available_payment_input will return public user tokens
is_public_user = request.env.ref('base.public_user') == request.env.user
is_public_user = request.env.user._is_public()
if is_public_user:
# we should not display payment tokens owned by the public user
payment_inputs.pop('pms', None)
+2 -3
View File
@@ -1089,9 +1089,8 @@ class Meeting(models.Model):
else:
sort_fields[field] = self[field]
if isinstance(self[field], models.BaseModel):
name_get = self[field].name_get()
if len(name_get) and len(name_get[0]) >= 2:
sort_fields[field] = name_get[0][1]
name_get = self[field].mapped('display_name')
sort_fields[field] = name_get and name_get[0] or ''
if r_date:
sort_fields['sort_start'] = r_date.strftime(VIRTUALID_DATETIME_FORMAT)
else:
+4 -4
View File
@@ -50,10 +50,10 @@ Sidebar.include({
* @override
*/
updateEnv: function (env) {
if (this.hasAttachments) {
this.env = env;
this._updateAttachments().then(this._redraw.bind(this));
}
this.env = env;
var _super = _.bind(this._super, this, env);
var def = this.hasAttachments ? this._updateAttachments() : $.when();
def.then(_super);
},
//--------------------------------------------------------------------------
+3 -2
View File
@@ -59,7 +59,7 @@ Sidebar.include({
}
if (!already_there) {
self._addItems('other', [{
callback: self._onGoogleDocItemClicked.bind(self, res.id, resID),
callback: self._onGoogleDocItemClicked.bind(self, res.id),
classname: 'oe_share_gdoc',
config_id: res.id,
label: res.name,
@@ -81,8 +81,9 @@ Sidebar.include({
* @param {integer} configID
* @param {integer} resID
*/
_onGoogleDocItemClicked: function (configID, resID) {
_onGoogleDocItemClicked: function (configID) {
var self = this;
var resID = this.env.activeIds[0];
var domain = [['id', '=', configID]];
var fields = ['google_drive_resource_id', 'google_drive_client_id'];
this._rpc({
@@ -33,6 +33,9 @@ QUnit.module('gdrive_integration', {
records: [{
id: 1,
display_name: "Locomotive Breath",
}, {
id: 2,
display_name: "Hey Macarena",
}],
},
'google.drive.config': {
@@ -104,7 +107,57 @@ QUnit.module('gdrive_integration', {
// Trigger opening of the dynamic link
$googleAction.click();
form.destroy();
});
QUnit.test('click on the google drive attachments after switching records', function (assert) {
assert.expect(3);
var self = this;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="display_name"/>' +
'</form>',
res_id: 1,
viewOptions: {
hasSidebar: true,
ids: [1, 2],
index: 0,
},
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/google.drive.config/get_google_drive_config') {
assert.deepEqual(args.args, ['partner', 1],
'The route to get google drive config should have been called');
return $.when([{id: 27, name: 'Cyberdyne Systems'}]);
}
if (route === '/web/dataset/call_kw/google.drive.config/search_read'){
return $.when([{google_drive_resource_id: "T1000",
google_drive_client_id: "cyberdyne.org",
id: 1}]);
}
if (route === '/web/dataset/call_kw/google.drive.config/get_google_drive_url') {
assert.deepEqual(args.args, [27, self.activeId, 'T1000'],
'The route to get the Google url should have been called');
// We don't return anything useful, otherwise it will open a new tab
return $.when();
}
return this._super.apply(this, arguments);
},
});
var nameToId = {
'Locomotive Breath': 1,
'Hey Macarena': 2,
}
// Trigger opening of the dynamic link
self.activeId = nameToId[$("[name='display_name']").text()];
form.sidebar.$('.oe_share_gdoc').find('a:first').click();
form.pager.$('.o_pager_next').click();
self.activeId = nameToId[$("[name='display_name']").text()];
form.sidebar.$('.oe_share_gdoc').find('a:first').click();
form.destroy();
});
});
+1 -1
View File
@@ -387,7 +387,7 @@ class HolidaysRequest(models.Model):
for holiday in self:
domain = [
('date_from', '<=', holiday.date_to),
('date_to', '>=', holiday.date_from),
('date_to', '>', holiday.date_from),
('employee_id', '=', holiday.employee_id.id),
('id', '!=', holiday.id),
('state', 'not in', ['cancel', 'refuse']),
+2 -1
View File
@@ -151,7 +151,8 @@ class IapAccount(models.Model):
def get(self, service_name):
account = self.search([('service_name', '=', service_name), ('company_id', 'in', [self.env.user.company_id.id, False])])
if not account:
account = self.create({'service_name': service_name})
# TODO: remove this sudo in v12 as we change the user rights.
account = self.sudo().create({'service_name': service_name}).with_env(self.env)
# Since the account did not exist yet, we will encounter a NoCreditError,
# which is going to rollback the database and undo the account creation,
# preventing the process to continue any further.
@@ -23,77 +23,77 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<field name="spoken_languages" eval="'en_US'"/>
</record>
<record id="l10n_cn_1012" model="account.account.template">
<field name="name">其他貨幣資金</field>
<field name="name">其他货币资金</field>
<field name="code">1012</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1101" model="account.account.template">
<field name="name">交易性金融資産</field>
<field name="name">交易性金融资产</field>
<field name="code">1101</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1121" model="account.account.template">
<field name="name">應收票據</field>
<field name="name">应收票据</field>
<field name="code">1121</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_receivable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1122" model="account.account.template">
<field name="name">應付賬款</field>
<field name="name">应付账款</field>
<field name="code">1122</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_receivable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1123" model="account.account.template">
<field name="name">預付賬款</field>
<field name="name">预付账款</field>
<field name="code">1123</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_prepayments"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1131" model="account.account.template">
<field name="name">應收股利</field>
<field name="name">应收股利</field>
<field name="code">1131</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_receivable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1132" model="account.account.template">
<field name="name">應收利息</field>
<field name="name">应收利息</field>
<field name="code">1132</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_receivable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1221" model="account.account.template">
<field name="name">其他應收款</field>
<field name="name">其他应收款</field>
<field name="code">1221</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_receivable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1231" model="account.account.template">
<field name="name">壞賬準備</field>
<field name="name">坏账准备</field>
<field name="code">1231</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1401" model="account.account.template">
<field name="name">材料采購</field>
<field name="name">材料采购</field>
<field name="code">1401</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1402" model="account.account.template">
<field name="name">在途物資</field>
<field name="name">在途物资</field>
<field name="code">1402</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
@@ -114,105 +114,105 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1405" model="account.account.template">
<field name="name">庫存商品</field>
<field name="name">库存商品</field>
<field name="code">1405</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1406" model="account.account.template">
<field name="name">發出商品</field>
<field name="name">发出商品</field>
<field name="code">1406</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1471" model="account.account.template">
<field name="name">存貨跌價準備</field>
<field name="name">存货跌价准备</field>
<field name="code">1471</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1407" model="account.account.template">
<field name="name">商品進銷差價</field>
<field name="name">商品进销差价</field>
<field name="code">1407</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1408" model="account.account.template">
<field name="name">委托加工物資</field>
<field name="name">委托加工物资</field>
<field name="code">1408</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1501" model="account.account.template">
<field name="name">持有至到期投資</field>
<field name="name">持有至到期投资</field>
<field name="code">1501</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_non_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1502" model="account.account.template">
<field name="name">持有至到期投資减值準備</field>
<field name="name">持有至到期投资减值准备</field>
<field name="code">1502</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_non_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1503" model="account.account.template">
<field name="name">可供出售金融資産</field>
<field name="name">可供出售金融资产</field>
<field name="code">1503</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_non_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1511" model="account.account.template">
<field name="name">長期股權投資</field>
<field name="name">长期股权投资</field>
<field name="code">1511</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_non_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1512" model="account.account.template">
<field name="name">長期股權投資减值準備</field>
<field name="name">长期股权投资减值准备</field>
<field name="code">1512</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_non_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1521" model="account.account.template">
<field name="name">投資性房地産</field>
<field name="name">投资性房地产</field>
<field name="code">1521</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_non_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1531" model="account.account.template">
<field name="name">長期應收款</field>
<field name="name">长期应收款</field>
<field name="code">1531</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_non_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1601" model="account.account.template">
<field name="name">固定資産</field>
<field name="name">固定资产</field>
<field name="code">1601</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_fixed_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1602" model="account.account.template">
<field name="name">累計折舊</field>
<field name="name">累计折旧</field>
<field name="code">1602</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_depreciation"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1603" model="account.account.template">
<field name="name">固定資産减值準備</field>
<field name="name">固定资产减值准备</field>
<field name="code">1603</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_depreciation"/>
@@ -226,49 +226,49 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1605" model="account.account.template">
<field name="name">工程物資</field>
<field name="name">工程物资</field>
<field name="code">1605</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_non_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1606" model="account.account.template">
<field name="name">固定資産情况</field>
<field name="name">固定资产情况</field>
<field name="code">1606</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_depreciation"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1701" model="account.account.template">
<field name="name">無形資産</field>
<field name="name">无形资产</field>
<field name="code">1701</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_non_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1702" model="account.account.template">
<field name="name">累計攤銷</field>
<field name="name">累计摊销</field>
<field name="code">1702</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_depreciation"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1703" model="account.account.template">
<field name="name">無形資産减值準備</field>
<field name="name">无形资产减值准备</field>
<field name="code">1703</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_depreciation"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1711" model="account.account.template">
<field name="name">商譽</field>
<field name="name">商誉</field>
<field name="code">1711</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_non_current_assets"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_1801" model="account.account.template">
<field name="name">長期待攤銷費用</field>
<field name="name">长期待摊销费用</field>
<field name="code">1801</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_depreciation"/>
@@ -282,266 +282,266 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2201" model="account.account.template">
<field name="name">應付票據</field>
<field name="name">应付票据</field>
<field name="code">2201</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2202" model="account.account.template">
<field name="name">應付賬款</field>
<field name="name">应付账款</field>
<field name="code">2202</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2203" model="account.account.template">
<field name="name">預收賬款</field>
<field name="name">预收账款</field>
<field name="code">2203</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2211" model="account.account.template">
<field name="name">應付職工薪酬</field>
<field name="name">应付职工薪酬</field>
<field name="code">2211</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2221" model="account.account.template">
<field name="name">應交税費</field>
<field name="name">应交税费</field>
<field name="code">2221</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2231" model="account.account.template">
<field name="name">應付利息</field>
<field name="name">应付利息</field>
<field name="code">2231</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2241" model="account.account.template">
<field name="name">應付股利</field>
<field name="name">应付股利</field>
<field name="code">2241</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2501" model="account.account.template">
<field name="name">其他應付款</field>
<field name="name">其他应付款</field>
<field name="code">2501</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2502" model="account.account.template">
<field name="name">應付債券</field>
<field name="name">应付债券</field>
<field name="code">2502</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2701" model="account.account.template">
<field name="name">長期應付款</field>
<field name="name">长期应付款</field>
<field name="code">2701</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2711" model="account.account.template">
<field name="name">專項應付款</field>
<field name="name">专项应付款</field>
<field name="code">2711</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2801" model="account.account.template">
<field name="name">預計負債</field>
<field name="name">预计负债</field>
<field name="code">2801</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_2901" model="account.account.template">
<field name="name">遞延所得税負債</field>
<field name="name">递延所得税负债</field>
<field name="code">2901</field>
<field name="reconcile" eval="True"/>
<field name="user_type_id" ref="account.data_account_type_payable"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_4001" model="account.account.template">
<field name="name">實收資本</field>
<field name="name">实收资本</field>
<field name="code">4001</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_equity"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_4002" model="account.account.template">
<field name="name">資本公積金</field>
<field name="name">资本公积金</field>
<field name="code">4002</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_equity"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_4003" model="account.account.template">
<field name="name">其他綜合收益</field>
<field name="name">其他综合收益</field>
<field name="code">4003</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_equity"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_4101" model="account.account.template">
<field name="name">盈餘公積</field>
<field name="name">盈余公积</field>
<field name="code">4101</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_equity"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_4103" model="account.account.template">
<field name="name">本年利潤</field>
<field name="name">本年利润</field>
<field name="code">4103</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_equity"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_4104" model="account.account.template">
<field name="name">利潤分配</field>
<field name="name">利润分配</field>
<field name="code">4104</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_equity"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_5001" model="account.account.template">
<field name="name">生産成本</field>
<field name="name">生产成本</field>
<field name="code">5001</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_direct_costs"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_5101" model="account.account.template">
<field name="name">制造費用</field>
<field name="name">制造费用</field>
<field name="code">5101</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_direct_costs"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_5201" model="account.account.template">
<field name="name">勞務成本</field>
<field name="name">劳务成本</field>
<field name="code">5201</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_direct_costs"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_5301" model="account.account.template">
<field name="name">研發支出</field>
<field name="name">研发支出</field>
<field name="code">5301</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_direct_costs"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6001" model="account.account.template">
<field name="name">主營業務收入</field>
<field name="name">主营业务收入</field>
<field name="code">6001</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_revenue"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6051" model="account.account.template">
<field name="name">其他業務收入</field>
<field name="name">其他业务收入</field>
<field name="code">6051</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_other_income"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6101" model="account.account.template">
<field name="name">公允價值變動損益</field>
<field name="name">公允价值变动损益</field>
<field name="code">6101</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_other_income"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6111" model="account.account.template">
<field name="name">投資收益</field>
<field name="name">投资收益</field>
<field name="code">6111</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_other_income"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6301" model="account.account.template">
<field name="name">營業外收入</field>
<field name="name">营业外收入</field>
<field name="code">6301</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_other_income"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6401" model="account.account.template">
<field name="name">主營業務成本</field>
<field name="name">主营业务成本</field>
<field name="code">6401</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6402" model="account.account.template">
<field name="name">其他業務成本</field>
<field name="name">其他业务成本</field>
<field name="code">6402</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6403" model="account.account.template">
<field name="name">營業税及附加</field>
<field name="name">营业税及附加</field>
<field name="code">6403</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6601" model="account.account.template">
<field name="name">銷售費用</field>
<field name="name">销售费用</field>
<field name="code">6601</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6602" model="account.account.template">
<field name="name">管理費用</field>
<field name="name">管理费用</field>
<field name="code">6602</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6603" model="account.account.template">
<field name="name">財務費用</field>
<field name="name">财务费用</field>
<field name="code">6603</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6701" model="account.account.template">
<field name="name">資産减值損失</field>
<field name="name">资产减值损失</field>
<field name="code">6701</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6711" model="account.account.template">
<field name="name">營業外支出</field>
<field name="name">营业外支出</field>
<field name="code">6711</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6801" model="account.account.template">
<field name="name">所得税費用</field>
<field name="name">所得税费用</field>
<field name="code">6801</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
<field name="chart_template_id" ref="l10n_chart_china_small_business"/>
</record>
<record id="l10n_cn_6901" model="account.account.template">
<field name="name">以前年度損益調整</field>
<field name="name">以前年度损益调整</field>
<field name="code">6901</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
@@ -560,3 +560,5 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
</data>
</odoo>
@@ -1,5 +1,5 @@
id,code,name,reconcile,user_type_id:id,chart_template_id:id
co_puc_110505,110505,Caja General,TRUE,account.data_account_type_liquidity,l10n_co_chart_template_generic
co_puc_110505,110505,Caja General,FALSE,account.data_account_type_liquidity,l10n_co_chart_template_generic
co_puc_120535,120535,Comercio al Por Mayor y al Por Menor,FALSE,account.data_account_type_current_assets,l10n_co_chart_template_generic
co_puc_129595,129595,Diversas,FALSE,account.data_account_type_current_assets,l10n_co_chart_template_generic
co_puc_130505,130505,Nacionales,TRUE,account.data_account_type_receivable,l10n_co_chart_template_generic
1 id code name reconcile user_type_id:id chart_template_id:id
2 co_puc_110505 110505 Caja General TRUE FALSE account.data_account_type_liquidity l10n_co_chart_template_generic
3 co_puc_120535 120535 Comercio al Por Mayor y al Por Menor FALSE account.data_account_type_current_assets l10n_co_chart_template_generic
4 co_puc_129595 129595 Diversas FALSE account.data_account_type_current_assets l10n_co_chart_template_generic
5 co_puc_130505 130505 Nacionales TRUE account.data_account_type_receivable l10n_co_chart_template_generic
@@ -8,7 +8,7 @@
<field name="code">0-111301</field>
<field name="name">0-Cuenta PayPal 1</field>
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
<field name="reconcile" eval="True"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="account_chart_template_0"/>
</record>
<record id="account_account_template_0_111401" model="account.account.template">
+1 -1
View File
@@ -2687,7 +2687,7 @@
<field name="parent_id" ref="l10n_es.account_group_599"/>
</record>
<record id="account_group_6" model="account.group">
<field name="code_prefix">2</field>
<field name="code_prefix">6</field>
<field name="name">Compras y gastos</field>
</record>
<record id="account_group_60" model="account.group">
+17 -7
View File
@@ -178,15 +178,14 @@ class Message(models.Model):
@api.model
def _search_need_moderation(self, operator, operand):
if operator == '=' and operand:
if operator == '=' and operand is True:
return ['&', '&',
('moderation_status', '=', 'pending_moderation'),
('model', '=', 'mail.channel'),
('res_id', 'in', self.env.user.moderation_channel_ids.ids)]
return ['|', '|',
('moderation_status', '!=', 'pending_moderation'),
('model', '!=', 'mail.channel'),
('res_id', 'not in', self.env.user.moderation_channel_ids.ids)]
# no support for other operators
return ValueError(_('Unsupported search filter on moderation status'))
#------------------------------------------------------
# Notification API
@@ -419,8 +418,19 @@ class Message(models.Model):
return messages._format_mail_failures()
@api.model
def message_fetch(self, domain, limit=20):
return self.search(domain, limit=limit).message_format()
def message_fetch(self, domain, limit=20, moderated_channel_ids=None):
messages = self.search(domain, limit=limit)
user_mod_channels = self.env.user.moderation_channel_ids.ids
if moderated_channel_ids and set(moderated_channel_ids).issubset(user_mod_channels):
# Split load moderated and regular messages, as the ORed domain can
# cause performance issues on large databases.
moderated_messages_dom = [['model', '=', 'mail.channel'],
['res_id', 'in', moderated_channel_ids],
['need_moderation', '=', True]]
messages |= self.search(moderated_messages_dom, limit=limit)
# Truncate the results to `limit`
messages = messages.sorted(key='id', reverse=True)[:limit]
return messages.message_format()
@api.multi
def message_format(self):
+1
View File
@@ -1323,6 +1323,7 @@ class MailThread(models.AbstractModel):
# if a new thread is created, parent is irrelevant
message_dict.pop('parent_id', None)
thread = MessageModel.message_new(message_dict, custom_values)
thread_id = thread.id
else:
if thread_id:
raise ValueError("Posting a message without model should be with a null res_id, to create a private message.")
+12 -1
View File
@@ -204,7 +204,9 @@ var Followers = AbstractField.extend({
return $.when(def).then(function (results) {
if (results) {
self.followers = _.uniq(results.followers.concat(self.followers), 'id');
self.subtypes = results.subtypes;
if (results.subtypes) { //read_followers will return False if current user is not in the list
self.subtypes = results.subtypes;
}
}
// filter out previously fetched followers that are no longer following
self.followers = _.filter(self.followers, function (follower) {
@@ -291,6 +293,15 @@ var Followers = AbstractField.extend({
});
} else {
var kwargs = _.extend({}, ids);
if (follower_id === undefined || follower_id === this.partnerID) {
//this.subtypes will only be updated if the current user
//just added himself to the followers. We need to update
//the subtypes manually when editing subtypes
//for current user
_.each(this.subtypes, function (subtype) {
subtype.followed = checklist.indexOf(subtype.id) > -1;
});
}
kwargs.subtype_ids = checklist;
kwargs.context = {}; // FIXME
this._rpc({
@@ -165,6 +165,7 @@ var SearchableThread = Thread.extend({
var self = this;
var domain = this._getThreadDomain();
var moderatedChannelIds = typeof(this._id) === 'number' ? [this._id] : false;
var cache = this._getCache(pDomain);
if (pDomain) {
@@ -179,6 +180,7 @@ var SearchableThread = Thread.extend({
method: 'message_fetch',
args: [domain],
kwargs: {
moderated_channel_ids: moderatedChannelIds,
limit: this._FETCH_LIMIT,
context: session.user_context
},
+1 -5
View File
@@ -1720,11 +1720,6 @@ QUnit.test('followers widget: follow/unfollow, edit subtypes', function (assert)
_.each(subtypes, function (subtype) {
subtype.followed = _.contains(args.kwargs.subtype_ids, subtype.id);
});
// hack: the server creates a new follower each time the subtypes are updated
// so we need here to mock that weird behavior here, as the followers widget
// relies on that behavior
this.data.partner.records[0].message_follower_ids = [2];
followers[0].id = 2;
} else {
// follow
this.data.partner.records[0].message_follower_ids = [1];
@@ -1743,6 +1738,7 @@ QUnit.test('followers widget: follow/unfollow, edit subtypes', function (assert)
return $.when({
followers: followers,
subtypes: subtypes,
// caution, subtype will only be returned if current user is in args follower list
});
}
if (route === '/web/dataset/call_kw/partner/message_unsubscribe') {
@@ -75,7 +75,17 @@ MockServer.include({
_mockMessageFetch: function (args) {
var domain = args.args[0];
var model = args.model;
var mod_channel_ids = args.kwargs.moderated_channel_ids;
var messages = this._getRecords(model, domain);
if (mod_channel_ids) {
var mod_messages = this._getRecords(
model,
[['model', '=', 'mail.channel'],
['res_id', 'in', mod_channel_ids],
['need_moderation', '=', true]]
);
messages = _.union(messages, mod_messages);
}
// sorted from highest ID to lowest ID (i.e. from youngest to oldest)
messages.sort(function (m1, m2) {
return m1.id < m2.id ? 1 : -1;
+1 -1
View File
@@ -287,7 +287,7 @@ class MassMailingCampaign(models.Model):
mass_mailing_ids = fields.One2many(
'mail.mass_mailing', 'mass_mailing_campaign_id',
string='Mass Mailings')
unique_ab_testing = fields.Boolean(string='Allow A/B Testing', default=True,
unique_ab_testing = fields.Boolean(string='Allow A/B Testing', default=False,
help='If checked, recipients will be mailed only once for the whole campaign. '
'This lets you send different mailings to randomly selected recipients and test '
'the effectiveness of the mailings, without causing duplicate messages.')
@@ -134,7 +134,7 @@ class ReportBomStructure(models.AbstractModel):
components = []
total = 0
for line in bom.bom_line_ids:
line_quantity = (bom_quantity) * line.product_qty
line_quantity = (bom_quantity / (bom.product_qty or 1.0)) * line.product_qty
if line._skip_bom_line(product):
continue
price = line.product_id.uom_id._compute_price(line.product_id.standard_price, line.product_uom_id) * line_quantity
+1
View File
@@ -203,6 +203,7 @@ class WebsitePayment(http.Controller):
values['sale_order_ids'] = [(6, 0, [order_id])]
reference_values = order_id and {'sale_order_ids': [(4, order_id)]} or {}
reference_values.update(acquirer_id=int(acquirer_id))
values['reference'] = request.env['payment.transaction']._compute_reference(values=reference_values, prefix=reference)
tx = request.env['payment.transaction'].sudo().with_context(lang=None).create(values)
tx.return_url = '/website_payment/confirm?tx_id=%d' % tx.id
+1 -1
View File
@@ -743,7 +743,7 @@ class PaymentTransaction(models.Model):
payments = defaultdict(lambda: self.env['account.payment'])
for trans in self:
if trans.payment_id:
payments += trans.payment_id
payments[trans.acquirer_id.company_id.id] += trans.payment_id
continue
payment_vals = trans._prepare_account_payment_vals()
+3 -3
View File
@@ -283,7 +283,7 @@ class PaymentTxOgone(models.Model):
return invalid_parameters
def _ogone_form_validate(self, data):
if self.state != 'draft':
if self.state not in ['draft', 'pending']:
_logger.info('Ogone: trying to validate an already validated tx (ref %s)', self.reference)
return True
@@ -431,7 +431,7 @@ class PaymentTxOgone(models.Model):
return self._ogone_s2s_validate_tree(tree)
def _ogone_s2s_validate_tree(self, tree, tries=2):
if self.state != 'draft':
if self.state not in ['draft', 'pending']:
_logger.info('Ogone: trying to validate an already validated tx (ref %s)', self.reference)
return True
@@ -469,7 +469,7 @@ class PaymentTxOgone(models.Model):
if status == 46: # HTML 3DS
vals['html_3ds'] = ustr(base64.b64decode(tree.HTML_ANSWER.text))
self.write(vals)
self._set_transaction_done()
self._set_transaction_pending()
elif status in self._ogone_wait_tx_status and tries > 0:
time.sleep(0.5)
self.write({'acquirer_reference': tree.get('PAYID')})
+6 -7
View File
@@ -5,6 +5,7 @@
import json
import logging
import re
import time
from hashlib import sha256
from werkzeug import urls
@@ -98,7 +99,7 @@ class AcquirerSips(models.Model):
return_context = {}
if sips_tx_values.get('return_url'):
return_context[u'return_url'] = u'%s' % sips_tx_values.pop('return_url')
return_context[u'return_url'] = u'%s' % urls.url_quote(sips_tx_values.pop('return_url'))
return_context[u'reference'] = u'%s' % sips_tx_values['reference']
sips_tx_values['Data'] += u'|returnContext=%s' % (json.dumps(return_context))
@@ -124,13 +125,11 @@ class TxSips(models.Model):
@api.model
def _compute_reference(self, values=None, prefix=None):
acquirer = self.env['payment.acquirer'].browse(values['acquirer_id'])
res = super(TxSips, self)._compute_reference(values=values, prefix=prefix)
acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id'))
if acquirer and acquirer.provider == 'sips':
if not prefix and values:
prefix = self._compute_reference_prefix(values)
prefix = re.sub(r'[^0-9a-zA-Z-]+', 'x', prefix)
return super(TxSips, self)._compute_reference(values=values, prefix=prefix)
return re.sub(r'[^0-9a-zA-Z]+', 'x', res) + 'x' + str(int(time.time()))
return res
# --------------------------------------------------
# FORM RELATED METHODS
+1 -1
View File
@@ -469,7 +469,7 @@ class PosOrder(models.Model):
def _filtered_for_reconciliation(self):
filter_states = ['invoiced', 'done']
if self.env['ir.config_parameter'].get_param('point_of_sale.order_reconcile_mode', 'all') == 'partner_only':
if self.env['ir.config_parameter'].sudo().get_param('point_of_sale.order_reconcile_mode', 'all') == 'partner_only':
return self.filtered(lambda order: order.state in filter_states and order.partner_id)
return self.filtered(lambda order: order.state in filter_states)
@@ -36,9 +36,13 @@ class TestAngloSaxonCommon(common.TransactionCase):
self.product = self.product.copy({'name': 'New product','standard_price': 100})
self.company.anglo_saxon_accounting = True
self.product.categ_id = self.category
self.product.property_account_expense_id = account_expense
self.product.property_account_income_id = self.account
sale_journal = self.env['account.journal'].create({'name': 'POS journal', 'type': 'sale', 'code': 'POS00'})
self.pos_config.journal_id = sale_journal
self.cash_journal = self.env['account.journal'].create({'name': 'CASH journal', 'type': 'cash', 'code': 'CSH00'})
self.pos_config.invoice_journal_id = False
self.pos_config.journal_ids = [self.cash_journal.id]
class TestAngloSaxonFlow(TestAngloSaxonCommon):
+1 -1
View File
@@ -42,7 +42,7 @@ def _message_post_helper(res_model='', res_id=None, message='', token='', nosubs
access_as_sudo = _has_token_access(res_model, res_id, token=token)
if access_as_sudo:
record = record.sudo()
if request.env.user == request.env.ref('base.public_user'):
if request.env.user._is_public():
if kw.get('pid') and consteq(kw.get('hash'), record._sign_token(int(kw.get('pid')))):
author_id = kw.get('pid')
else:
+5
View File
@@ -126,6 +126,7 @@ class ProductProduct(models.Model):
image_medium = fields.Binary(
"Medium-sized image", compute='_compute_images', inverse='_set_image_medium',
help="Image of the product variant (Medium-sized image of product template if false).")
is_product_variant = fields.Boolean(compute='_compute_is_product_variant')
standard_price = fields.Float(
'Cost', company_dependent=True,
@@ -153,6 +154,10 @@ class ProductProduct(models.Model):
def _get_invoice_policy(self):
return False
def _compute_is_product_variant(self):
for product in self:
product.is_product_variant = True
def _compute_product_price(self):
prices = {}
pricelist_id_or_name = self._context.get('pricelist')
+1 -4
View File
@@ -241,10 +241,7 @@ class ProductTemplate(models.Model):
def _compute_is_product_variant(self):
for template in self:
if template._name == 'product.template':
template.is_product_variant = False
else:
template.is_product_variant = True
template.is_product_variant = False
@api.model
def _get_weight_uom_id_from_ir_config_parameter(self):
+1
View File
@@ -36,6 +36,7 @@ class TestProductCommon(common.SavepointCase):
cls.product_1 = Product.create({
'name': 'Courage',
'type': 'consu',
'default_code': 'PROD-1',
'uom_id': cls.uom_dunit.id,
'uom_po_id': cls.uom_dunit.id})
+8
View File
@@ -60,6 +60,14 @@ class TestVariants(common.TestProductCommon):
self.size_attr_value_l = self.env['product.attribute.value'].create({'name': 'L', 'attribute_id': self.size_attr.id})
return res
def test_variants_is_product_variant(self):
template = self.product_7_template
variants = template.product_variant_ids
self.assertFalse(template.is_product_variant,
'Product template is not a variant')
self.assertEqual({True}, set(v.is_product_variant for v in variants),
'Product variants are variants')
def test_variants_creation_mono(self):
test_template = self.env['product.template'].create({
'name': 'Sofa',
+3 -2
View File
@@ -285,7 +285,9 @@ class Project(models.Model):
def map_tasks(self, new_project_id):
""" copy and map tasks from old to new project """
tasks = self.env['project.task']
for task in self.tasks:
# We want to copy archived task, but do not propagate an active_test context key
task_ids = self.env['project.task'].with_context(active_test=False).search([('project_id', '=', self.id)]).ids
for task in self.env['project.task'].browse(task_ids):
# preserve task name and stage, normally altered during copy
defaults = self._map_tasks_default_valeus(task)
tasks += task.copy(defaults)
@@ -296,7 +298,6 @@ class Project(models.Model):
def copy(self, default=None):
if default is None:
default = {}
self = self.with_context(active_test=False)
if not default.get('name'):
default['name'] = _("%s (copy)") % (self.name)
project = super(Project, self).copy(default)
+1 -1
View File
@@ -716,7 +716,7 @@
<field name="name">Project's tasks</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,graph,kanban</field>
<field name="domain">[('project_id', 'child_of', [active_id])]</field>
<field name="domain">[('project_id', '=', active_id)]</field>
<field name="context">{'project_id':active_id}</field>
</record>
+1
View File
@@ -201,6 +201,7 @@
</tree>
<form string="Purchase Order Line">
<sheet>
<field name="state" invisible="1"/>
<group>
<group>
<field name="product_id"
@@ -118,6 +118,7 @@ class TestCreatePicking(common.TestProductCommon):
'route_ids': [(4, self.ref('stock.route_warehouse0_mto')), (4, self.ref('purchase_stock.route_warehouse0_buy'))],
'seller_ids': [(6, 0, [seller.id])],
'categ_id': self.env.ref('product.product_category_all').id,
'supplier_taxes_id': [(6, 0, [])],
})
customer_move = self.env['stock.move'].create({
+11
View File
@@ -48,6 +48,17 @@ class SaleOrderLine(models.Model):
components[product] = {'qty': qty, 'uom': to_uom.id}
return components
def _get_qty_procurement(self):
self.ensure_one()
# Specific case when we change the qty on a SO for a kit product.
# We don't try to be too smart and keep a simple approach: we compare the quantity before
# and after update, and return the difference. We don't take into account what was already
# sent, or any other exceptional case.
bom = self.env['mrp.bom']._bom_find(product=self.product_id)
if bom and bom.type == 'phantom' and 'previous_product_uom_qty' in self.env.context:
return self.env.context['previous_product_uom_qty'].get(self.id, 0.0)
return super(SaleOrderLine, self)._get_qty_procurement()
class AccountInvoiceLine(models.Model):
# TDE FIXME: what is this code ??
+7 -3
View File
@@ -201,14 +201,15 @@ class SaleOrderLine(models.Model):
@api.multi
def write(self, values):
lines = False
lines = self.env['sale.order.line']
if 'product_uom_qty' in values:
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
lines = self.filtered(
lambda r: r.state == 'sale' and not r.is_expense and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1)
previous_product_uom_qty = {line.id: line.product_uom_qty for line in lines}
res = super(SaleOrderLine, self).write(values)
if lines:
lines._action_launch_stock_rule()
lines.with_context(previous_product_uom_qty=previous_product_uom_qty)._action_launch_stock_rule()
return res
@api.depends('order_id.state')
@@ -257,7 +258,10 @@ class SaleOrderLine(models.Model):
return {}
if self.product_id.type == 'product':
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
product = self.product_id.with_context(warehouse=self.order_id.warehouse_id.id)
product = self.product_id.with_context(
warehouse=self.order_id.warehouse_id.id,
lang=self.order_id.partner_id.lang or self.env.user.lang or 'en_US'
)
product_qty = self.product_uom._compute_quantity(self.product_uom_qty, self.product_id.uom_id)
if float_compare(product.virtual_available, product_qty, precision_digits=precision) == -1:
is_available = self._check_routing()
+5
View File
@@ -14,26 +14,31 @@
<field name="location_id" ref="stock_location_suppliers"/>
<field name="usage">supplier</field>
<field name="barcode">2601985</field>
<field name="company_id"></field>
</record>
<record id="stock_location_4" model="stock.location">
<field name="name">Big Vendors</field>
<field name="location_id" ref="stock_location_3"/>
<field name="usage">supplier</field>
<field name="company_id"></field>
</record>
<record id="stock_location_5" model="stock.location">
<field name="name">Generic IT Vendors</field>
<field name="location_id" ref="stock_location_3"/>
<field name="usage">supplier</field>
<field name="company_id"></field>
</record>
<record id="stock_location_7" model="stock.location">
<field name="name">European Customers</field>
<field name="location_id" ref="stock_location_customers"/>
<field name="usage">customer</field>
<field name="company_id"></field>
</record>
<record id="stock_location_8" model="stock.location">
<field name="name">Non European Customers</field>
<field name="location_id" ref="stock_location_customers"/>
<field name="usage">customer</field>
<field name="company_id"></field>
</record>
+1 -1
View File
@@ -161,7 +161,7 @@ class Product(models.Model):
if self.env.context.get('company_owned', False):
company_id = self.env.user.company_id.id
return (
[('location_id.company_id', '=', company_id)],
[('location_id.company_id', '=', company_id), ('location_id.usage', 'in', ['internal', 'transit'])],
[('location_id.company_id', '=', False), ('location_dest_id.company_id', '=', company_id)],
[('location_id.company_id', '=', company_id), ('location_dest_id.company_id', '=', False),
])
+3 -3
View File
@@ -1020,7 +1020,7 @@ class StockMove(models.Model):
if move.propagate:
# only cancel the next move if all my siblings are also cancelled
if all(state == 'cancel' for state in siblings_states):
move.move_dest_ids._action_cancel()
move.move_dest_ids.filtered(lambda m: m.state != 'done')._action_cancel()
else:
if all(state in ('done', 'cancel') for state in siblings_states):
move.move_dest_ids.write({'procure_method': 'make_to_stock'})
@@ -1030,6 +1030,7 @@ class StockMove(models.Model):
def _prepare_extra_move_vals(self, qty):
vals = {
'origin_returned_move_id': self.origin_returned_move_id.id,
'product_uom_qty': qty,
'picking_id': self.picking_id.id,
'price_unit': self.price_unit,
@@ -1085,8 +1086,7 @@ class StockMove(models.Model):
def _action_done(self):
self.filtered(lambda move: move.state == 'draft')._action_confirm() # MRP allows scrapping draft moves
moves = self.filtered(lambda x: x.state not in ('done', 'cancel'))
moves = self.exists().filtered(lambda x: x.state not in ('done', 'cancel'))
moves_todo = self.env['stock.move']
# Cancel moves where necessary ; we should do it before creating the extra moves because
+1 -1
View File
@@ -418,7 +418,7 @@ class QuantPackage(models.Model):
return action
def _get_contained_quants(self):
return self.env['stock.quant'].search([('package_id', 'child_of', self.ids)])
return self.env['stock.quant'].search([('package_id', 'in', self.ids)])
def _get_all_products_quantities(self):
'''This function computes the different product quantities for the given package
+24
View File
@@ -185,6 +185,30 @@ class TestPickShip(TestStockCommon):
# the client picking should not be assigned anymore, as we returned partially what we took
self.assertEqual(picking_client.state, 'confirmed')
def test_mto_moves_return_extra(self):
picking_pick, picking_client = self.create_pick_ship()
stock_location = self.env['stock.location'].browse(self.stock_location)
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0
picking_pick.action_done()
self.assertEqual(picking_pick.state, 'done')
self.assertEqual(picking_client.state, 'assigned')
# return more than we've done
stock_return_picking = self.env['stock.return.picking']\
.with_context(active_ids=picking_pick.ids, active_id=picking_pick.ids[0])\
.create({})
stock_return_picking.product_return_moves.quantity = 12.0 # Return 2 extra
stock_return_picking_action = stock_return_picking.create_returns()
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
# Verify the extra move has been merged with the original move
self.assertAlmostEqual(return_pick.move_lines.product_uom_qty, 12.0)
self.assertAlmostEqual(return_pick.move_lines.quantity_done, 0.0)
self.assertAlmostEqual(return_pick.move_lines.reserved_availability, 10.0)
def test_mto_resupply_cancel_ship(self):
""" This test simulates a pick pack ship with a resupply route
set. Pick and pack are validated, ship is cancelled. This test
+1 -1
View File
@@ -62,7 +62,7 @@ class TestWarehouse(TestStockCommon):
# Check inventory obj details (1 inventory with 1 line, because 1 product change)
inventory = self.env['stock.inventory'].search([('id', 'not in', self.existing_inventories.ids)])
self.assertEqual(len(inventory), 1)
self.assertIn('INV: %s' % self.product_1.name, inventory.name)
self.assertIn('INV: %s' % self.product_1.display_name, inventory.name)
self.assertEqual(len(inventory.line_ids), 1)
self.assertEqual(inventory.line_ids.product_id, self.product_1)
self.assertEqual(inventory.line_ids.product_qty, 50.0)
+1 -1
View File
@@ -189,7 +189,7 @@
<field name="location_id"/>
</group>
</group>
<separator string="Content" />
<separator string="Content"/>
<field name="quant_ids">
<tree>
<field name="product_id"/>
+5 -4
View File
@@ -384,7 +384,8 @@ class StockMove(models.Model):
qty_done = 0.0
if float_is_zero(product_tot_qty_available, precision_rounding=rounding):
new_std_price = move._get_price_unit()
elif float_is_zero(product_tot_qty_available + move.product_qty, precision_rounding=rounding):
elif float_is_zero(product_tot_qty_available + move.product_qty, precision_rounding=rounding) or \
float_is_zero(product_tot_qty_available + qty_done, precision_rounding=rounding):
new_std_price = move._get_price_unit()
else:
# Get the standard price
@@ -507,9 +508,9 @@ class StockMove(models.Model):
if not accounts_data.get('stock_journal', False):
raise UserError(_('You don\'t have any stock journal defined on your product category, check if you have installed a chart of accounts.'))
if not acc_src:
raise UserError(_('Cannot find a stock input account for the product %s. You must define one on the product category, or on the location, before processing this operation.') % (self.product_id.name))
raise UserError(_('Cannot find a stock input account for the product %s. You must define one on the product category, or on the location, before processing this operation.') % (self.product_id.display_name))
if not acc_dest:
raise UserError(_('Cannot find a stock output account for the product %s. You must define one on the product category, or on the location, before processing this operation.') % (self.product_id.name))
raise UserError(_('Cannot find a stock output account for the product %s. You must define one on the product category, or on the location, before processing this operation.') % (self.product_id.display_name))
if not acc_valuation:
raise UserError(_('You don\'t have any stock valuation account defined on your product category. You must define one before processing this operation.'))
journal_id = accounts_data['stock_journal'].id
@@ -533,7 +534,7 @@ class StockMove(models.Model):
# check that all data is correct
if self.company_id.currency_id.is_zero(debit_value):
raise UserError(_("The cost of %s is currently equal to 0. Change the cost or the configuration of your product to avoid an incorrect valuation.") % (self.product_id.name,))
raise UserError(_("The cost of %s is currently equal to 0. Change the cost or the configuration of your product to avoid an incorrect valuation.") % (self.product_id.display_name,))
credit_value = debit_value
@@ -3138,7 +3138,7 @@ class TestStockValuation(TransactionCase):
self.assertEqual(move1.remaining_value, 10)
self.assertEqual(move1.remaining_qty, 1)
self.assertAlmostEqual(self.product1.qty_available, 0.0)
self.assertAlmostEqual(self.product1.qty_at_date, 2.0)
self.assertAlmostEqual(self.product1.qty_at_date, 1.0)
self.assertEqual(self.product1.stock_value, 10)
self.assertTrue(len(move1.account_move_ids), 1)
+1 -1
View File
@@ -73,8 +73,8 @@
<field name="cost_method" invisible="1"/>
</field>
<field name="standard_price" position="replace">
<label for="standard_price"/>
<div name="standard_price_div">
<label for="standard_price"/>
<field name="standard_price"
attrs="{'readonly':[('valuation','=','real_time')]}"
widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}" class="float-left"/>
@@ -107,9 +107,9 @@ class LandedCost(models.Model):
new_landed_cost_value = line.move_id.landed_cost_value + line.additional_landed_cost
line.move_id.write({
'landed_cost_value': new_landed_cost_value,
'value': line.move_id.value + cost_to_add,
'value': line.move_id.value + line.additional_landed_cost,
'remaining_value': line.move_id.remaining_value + cost_to_add,
'price_unit': (line.move_id.value + cost_to_add) / line.move_id.product_qty,
'price_unit': (line.move_id.value + line.additional_landed_cost) / line.move_id.product_qty,
})
# `remaining_qty` is negative if the move is out and delivered proudcts that were not
# in stock.
@@ -340,7 +340,9 @@ ActionManager.include({
action.controllerID = controller.jsID;
return self._executeAction(action, options).done(function () {
if (lazyLoadedController) {
self.controllerStack.unshift(lazyLoadedController.jsID);
// controller should be placed just before the current one
var index = self.controllerStack.length - 1;
self.controllerStack.splice(index, 0, lazyLoadedController.jsID);
self.controlPanel.update({
breadcrumbs: self._getBreadcrumbs(),
}, {clear: false});
@@ -144,6 +144,13 @@ odoo.define('web.KeyboardNavigationMixin', function (require) {
});
this._addAccessKeyOverlays();
}
// on mac, there are a number of keys that are only accessible though the usage of
// the ALT key (like the @ sign in most keyboards)
// for them we do not facilitate the access keys, so they will need to be activated classically
// though Control + Alt + key (case sensitive), see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey
if (this.BrowserDetection.isOsMac())
return;
if (keyDownEvent.altKey && !keyDownEvent.ctrlKey && keyDownEvent.key.length === 1) { // we don't want to catch the Alt key down, only the characters A to Z and number keys
var elementWithAccessKey = [];
if (keyDownEvent.keyCode >= 65 && keyDownEvent.keyCode <= 90 || keyDownEvent.keyCode >= 97 && keyDownEvent.keyCode <= 122) {
@@ -504,7 +504,7 @@ DebugManager.include({
model: 'ir.default',
method: 'set',
args: [
self._controller.fields_view.model,
self._action.res_model,
fieldToSet,
value,
selfUser,
@@ -811,6 +811,43 @@ QUnit.module('ActionManager', {
actionManager.destroy();
});
QUnit.test('lazy load multi record view with previous action', function (assert) {
assert.expect(6);
var actionManager = createActionManager({
actions: this.actions,
archs: this.archs,
data: this.data,
debug: 1,
});
actionManager.doAction(4);
assert.strictEqual($('.o_control_panel .breadcrumb li').length, 1,
"there should be one controller in the breadcrumbs");
assert.strictEqual($('.o_control_panel .breadcrumb li').text(), 'Partners Action 4',
"breadcrumbs should contain the display_name of the opened record");
actionManager.doAction(3, {
resID: 2,
viewType: 'form',
});
assert.strictEqual($('.o_control_panel .breadcrumb li').length, 3,
"there should be three controllers in the breadcrumbs");
assert.strictEqual($('.o_control_panel .breadcrumb li').text(), 'Partners Action 4PartnersSecond record',
"the breadcrumb elements should be correctly ordered");
// go back to List
$('.o_control_panel .breadcrumb a:last').click();
assert.strictEqual($('.o_control_panel .breadcrumb li').length, 2,
"there should be two controllers in the breadcrumbs");
assert.strictEqual($('.o_control_panel .breadcrumb li').text(), 'Partners Action 4Partners',
"the breadcrumb elements should be correctly ordered");
actionManager.destroy();
});
QUnit.test('change the viewType of the current action', function (assert) {
assert.expect(13);
@@ -125,6 +125,7 @@ var createActionManager = function (params) {
*/
var createDebugManager = function (params) {
params = params || {};
var mockRPC = params.mockRPC;
_.extend(params, {
mockRPC: function (route, args) {
if (args.method === 'check_access_rights') {
@@ -133,6 +134,9 @@ var createDebugManager = function (params) {
if (args.method === 'xmlid_to_res_id') {
return $.when(true);
}
if (mockRPC) {
return mockRPC.apply(this, arguments);
}
return this._super.apply(this, arguments);
},
session: {
@@ -84,5 +84,84 @@ QUnit.module('DebugManager', {}, function () {
debugManager.destroy();
});
QUnit.test("Debug: Set defaults with right model", function (assert) {
assert.expect(2);
/* Click on debug > set default,
* set some defaults, click on save
* model and some other data should be sent to server
*/
// We'll need a full blown architecture with some data
var data = {
partner: {
fields: {
foo: {string: "Foo", type: "char", default: "My little Foo Value"},
},
records: [{
id: 1,
foo: "yop",
}]
},
'ir.default': { // We just need this to be defined
fields: {},
},
}
var form = testUtils.createView({
View: FormView,
model: 'partner',
data: data,
arch: '<form string="Partners">' +
'<field name="foo" />' +
'</form>',
res_id: 1,
});
// Now the real tested component
var debugManager = createDebugManager({
data: data,
mockRPC: function (route, args) {
if (route == "/web/dataset/call_kw/ir.default/set") {
assert.deepEqual(args.args, ["partner", "foo", "yop", true, true, false],
'Model, field, value and booleans for current user/company should have been passed');
return $.when();
}
return this._super.apply(this, arguments);
}
});
debugManager.appendTo($('#qunit-fixture'));
// Simulate update debug manager from web client
var action = {
views: [{
fieldsView: {
view_id: 1,
model: 'partner',
type: 'form',
},
type: "form",
}],
res_model: 'partner',
};
// We are all set
debugManager.update('action', action, form);
// click on set_defaults dropdown
debugManager.$('a[data-action="set_defaults"]').click();
var $modal = $('.modal-content');
assert.strictEqual($modal.length, 1, 'One modal present');
$modal.find('select[id=formview_default_fields] option[value=foo]').prop('selected', true);
// Save
$modal.find('.modal-footer button').eq(1).click();
form.destroy();
debugManager.destroy();
});
});
});
+288
View File
@@ -304,5 +304,293 @@
</t>
</field>
</record>
<!-- BS Debug Page -->
<!-- Showcase all (most?) BS components and utilities -->
<record id="bs_debug_page" model="website.page">
<field name="name">BS Debug</field>
<field name="url">/website/demo/bootstrap</field>
<field name="website_published">False</field>
<field name="type">qweb</field>
<field name="key">website.bs_debug_page_view</field>
<field name="arch" type="xml">
<t name="Debug" t-name="website.bs_debug_page_view">
<t t-call="website.layout">
<t t-set="odoo_theme_colors" t-value="[['alpha', 'Alpha'], ['beta', 'Beta'], ['gamma', 'Gamma'], ['delta', 'Delta'], ['epsilon', 'Epsilon']]"/>
<t t-set="bs_basic_colors" t-value="[['primary', 'Primary'], ['default', 'Default']]"/>
<t t-set="bs_state_colors" t-value="[['success', 'Success'], ['info', 'Info'], ['warning', 'Warning'], ['danger', 'Danger']]"/>
<t t-set="bs_theme_colors" t-value="bs_basic_colors + bs_state_colors"/>
<t t-set="bs_gray_colors" t-value="[['white', 'White'], ['gray-lighter', 'Gray Lighter'], ['gray-light', 'Gray Light'], ['gray', 'Gray'], ['gray-dark', 'Gray Dark'], ['gray-darker', 'Gray Darker'], ['black', 'Black']]"/>
<div id="wrap" class="oe_structure">
<section class="py-2">
<div class="container">
<h1>Components</h1>
<div class="row">
<div class="col-md-9">
<h2>Badge</h2>
<span class="badge mb4">This is a badge</span>
<h2 class="mt8">Label</h2>
<t t-foreach="bs_theme_colors" t-as="color">
<span t-attf-class="label mb4 label-#{color[0]}"><t t-esc="color[1]"/></span>
</t>
<h3 class="mt8 h5">Link</h3>
<t t-foreach="bs_theme_colors" t-as="color">
<a href="#" t-attf-class="label mb4 label-#{color[0]}"><t t-esc="color[1]"/></a>
</t>
<h3 class="mt8 h5">Autosizing</h3>
<div class="mt0 h3">
<t t-foreach="bs_theme_colors" t-as="color">
<span t-attf-class="label mb4 label-#{color[0]}"><t t-esc="color[1]"/></span>
</t>
</div>
<h2 class="mt16">Button</h2>
<t t-foreach="odoo_theme_colors" t-as="color">
<button type="button" t-attf-class="btn mb4 btn-#{color[0]}"><t t-esc="color[1]"/></button>
</t>
<br/>
<t t-foreach="bs_theme_colors" t-as="color">
<button type="button" t-attf-class="btn mb4 btn-#{color[0]}"><t t-esc="color[1]"/></button>
</t>
<h3 class="mt8 h6">Extra Small</h3>
<t t-foreach="odoo_theme_colors" t-as="color">
<button type="button" t-attf-class="btn mb4 btn-xs btn-#{color[0]}"><t t-esc="color[1]"/></button>
</t>
<br/>
<t t-foreach="bs_theme_colors" t-as="color">
<button type="button" t-attf-class="btn mb4 btn-xs btn-#{color[0]}"><t t-esc="color[1]"/></button>
</t>
<h3 class="mt8 h5">Small</h3>
<t t-foreach="odoo_theme_colors" t-as="color">
<button type="button" t-attf-class="btn mb4 btn-sm btn-#{color[0]}"><t t-esc="color[1]"/></button>
</t>
<br/>
<t t-foreach="bs_theme_colors" t-as="color">
<button type="button" t-attf-class="btn mb4 btn-sm btn-#{color[0]}"><t t-esc="color[1]"/></button>
</t>
<h3 class="mt8 h5">Large</h3>
<t t-foreach="odoo_theme_colors" t-as="color">
<button type="button" t-attf-class="btn mb4 btn-lg btn-#{color[0]}"><t t-esc="color[1]"/></button>
</t>
<br/>
<t t-foreach="bs_theme_colors" t-as="color">
<button type="button" t-attf-class="btn mb4 btn-lg btn-#{color[0]}"><t t-esc="color[1]"/></button>
</t>
<h2 class="mt16">Dropdown</h2>
<div class="dropdown">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">Toggle <span class="caret"/></button>
<ul class="dropdown-menu">
<li class="dropdown-header">Header</li>
<li><a href="#">Action</a></li>
<li><a href="#">Something else here</a></li>
<li class="divider"/>
<li><a href="#">Separated link</a></li>
</ul>
</div>
<h2 class="mt16">Navbar</h2>
<nav class="navbar navbar-default">
<div class="navbar-header">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="nav navbar-nav">
<li class="active">
<a href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li>
<a href="#">Link</a>
</li>
<li>
<a class="disabled" href="#">Disabled</a>
</li>
</ul>
<form class="navbar-form navbar-right">
<input class="form-control" type="search" placeholder="Search" aria-label="Search"/>
<button class="btn btn-success" type="submit">Search</button>
</form>
</div>
</nav>
<h2 class="mt16">Form</h2>
<form>
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email"/>
<small id="emailHelp" class="help-block text-muted">We'll never share your email with anyone else.</small>
</div>
</form>
<h2 class="mt16">Pagination</h2>
<nav>
<ul class="pagination">
<li class="disabled">
<a href="#" tabindex="-1">Previous</a>
</li>
<li>
<a href="#">1</a>
</li>
<li class="active">
<a href="#">2 <span class="sr-only">(current)</span></a>
</li>
<li>
<a href="#">3</a>
</li>
<li>
<a href="#">Next</a>
</li>
</ul>
</nav>
</div>
<div class="col-md-3">
<h2>Alert</h2>
<t t-foreach="bs_state_colors" t-as="color">
<div t-attf-class="alert alert-#{color[0]}">
This is a "<t t-esc="color[1]"/>" alert with a <a href="#" class="alert-link">link</a>.
</div>
</t>
<h2 class="mt16">Breadcrumb</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li><a href="#">Home</a></li>
<li><a href="#">Library</a></li>
<li class="active" aria-current="page">Data</li>
</ol>
</nav>
<h2 class="mt16">Card</h2>
<div class="panel panel-default">
<div class="panel-heading">
Card Header
</div>
<div class="panel-body">
Card Body
</div>
<ul class="list-group">
<li class="list-group-item">Item 1</li>
<li class="list-group-item">Item 2</li>
</ul>
<div class="panel-footer">
Card Footer
</div>
</div>
</div>
</div>
</div>
</section>
<section class="py-2">
<div class="container">
<h1>Utilities &amp; Typography</h1>
<div class="row">
<div class="col-md-9">
<div>
<t t-foreach="odoo_theme_colors" t-as="color">
<div t-attf-class="bg-#{color[0]}" style="display: inline-block; padding: 4px 12px;">
<t t-esc="color[1]"/>
</div>
</t>
</div>
<div class="mt8">
<t t-foreach="bs_theme_colors" t-as="color">
<div t-attf-class="bg-#{color[0]}" style="display: inline-block; padding: 4px 12px;">
<t t-esc="color[1]"/>
</div>
</t>
</div>
<div class="mt8">
<t t-foreach="bs_gray_colors" t-as="color">
<div t-attf-class="bg-#{color[0]}" style="display: inline-block; padding: 4px 12px;">
<t t-esc="color[1]"/>
</div>
</t>
</div>
<div class="mt16">
<t t-foreach="odoo_theme_colors" t-as="color">
<div t-attf-class="text-#{color[0]}" style="display: inline-block; padding: 4px 12px;">
<t t-esc="color[1]"/>
</div>
</t>
</div>
<div class="mt8">
<t t-foreach="bs_theme_colors" t-as="color">
<div t-attf-class="text-#{color[0]}" style="display: inline-block; padding: 4px 12px;">
<t t-esc="color[1]"/>
</div>
</t>
</div>
<div class="mt8">
<t t-foreach="bs_gray_colors" t-as="color">
<div t-attf-class="text-#{color[0]}" style="display: inline-block; padding: 4px 12px;">
<t t-esc="color[1]"/>
</div>
</t>
</div>
</div>
<div class="col-md-3">
<h1>Headings 1</h1>
<h2>Headings 2</h2>
<h3>Headings 3</h3>
<h4>Headings 4</h4>
<h5>Headings 5</h5>
<h6>Headings 6</h6>
<p>Paragraph with <strong>bold</strong>, <span class="text-muted">muted</span> and <em>italic</em> texts</p>
<p><a href="#">Link</a></p>
<p><button type="button" class="btn btn-link">Link button</button></p>
</div>
</div>
</div>
</section>
</div>
</t>
</t>
</field>
</record>
<!-- Snippet Debug Page -->
<!-- Showcase all snippets -->
<record id="snippets_debug_page" model="website.page">
<field name="name">Snippet Debug</field>
<field name="url">/website/demo/snippets</field>
<field name="website_published">False</field>
<field name="type">qweb</field>
<field name="key">website.snippets_debug_page_view</field>
<field name="arch" type="xml">
<t name="Debug" t-name="website.snippets_debug_page_view">
<t t-call="website.layout">
<style>
#snippets_menu, #o_scroll > .o_panel > .o_panel_header {
display: none !important;
}
[data-oe-type="snippet"]:not([data-module-id])::before {
content: attr(name);
display: block;
padding: 16px;
background-color: lightgray;
color: black;
font-size: 24px;
}
[data-oe-type="snippet"]:not([data-module-id])::after {
content: "";
display: table;
clear: both;
}
</style>
<div id="wrap" class="oe_structure">
<t t-call="website.snippets"/>
</div>
</t>
</t>
</field>
</record>
</data>
</odoo>
@@ -699,6 +699,7 @@ registry.gallery = Animation.extend({
* @param {Event} ev
*/
_onClickImg: function (ev) {
var self = this;
var $cur = $(ev.currentTarget);
var urls = [];
@@ -743,7 +744,12 @@ registry.gallery = Animation.extend({
$modal.find('.modal-content, .modal-body.o_slideshow').css('height', '100%');
$modal.appendTo(document.body);
this.carousel = new registry.gallery_slider($modal.find('.carousel').carousel());
$modal.one('shown.bs.modal', function () {
self.trigger_up('animation_start_demand', {
editableMode: false,
$target: $modal.find('.modal-body.o_slideshow'),
});
});
},
});
+6 -8
View File
@@ -788,28 +788,26 @@ table.table_desc tr td {
border-width: 0;
position: absolute;
bottom: 0;
width: 100%;
margin-left: 0;
left: 0%;
> * {
list-style-image: none;
display: inline-block;
width: 40px;
height: 40px;
margin: 0 0px 5px 5px;
margin: 2.5px 2.5px 2.5px 2.5px;
padding: 0;
border: 1px solid #aaa;
text-indent: initial;
background-size: cover;
background-color: #fff;
border-radius: 0;
vertical-align: bottom;
flex: 0 0 40px;
&:not(.active) {
opacity: 0.8;
-webkit-filter: grayscale(100%);
filter: grayscale(100%);
filter: gray;
filter: url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' height='0'><filter id='greyscale'><feColorMatrix type='matrix' values='0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0' /></filter></svg>#greyscale");
}
&:first-child {
margin-left: 0;
filter: grayscale(1);
}
}
}
+1
View File
@@ -53,6 +53,7 @@ class Forum(models.Model):
'build your professional profile and become a better marketer together.'))
welcome_message = fields.Html(
'Welcome Message',
translate=True,
default = """<section class="bg-info" style="height: 168px;"><div class="container">
<div class="row">
<div class="col-lg-12">
+7 -2
View File
@@ -17,8 +17,9 @@ class SaleOrder(models.Model):
_inherit = "sale.order"
website_order_line = fields.One2many(
'sale.order.line', 'order_id',
string='Order Lines displayed on Website', readonly=True,
'sale.order.line',
compute='_compute_website_order_line',
string='Order Lines displayed on Website',
help='Order Lines to be displayed on the website. They should not be used for computation purpose.',
)
cart_quantity = fields.Integer(compute='_compute_cart_info', string='Cart Quantity')
@@ -29,6 +30,10 @@ class SaleOrder(models.Model):
help='Website through which this order was placed.',
store=True, readonly=True)
@api.one
def _compute_website_order_line(self):
self.website_order_line = self.order_line
@api.multi
@api.depends('website_order_line.product_uom_qty', 'website_order_line.product_id')
def _compute_cart_info(self):
+1 -1
View File
@@ -177,7 +177,7 @@ class Website(models.Model):
'partner_id': partner.id,
'pricelist_id': pricelist.id,
'payment_term_id': self.sale_get_payment_term(partner),
'team_id': self.salesteam_id.id,
'team_id': self.salesteam_id.id or partner.parent_id.team_id.id or partner.team_id.id,
'partner_invoice_id': partner.id,
'partner_shipping_id': addr['delivery'],
'user_id': salesperson_id or self.salesperson_id.id or default_user_id,
@@ -17,11 +17,11 @@ class SaleOrder(models.Model):
has_delivery = fields.Boolean(
compute='_compute_has_delivery', string='Has delivery',
help="Has an order line set for delivery", store=True)
website_order_line = fields.One2many(
'sale.order.line', 'order_id',
string='Order Lines displayed on Website', readonly=True,
domain=[('is_delivery', '=', False)],
help='Order Lines to be displayed on the website. They should not be used for computation purpose.')
@api.one
def _compute_website_order_line(self):
super(SaleOrder, self)._compute_website_order_line()
self.website_order_line = self.website_order_line.filtered(lambda l: not l.is_delivery)
@api.depends('order_line.price_unit', 'order_line.tax_id', 'order_line.discount', 'order_line.product_uom_qty')
def _compute_amount_delivery(self):
+16
View File
@@ -0,0 +1,16 @@
India, 2018-09-18
Dreambits Technologies Pvt. Ltd. agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Karan Shah admin@dreambits.in https://github.com/karan-dreambits
List of contributors:
Karan Shah admin@dreambits.in https://github.com/karan-dreambits
Devang Pipaliya devang@dreambits.in https://github.com/devang-dreambits
+17
View File
@@ -0,0 +1,17 @@
China, 2018-09-28
Inspur Genersoft Co., Ltd agrees to the terms of the Odoo Corporate Contributor
License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
David Yu, yudw@inspur.com
List of contributors:
Brain Wang wangbuke@inspur.com
David Yu yudw@inspur.com
Alex Aisin-Gioro wubai@inspur.com
+2 -2
View File
@@ -56,7 +56,7 @@ Running the server
Maximum allowed virtual memory per worker. If the limit is exceeded,
the worker is killed and recycled at the end of the current request.
Defaults to 2048MB.
Defaults to 2048MiB.
.. option:: --limit-memory-hard <limit>
@@ -64,7 +64,7 @@ Running the server
immediately killed without waiting for the end of the current request
processing.
Defaults to 2560MB.
Defaults to 2560MiB.
.. option:: --limit-time-cpu <limit>
@@ -653,6 +653,7 @@
<field file="base/static/img/country_flags/gt.png" name="image" type="base64" />
<field name="currency_id" ref="GTQ" />
<field eval="502" name="phone_code" />
<field name="vat_label">NIT</field>
</record>
<record id="gu" model="res.country">
<field name="name">Guam</field>
@@ -1127,6 +1128,7 @@
<field name="currency_id" ref="MXN" />
<field eval="52" name="phone_code" />
<field name="vat_label">RFC</field>
<field eval="'%(street)s %(street2)s\n%(zip)s %(city)s, %(state_code)s\n%(country_name)s'" name="address_format" />
</record>
<record id="my" model="res.country">
<field name="name">Malaysia</field>
@@ -1732,6 +1734,7 @@
<field file="base/static/img/country_flags/vn.png" name="image" type="base64" />
<field name="currency_id" ref="VND" />
<field eval="84" name="phone_code" />
<field eval="'%(street)s\n%(street2)s\n%(city)s\n%(name)s %(zip)s %(country_name)s'" name="address_format" />
</record>
<record id="vu" model="res.country">
<field name="name">Vanuatu</field>
+3
View File
@@ -568,6 +568,9 @@ class IrActionsServer(models.Model):
active_id = self._context.get('active_id')
if not active_id and self._context.get('onchange_self'):
active_id = self._context['onchange_self']._origin.id
if not active_id: # onchange on new record
func = getattr(self, 'run_action_%s' % action.state)
res = func(action, eval_context=eval_context)
active_ids = self._context.get('active_ids', [active_id] if active_id else [])
for active_id in active_ids:
# run context dedicated to a particular active_id
+6 -4
View File
@@ -298,16 +298,18 @@ class IrHttp(models.AbstractModel):
obj = cls._xmlid_to_obj(env, xmlid)
if access_mode:
obj = cls.check_access_mode(env, id, access_mode, model, access_token=access_token, related_id=related_id)
elif id and model == 'ir.attachment' and access_token:
obj = env[model].sudo().browse(int(id))
if not consteq(obj.access_token or '', access_token):
return (403, [], None)
elif id and model in env.registry:
obj = env[model].browse(int(id))
# obj exists
if not obj or not obj.exists() or field not in obj:
return (404, [], None)
# access token grant access
if model == 'ir.attachment' and access_token:
obj = obj.sudo()
if not consteq(obj.access_token or '', access_token):
return (403, [], None)
# check read access
try:
last_update = obj['__last_update']
+1 -1
View File
@@ -1038,7 +1038,7 @@ class GroupsView(models.Model):
def linearize(app, gs):
# 'User Type' is an exception
if app.xml_id == 'base.module_category_user_type':
return (app, 'selection', gs)
return (app, 'selection', gs.sorted('id'))
# determine sequence order: a group appears after its implied groups
order = {g: len(g.trans_implied_ids & gs) for g in gs}
# check whether order is total, i.e., sequence orders are distinct
+50
View File
@@ -80,3 +80,53 @@ class test_ir_http_mimetype(common.TransactionCase):
mimetype = dict(headers).get('Content-Type')
self.assertEqual(mimetype, 'image/gif')
def test_ir_http_attachment_access(self):
""" Test attachment access with and without access token """
public_user = self.env.ref('base.public_user')
attachment = self.env['ir.attachment'].create({
'datas': GIF,
'name': 'Test valid access token with image',
'datas_fname': 'image.gif'
})
defaults = {
'id': attachment.id,
'default_mimetype': 'image/gif',
'env': public_user.sudo(public_user.id).env,
}
def test_access(**kwargs):
status, _, _ = self.env['ir.http'].binary_content(
**defaults, **kwargs
)
return status
status = test_access()
self.assertEqual(status, 403, "no access")
status = test_access(access_token='Secret')
self.assertEqual(status, 403,
"no access if access token for attachment without access token")
attachment.access_token = 'Secret'
status = test_access(access_token='Secret')
self.assertEqual(status, 200, "access for correct access token")
status = test_access(access_token='Wrong')
self.assertEqual(status, 403, "no access for wrong access token")
attachment.public = True
status = test_access()
self.assertEqual(status, 200, "access for attachment with access")
status = test_access(access_token='Wrong')
self.assertEqual(status, 403,
"no access for wrong access token for attachment with access")
attachment.unlink()
status = test_access()
self.assertEqual(status, 404, "no access for deleted attachment")
status = test_access(access_token='Secret')
self.assertEqual(status, 404,
"no access with access token for deleted attachment")
+6
View File
@@ -16,6 +16,7 @@
<record id="message_0_0" model="test_new_api.message">
<field name="discussion" ref="discussion_0"/>
<field name="body">Hey dude!</field>
<field name="label">hello</field>
</record>
<record id="message_0_1" model="test_new_api.message">
<field name="discussion" ref="discussion_0"/>
@@ -27,6 +28,11 @@
<field name="body">This is a much longer message</field>
</record>
<record id="emailmessage_0_0" model="test_new_api.emailmessage">
<field name="message" ref="message_0_0"/>
<field name="email_to">first@example.localhost</field>
</record>
<record id="bool_1" model="domain.bool">
<field name="bool_true" eval="True"/>
</record>
+2 -1
View File
@@ -74,7 +74,7 @@ class Discussion(models.Model):
categories = fields.Many2many('test_new_api.category',
'test_new_api_discussion_category', 'discussion', 'category')
participants = fields.Many2many('res.users', context={'active_test': False})
messages = fields.One2many('test_new_api.message', 'discussion')
messages = fields.One2many('test_new_api.message', 'discussion', copy=True)
message_concat = fields.Text(string='Message concatenate')
important_messages = fields.One2many('test_new_api.message', 'discussion',
domain=[('important', '=', True)])
@@ -128,6 +128,7 @@ class Message(models.Model):
'res.partner', compute='_compute_author_partner',
search='_search_author_partner')
important = fields.Boolean()
label = fields.Char(translate=True)
@api.one
@api.constrains('author', 'discussion')
@@ -964,6 +964,40 @@ class TestFields(common.TransactionCase):
self.assertEqual(len(discussion.important_messages), 2)
self.assertEqual(len(discussion.very_important_messages), 2)
def test_80_copy(self):
Translations = self.env['ir.translation']
discussion = self.env.ref('test_new_api.discussion_0')
message = self.env.ref('test_new_api.message_0_0')
message1 = self.env.ref('test_new_api.message_0_1')
email = self.env.ref('test_new_api.emailmessage_0_0')
self.assertEqual(email.message, message)
french = self.env['res.lang']._lang_get('fr_FR')
french.active = True
def count(msg):
# return the number of translations of msg.label
return Translations.search_count([
('name', '=', 'test_new_api.message,label'),
('res_id', '=', msg.id),
])
# set a translation for message.label
email.with_context(lang='fr_FR').label = "bonjour"
self.assertEqual(count(message), 1)
self.assertEqual(count(message1), 0)
# setting the parent record should not copy its translations
email.copy({'message': message1.id})
self.assertEqual(count(message), 1)
self.assertEqual(count(message1), 0)
# setting a one2many should not copy translations on the lines
discussion.copy({'messages': [(6, 0, message1.ids)]})
self.assertEqual(count(message), 1)
self.assertEqual(count(message1), 0)
class TestX2many(common.TransactionCase):
def test_search_many2many(self):
@@ -0,0 +1,2 @@
module,type,name,res_id,src,value,comments
test_translation_import,code,addons/test_translation_import/models.py,20,Accounting,samva,
1 module type name res_id src value comments
2 test_translation_import code addons/test_translation_import/models.py 20 Accounting samva
@@ -0,0 +1,20 @@
# This is a test PO file, not a complete one. It is manually maintained
# to test the import translation behavior of Odoo.
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~11.4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-18 13:20+0000\n"
"PO-Revision-Date: 2018-09-18 13:20+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: test_translation_import
#: code:addons/test_translation_import/models.py:18
#, python-format
msgid "Klingon"
msgstr "tlhIngan"
@@ -15,3 +15,7 @@ class m(models.TransientModel):
# With the name label above, this source string should be generated twice.
_('1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB')
_('Klingon')
_('Accounting')
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from contextlib import closing
import base64
import io
import odoo
from odoo.tests import common
from odoo.tools.misc import file_open, mute_logger
from odoo.tools.translate import _
class TestTermCount(common.TransactionCase):
@@ -79,3 +82,49 @@ class TestTermCount(common.TransactionCase):
menu.with_context(lang='fr_FR').name = "Nouveau nom"
update_translations()
self.assertEqual(menu.with_context(lang='fr_FR').name, "Nouveau nom", 'The translation of "New Name" should be "Nouveau nom"')
def test_import_from_po_file(self):
"""Test the import from a single po file works"""
with file_open('test_translation_import/i18n/tlh.po', 'rb') as f:
po_file = base64.encodestring(f.read())
import_tlh = self.env["base.language.import"].create({
'name': 'Klingon',
'code': 'tlh',
'data': po_file,
'filename': 'tlh.po',
})
with mute_logger('odoo.addons.base.models.res_lang'):
import_tlh.import_lang()
lang_count = self.env['res.lang'].search_count([('code', '=', 'tlh')])
self.assertEqual(lang_count, 1, "The imported language was not creates")
trans_count = self.env['ir.translation'].search_count([('lang', '=', 'tlh')])
self.assertEqual(trans_count, 1, "The imported translations were not created")
self.env.context = dict(self.env.context, lang="tlh")
self.assertEqual(_("Klingon"), "tlhIngan", "The code translation was not applied")
def test_import_from_csv_file(self):
"""Test the import from a single CSV file works"""
with file_open('test_translation_import/i18n/dot.csv', 'rb') as f:
po_file = base64.encodestring(f.read())
import_tlh = self.env["base.language.import"].create({
'name': 'Dothraki',
'code': 'dot',
'data': po_file,
'filename': 'dot.csv',
})
with mute_logger('odoo.addons.base.models.res_lang'):
import_tlh.import_lang()
lang_count = self.env['res.lang'].search_count([('code', '=', 'dot')])
self.assertEqual(lang_count, 1, "The imported language was not creates")
trans_count = self.env['ir.translation'].search_count([('lang', '=', 'dot')])
self.assertEqual(trans_count, 1, "The imported translations were not created")
self.env.context = dict(self.env.context, lang="dot")
self.assertEqual(_("Accounting"), "samva", "The code translation was not applied")
+4 -1
View File
@@ -1754,7 +1754,10 @@ class Binary(Field):
return None
if isinstance(value, bytes):
return psycopg2.Binary(value)
return psycopg2.Binary(pycompat.text_type(value).encode('ascii'))
try:
return psycopg2.Binary(pycompat.text_type(value).encode('ascii'))
except UnicodeEncodeError:
raise UserError(_("ASCII characters are required for %s in %s") % (value, self.name))
def convert_to_cache(self, value, record, validate=True):
if isinstance(value, _BINARY):
+16 -3
View File
@@ -4133,7 +4133,13 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
return [default]
@api.multi
def copy_translations(old, new):
def copy_translations(old, new, excluded=()):
""" Recursively copy the translations from original to new record
:param old: the original record
:param new: the new record (copy of the original one)
:param excluded: a container of user-provided field names
"""
# avoid recursion through already copied records in case of circular relationship
if '__copy_translations_seen' not in old._context:
old = old.with_context(__copy_translations_seen=defaultdict(set))
@@ -4159,13 +4165,20 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
if not field.copy:
continue
if field.type == 'one2many':
if field.inherited and field.related[0] in excluded:
# inherited fields that come from a user-provided parent record
# must not copy translations, as the parent record is not a copy
# of the old parent record
continue
if field.type == 'one2many' and field.name not in excluded:
# we must recursively copy the translations for o2m; here we
# rely on the order of the ids to match the translations as
# foreseen in copy_data()
old_lines = old[name].sorted(key='id')
new_lines = new[name].sorted(key='id')
for (old_line, new_line) in pycompat.izip(old_lines, new_lines):
# don't pass excluded as it is not about those lines
old_line.copy_translations(new_line)
elif field.translate:
@@ -4207,7 +4220,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
vals = self.copy_data(default)[0]
# To avoid to create a translation in the lang of the user, copy_translation will do it
new = self.with_context(lang=None).create(vals)
self.with_context(from_copy_translation=True).copy_translations(new)
self.with_context(from_copy_translation=True).copy_translations(new, excluded=default or ())
return new
@api.multi
+4 -2
View File
@@ -286,10 +286,12 @@ class configmanager(object):
help="Specify the number of workers, 0 disable prefork mode.",
type="int")
group.add_option("--limit-memory-soft", dest="limit_memory_soft", my_default=2048 * 1024 * 1024,
help="Maximum allowed virtual memory per worker, when reached the worker be reset after the current request (default 671088640 aka 640MB).",
help="Maximum allowed virtual memory per worker, when reached the worker be "
"reset after the current request (default 2048MiB).",
type="int")
group.add_option("--limit-memory-hard", dest="limit_memory_hard", my_default=2560 * 1024 * 1024,
help="Maximum allowed virtual memory per worker, when reached, any memory allocation will fail (default 805306368 aka 768MB).",
help="Maximum allowed virtual memory per worker, when reached, any memory "
"allocation will fail (default 2560MiB).",
type="int")
group.add_option("--limit-time-cpu", dest="limit_time_cpu", my_default=60,
help="Maximum allowed CPU time per request (default 60).",
+23 -19
View File
@@ -1038,6 +1038,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
# (Because the POT comments are correct on Launchpad but not the
# PO comments due to a Launchpad limitation. See LP bug 933496.)
pot_reader = []
use_pot_reference = False
# now, the serious things: we read the language file
fileobj.seek(0)
@@ -1066,6 +1067,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
pot_handle = file_open(os.path.join(
addons, module, i18n_dir, module + '.pot'), mode='rb')
pot_reader = PoFile(pot_handle)
use_pot_reference = True
except:
pass
@@ -1100,17 +1102,18 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
dic['lang'] = lang
dic.update(pycompat.izip(fields, row))
# discard the target from the POT targets.
src = dic['src']
target_key = (dic['type'], dic['name'], dic['type'] != 'code' and dic['res_id'] or 0)
target = pot_targets.get(src)
if not target or target_key not in target.targets:
_logger.info("Translation '%s' (%s, %s, %s) not found in reference pot, skipping",
src[:60], dic['type'], dic['name'], dic['res_id'])
return
if use_pot_reference:
# discard the target from the POT targets.
src = dic['src']
target_key = (dic['type'], dic['name'], dic['type'] != 'code' and dic['res_id'] or 0)
target = pot_targets.get(src)
if not target or target_key not in target.targets:
_logger.info("Translation '%s' (%s, %s, %s) not found in reference pot, skipping",
src[:60], dic['type'], dic['name'], dic['res_id'])
return
target.value = dic['value']
target.targets.discard(target_key)
target.value = dic['value']
target.targets.discard(target_key)
# This would skip terms that fail to specify a res_id
res_id = dic['res_id']
@@ -1138,15 +1141,16 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
for row in reader:
process_row(row)
# Then process the entries implied by the POT file (which is more
# correct w.r.t. the targets) if some of them remain.
pot_rows = []
for src, target in pot_targets.items():
if target.value:
for type, name, res_id in target.targets:
pot_rows.append((type, name, res_id, src, target.value, target.comments))
for row in pot_rows:
process_row(row)
if use_pot_reference:
# Then process the entries implied by the POT file (which is more
# correct w.r.t. the targets) if some of them remain.
pot_rows = []
for src, target in pot_targets.items():
if target.value:
for type, name, res_id in target.targets:
pot_rows.append((type, name, res_id, src, target.value, target.comments))
for row in pot_rows:
process_row(row)
irt_cursor.finish()
Translation.clear_caches()