[MERGE] forward port branch saas-11.4 up to 57e387b645
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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']),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 ??
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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),
|
||||
])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 & 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'),
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user