[MERGE] forward port branch 11.0 up to c2e878e34c
This commit is contained in:
@@ -4,6 +4,7 @@ include README.md
|
|||||||
recursive-include odoo *.css
|
recursive-include odoo *.css
|
||||||
recursive-include odoo *.csv
|
recursive-include odoo *.csv
|
||||||
recursive-include odoo *.doc
|
recursive-include odoo *.doc
|
||||||
|
recursive-include odoo *.docx
|
||||||
recursive-include odoo *.eml
|
recursive-include odoo *.eml
|
||||||
recursive-include odoo *.eot
|
recursive-include odoo *.eot
|
||||||
recursive-include odoo *.gif
|
recursive-include odoo *.gif
|
||||||
@@ -16,6 +17,8 @@ recursive-include odoo *.less
|
|||||||
recursive-include odoo *.md
|
recursive-include odoo *.md
|
||||||
recursive-include odoo *.mp3
|
recursive-include odoo *.mp3
|
||||||
recursive-include odoo *.ogg
|
recursive-include odoo *.ogg
|
||||||
|
recursive-include odoo *.ods
|
||||||
|
recursive-include odoo *.odt
|
||||||
recursive-include odoo *.otf
|
recursive-include odoo *.otf
|
||||||
recursive-include odoo *.pdf
|
recursive-include odoo *.pdf
|
||||||
recursive-include odoo *.png
|
recursive-include odoo *.png
|
||||||
@@ -35,8 +38,10 @@ recursive-include odoo *.wsdl
|
|||||||
recursive-include odoo *.xls
|
recursive-include odoo *.xls
|
||||||
recursive-include odoo *.xsd
|
recursive-include odoo *.xsd
|
||||||
recursive-include odoo *.xsl
|
recursive-include odoo *.xsl
|
||||||
|
recursive-include odoo *.xlsx
|
||||||
recursive-include odoo *.xml
|
recursive-include odoo *.xml
|
||||||
recursive-include odoo *.yml
|
recursive-include odoo *.yml
|
||||||
|
recursive-include odoo *.zip
|
||||||
recursive-include odoo/addons/l10n_mx_edi *.xslt *.key *.cer *.txt
|
recursive-include odoo/addons/l10n_mx_edi *.xslt *.key *.cer *.txt
|
||||||
recursive-include odoo/addons/l10n_mx_reports *.xslt
|
recursive-include odoo/addons/l10n_mx_reports *.xslt
|
||||||
recursive-exclude * *.py[co]
|
recursive-exclude * *.py[co]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<field name="lang">${object.partner_id.lang}</field>
|
<field name="lang">${object.partner_id.lang}</field>
|
||||||
<field name="body_html" type="xml">
|
<field name="body_html" type="xml">
|
||||||
<p>Dear ${object.partner_id.name},</p>
|
<p>Dear ${object.partner_id.name},</p>
|
||||||
<p>Thank you for your payment.<br />Here is your payment receipt <strong>${(object.name or '').replace('/','-')}</strong> amounting to <strong>${object.amount} ${object.currency_id.name}</strong> from ${object.company_id.name}.</p>
|
<p>Thank you for your payment.<br />Here is your payment receipt <strong>${(object.name or '').replace('/','-')}</strong> amounting to <strong>${format_amount(object.amount, object.currency_id)}</strong> from ${object.company_id.name}.</p>
|
||||||
<p>If you have any questions, please do not hesitate to contact us.</p>
|
<p>If you have any questions, please do not hesitate to contact us.</p>
|
||||||
<p>Best regards,
|
<p>Best regards,
|
||||||
% if user and user.signature:
|
% if user and user.signature:
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ class AccountJournal(models.Model):
|
|||||||
for journal in self:
|
for journal in self:
|
||||||
if journal.sequence_id and journal.sequence_number_next:
|
if journal.sequence_id and journal.sequence_number_next:
|
||||||
sequence = journal.sequence_id._get_current_sequence()
|
sequence = journal.sequence_id._get_current_sequence()
|
||||||
sequence.number_next = journal.sequence_number_next
|
sequence.sudo().number_next = journal.sequence_number_next
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
# do not depend on 'refund_sequence_id.date_range_ids', because
|
# do not depend on 'refund_sequence_id.date_range_ids', because
|
||||||
@@ -525,6 +525,8 @@ class AccountJournal(models.Model):
|
|||||||
if ('company_id' in vals and journal.company_id.id != vals['company_id']):
|
if ('company_id' in vals and journal.company_id.id != vals['company_id']):
|
||||||
if self.env['account.move'].search([('journal_id', 'in', self.ids)], limit=1):
|
if self.env['account.move'].search([('journal_id', 'in', self.ids)], limit=1):
|
||||||
raise UserError(_('This journal already contains items, therefore you cannot modify its company.'))
|
raise UserError(_('This journal already contains items, therefore you cannot modify its company.'))
|
||||||
|
if self.bank_account_id:
|
||||||
|
self.bank_account_id.company_id = vals['company_id']
|
||||||
if ('code' in vals and journal.code != vals['code']):
|
if ('code' in vals and journal.code != vals['code']):
|
||||||
if self.env['account.move'].search([('journal_id', 'in', self.ids)], limit=1):
|
if self.env['account.move'].search([('journal_id', 'in', self.ids)], limit=1):
|
||||||
raise UserError(_('This journal already contains items, therefore you cannot modify its short name.'))
|
raise UserError(_('This journal already contains items, therefore you cannot modify its short name.'))
|
||||||
@@ -538,6 +540,8 @@ class AccountJournal(models.Model):
|
|||||||
self.default_debit_account_id.currency_id = vals['currency_id']
|
self.default_debit_account_id.currency_id = vals['currency_id']
|
||||||
if not 'default_credit_account_id' in vals and self.default_credit_account_id:
|
if not 'default_credit_account_id' in vals and self.default_credit_account_id:
|
||||||
self.default_credit_account_id.currency_id = vals['currency_id']
|
self.default_credit_account_id.currency_id = vals['currency_id']
|
||||||
|
if self.bank_account_id:
|
||||||
|
self.bank_account_id.currency_id = vals['currency_id']
|
||||||
if 'bank_account_id' in vals and not vals.get('bank_account_id'):
|
if 'bank_account_id' in vals and not vals.get('bank_account_id'):
|
||||||
raise UserError(_('You cannot remove the bank account from the journal once set.'))
|
raise UserError(_('You cannot remove the bank account from the journal once set.'))
|
||||||
result = super(AccountJournal, self).write(vals)
|
result = super(AccountJournal, self).write(vals)
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ class AccountBankStatement(models.Model):
|
|||||||
def link_bank_to_partner(self):
|
def link_bank_to_partner(self):
|
||||||
for statement in self:
|
for statement in self:
|
||||||
for st_line in statement.line_ids:
|
for st_line in statement.line_ids:
|
||||||
if st_line.bank_account_id and st_line.partner_id and st_line.bank_account_id.partner_id != st_line.partner_id:
|
if st_line.bank_account_id and st_line.partner_id and not st_line.bank_account_id.partner_id:
|
||||||
st_line.bank_account_id.partner_id = st_line.partner_id
|
st_line.bank_account_id.partner_id = st_line.partner_id
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -341,12 +341,13 @@ class account_journal(models.Model):
|
|||||||
ctx.update({
|
ctx.update({
|
||||||
'journal_type': self.type,
|
'journal_type': self.type,
|
||||||
'default_journal_id': self.id,
|
'default_journal_id': self.id,
|
||||||
'search_default_journal_id': self.id,
|
|
||||||
'default_type': invoice_type,
|
'default_type': invoice_type,
|
||||||
'type': invoice_type
|
'type': invoice_type
|
||||||
})
|
})
|
||||||
|
|
||||||
[action] = self.env.ref('account.%s' % action_name).read()
|
[action] = self.env.ref('account.%s' % action_name).read()
|
||||||
|
if not self.env.context.get('use_domain'):
|
||||||
|
ctx['search_default_journal_id'] = self.id
|
||||||
action['context'] = ctx
|
action['context'] = ctx
|
||||||
action['domain'] = self._context.get('use_domain', [])
|
action['domain'] = self._context.get('use_domain', [])
|
||||||
account_invoice_filter = self.env.ref('account.view_account_invoice_filter', False)
|
account_invoice_filter = self.env.ref('account.view_account_invoice_filter', False)
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class AccountMove(models.Model):
|
|||||||
default=lambda self: self.env.user.company_id)
|
default=lambda self: self.env.user.company_id)
|
||||||
matched_percentage = fields.Float('Percentage Matched', compute='_compute_matched_percentage', digits=0, store=True, readonly=True, help="Technical field used in cash basis method")
|
matched_percentage = fields.Float('Percentage Matched', compute='_compute_matched_percentage', digits=0, store=True, readonly=True, help="Technical field used in cash basis method")
|
||||||
# Dummy Account field to search on account.move by account_id
|
# Dummy Account field to search on account.move by account_id
|
||||||
dummy_account_id = fields.Many2one('account.account', related='line_ids.account_id', string='Account', store=False)
|
dummy_account_id = fields.Many2one('account.account', related='line_ids.account_id', string='Account', store=False, readonly=True)
|
||||||
tax_cash_basis_rec_id = fields.Many2one(
|
tax_cash_basis_rec_id = fields.Many2one(
|
||||||
'account.partial.reconcile',
|
'account.partial.reconcile',
|
||||||
string='Tax Cash Basis Entry of',
|
string='Tax Cash Basis Entry of',
|
||||||
@@ -1361,7 +1361,7 @@ class AccountMoveLine(models.Model):
|
|||||||
record.payment_id.state = 'reconciled'
|
record.payment_id.state = 'reconciled'
|
||||||
|
|
||||||
result = super(AccountMoveLine, self).write(vals)
|
result = super(AccountMoveLine, self).write(vals)
|
||||||
if self._context.get('check_move_validity', True):
|
if self._context.get('check_move_validity', True) and any(key in vals for key in ('account_id', 'journal_id', 'date', 'move_id', 'debit', 'credit')):
|
||||||
move_ids = set()
|
move_ids = set()
|
||||||
for line in self:
|
for line in self:
|
||||||
if line.move_id.id not in move_ids:
|
if line.move_id.id not in move_ids:
|
||||||
|
|||||||
@@ -576,9 +576,9 @@ class account_payment(models.Model):
|
|||||||
writeoff_line['amount_currency'] = amount_currency_wo
|
writeoff_line['amount_currency'] = amount_currency_wo
|
||||||
writeoff_line['currency_id'] = currency_id
|
writeoff_line['currency_id'] = currency_id
|
||||||
writeoff_line = aml_obj.create(writeoff_line)
|
writeoff_line = aml_obj.create(writeoff_line)
|
||||||
if counterpart_aml['debit'] or writeoff_line['credit']:
|
if counterpart_aml['debit'] or (writeoff_line['credit'] and not counterpart_aml['credit']):
|
||||||
counterpart_aml['debit'] += credit_wo - debit_wo
|
counterpart_aml['debit'] += credit_wo - debit_wo
|
||||||
if counterpart_aml['credit'] or writeoff_line['debit']:
|
if counterpart_aml['credit'] or (writeoff_line['debit'] and not counterpart_aml['debit']):
|
||||||
counterpart_aml['credit'] += debit_wo - credit_wo
|
counterpart_aml['credit'] += debit_wo - credit_wo
|
||||||
counterpart_aml['amount_currency'] -= amount_currency_wo
|
counterpart_aml['amount_currency'] -= amount_currency_wo
|
||||||
|
|
||||||
|
|||||||
@@ -526,17 +526,37 @@ var StatementModel = BasicModel.extend({
|
|||||||
*/
|
*/
|
||||||
togglePartialReconcile: function (handle) {
|
togglePartialReconcile: function (handle) {
|
||||||
var line = this.getLine(handle);
|
var line = this.getLine(handle);
|
||||||
var props = _.filter(line.reconciliation_proposition, {'invalid': false});
|
|
||||||
var prop = props[0];
|
// Retrieve the toggle proposition
|
||||||
if (props.length !== 1 || Math.abs(line.st_line.amount) >= Math.abs(prop.amount)) {
|
var selected;
|
||||||
|
_.each(line.reconciliation_proposition, function (prop) {
|
||||||
|
if (!prop.invalid) {
|
||||||
|
if (((line.balance.amount < 0 || !line.partial_reconcile) && prop.amount > 0 && line.st_line.amount > 0 && line.st_line.amount < prop.amount) ||
|
||||||
|
((line.balance.amount > 0 || !line.partial_reconcile) && prop.amount < 0 && line.st_line.amount < 0 && line.st_line.amount > prop.amount)) {
|
||||||
|
selected = prop;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no toggled proposition found, reject it
|
||||||
|
if (selected == null)
|
||||||
return $.Deferred().reject();
|
return $.Deferred().reject();
|
||||||
}
|
|
||||||
prop.partial_reconcile = !prop.partial_reconcile;
|
// Inverse partial_reconcile value
|
||||||
if (!prop.partial_reconcile) {
|
selected.partial_reconcile = !selected.partial_reconcile;
|
||||||
|
if (!selected.partial_reconcile) {
|
||||||
return this._computeLine(line);
|
return this._computeLine(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute the write_off
|
||||||
|
var format_options = { currency_id: line.st_line.currency_id };
|
||||||
|
selected.write_off_amount = selected.amount + line.balance.amount;
|
||||||
|
selected.write_off_amount_str = field_utils.format.monetary(Math.abs(selected.write_off_amount), {}, format_options);
|
||||||
|
selected.write_off_amount_str = selected.write_off_amount_str.replace(' ', ' ');
|
||||||
|
|
||||||
return this._computeLine(line).then(function () {
|
return this._computeLine(line).then(function () {
|
||||||
if (prop.partial_reconcile) {
|
if (selected.partial_reconcile) {
|
||||||
line.balance.amount = 0;
|
line.balance.amount = 0;
|
||||||
line.balance.type = 1;
|
line.balance.type = 1;
|
||||||
line.mode = 'inactive';
|
line.mode = 'inactive';
|
||||||
@@ -646,6 +666,7 @@ var StatementModel = BasicModel.extend({
|
|||||||
return isNaN(prop.id);
|
return isNaN(prop.id);
|
||||||
}), self._formatToProcessReconciliation.bind(self, line)),
|
}), self._formatToProcessReconciliation.bind(self, line)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the lines are not fully balanced, create an unreconciled amount.
|
// If the lines are not fully balanced, create an unreconciled amount.
|
||||||
// line.st_line.currency_id is never false here because its equivalent to
|
// line.st_line.currency_id is never false here because its equivalent to
|
||||||
// statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.journal_id.company_id.currency_id (Python-side).
|
// statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.journal_id.company_id.currency_id (Python-side).
|
||||||
@@ -1078,8 +1099,9 @@ var StatementModel = BasicModel.extend({
|
|||||||
// Do not forward port in master. @CSN will change this
|
// Do not forward port in master. @CSN will change this
|
||||||
var amount = prop.computed_with_tax && -prop.base_amount || -prop.amount;
|
var amount = prop.computed_with_tax && -prop.base_amount || -prop.amount;
|
||||||
if (prop.partial_reconcile === true) {
|
if (prop.partial_reconcile === true) {
|
||||||
amount = -line.st_line.amount;
|
amount = -prop.write_off_amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = {
|
var result = {
|
||||||
name : prop.label,
|
name : prop.label,
|
||||||
debit : amount > 0 ? amount : 0,
|
debit : amount > 0 ? amount : 0,
|
||||||
|
|||||||
@@ -353,7 +353,21 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
|
|||||||
|
|
||||||
// reconciliation_proposition
|
// reconciliation_proposition
|
||||||
var $props = this.$('.accounting_view tbody').empty();
|
var $props = this.$('.accounting_view tbody').empty();
|
||||||
var props = _.filter(state.reconciliation_proposition, {'display': true});
|
|
||||||
|
// loop state propositions
|
||||||
|
var props = [];
|
||||||
|
var nb_debit_props = 0;
|
||||||
|
var nb_credit_props = 0;
|
||||||
|
_.each(state.reconciliation_proposition, function (prop) {
|
||||||
|
if (prop.display) {
|
||||||
|
props.push(prop);
|
||||||
|
if (prop.amount < 0)
|
||||||
|
nb_debit_props += 1;
|
||||||
|
else if (prop.amount > 0)
|
||||||
|
nb_credit_props += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
_.each(props, function (line) {
|
_.each(props, function (line) {
|
||||||
var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state}));
|
var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state}));
|
||||||
if (!isNaN(line.id)) {
|
if (!isNaN(line.id)) {
|
||||||
@@ -361,18 +375,16 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
|
|||||||
.appendTo($line.find('.cell_info_popover'))
|
.appendTo($line.find('.cell_info_popover'))
|
||||||
.attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line}));
|
.attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line}));
|
||||||
}
|
}
|
||||||
|
if (line.already_paid === false &&
|
||||||
if ((state.balance.amount_currency !== 0 || line.partial_reconcile) && props.length === 1 &&
|
((state.balance.amount_currency < 0 || line.partial_reconcile) && nb_credit_props == 1
|
||||||
line.already_paid === false &&
|
&& line.amount > 0 && state.st_line.amount > 0 && state.st_line.amount < line.amount) ||
|
||||||
(
|
((state.balance.amount_currency > 0 || line.partial_reconcile) && nb_debit_props == 1
|
||||||
(state.st_line.amount > 0 && state.st_line.amount < props[0].amount) ||
|
&& line.amount < 0 && state.st_line.amount < 0 && state.st_line.amount > line.amount)) {
|
||||||
(state.st_line.amount < 0 && state.st_line.amount > props[0].amount))
|
|
||||||
) {
|
|
||||||
var $cell = $line.find(line.amount > 0 ? '.cell_right' : '.cell_left');
|
var $cell = $line.find(line.amount > 0 ? '.cell_right' : '.cell_left');
|
||||||
var text;
|
var text;
|
||||||
if (line.partial_reconcile) {
|
if (line.partial_reconcile) {
|
||||||
text = _t("Undo the partial reconciliation.");
|
text = _t("Undo the partial reconciliation.");
|
||||||
$cell.text(state.st_line.amount_str);
|
$cell.text(line.write_off_amount_str);
|
||||||
} else {
|
} else {
|
||||||
text = _t("This move's amount is higher than the transaction's amount. Click to register a partial payment and keep the payment balance open.");
|
text = _t("This move's amount is higher than the transaction's amount. Click to register a partial payment and keep the payment balance open.");
|
||||||
}
|
}
|
||||||
@@ -414,20 +426,24 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
|
|||||||
this._renderCreate(state);
|
this._renderCreate(state);
|
||||||
}
|
}
|
||||||
var data = this.model.get(this.handleCreateRecord).data;
|
var data = this.model.get(this.handleCreateRecord).data;
|
||||||
|
this.model.notifyChanges(this.handleCreateRecord, state.createForm).then(function () {
|
||||||
this.model.notifyChanges(this.handleCreateRecord, state.createForm);
|
// FIXME can't it directly written REPLACE_WITH ids=state.createForm.analytic_tag_ids
|
||||||
this.model.notifyChanges(this.handleCreateRecord, {analytic_tag_ids: {operation: 'REPLACE_WITH', ids: []}});
|
this.model.notifyChanges(this.handleCreateRecord, {analytic_tag_ids: {operation: 'REPLACE_WITH', ids: []}}).then(function (){
|
||||||
_.each(state.createForm.analytic_tag_ids, function (tag) {
|
var defs = [];
|
||||||
self.model.notifyChanges(self.handleCreateRecord, {analytic_tag_ids: {operation: 'ADD_M2M', ids: tag}});
|
_.each(state.createForm.analytic_tag_ids, function (tag) {
|
||||||
});
|
defs.push(self.model.notifyChanges(self.handleCreateRecord, {analytic_tag_ids: {operation: 'ADD_M2M', ids: tag}}));
|
||||||
|
});
|
||||||
var record = this.model.get(this.handleCreateRecord);
|
$.when.apply($, defs).then(function () {
|
||||||
_.each(this.fields, function (field, fieldName) {
|
var record = self.model.get(self.handleCreateRecord);
|
||||||
if (self._avoidFieldUpdate[fieldName]) return;
|
_.each(self.fields, function (field, fieldName) {
|
||||||
if (fieldName === "partner_id") return;
|
if (self._avoidFieldUpdate[fieldName]) return;
|
||||||
if ((data[fieldName] || state.createForm[fieldName]) && !_.isEqual(state.createForm[fieldName], data[fieldName])) {
|
if (fieldName === "partner_id") return;
|
||||||
field.reset(record);
|
if ((data[fieldName] || state.createForm[fieldName]) && !_.isEqual(state.createForm[fieldName], data[fieldName])) {
|
||||||
}
|
field.reset(record);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.$('.create .add_line').toggle(!!state.balance.amount_currency);
|
this.$('.create .add_line').toggle(!!state.balance.amount_currency);
|
||||||
@@ -639,7 +655,8 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(event.keyCode === 13) {
|
if(event.keyCode === 13) {
|
||||||
if (_.findWhere(this.model.lines, {mode: 'create'}).balance.amount) {
|
var created_lines = _.findWhere(this.model.lines, {mode: 'create'});
|
||||||
|
if (created_lines && created_lines.balance.amount) {
|
||||||
this._onCreateProposition();
|
this._onCreateProposition();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -34,17 +34,22 @@ var db = {
|
|||||||
fields: {
|
fields: {
|
||||||
id: {string: "ID", type: 'integer'},
|
id: {string: "ID", type: 'integer'},
|
||||||
code: {string: "code", type: 'integer'},
|
code: {string: "code", type: 'integer'},
|
||||||
display_name: {string: "Displayed name", type: 'char'},
|
name: {string: "Displayed name", type: 'char'},
|
||||||
},
|
},
|
||||||
records: [
|
records: [
|
||||||
{id: 282, code: 100000, display_name: "100000 Fixed Asset Account"},
|
{id: 282, code: 100000, name: "100000 Fixed Asset Account"},
|
||||||
{id: 283, code: 101000, display_name: "101000 Current Assets"},
|
{id: 283, code: 101000, name: "101000 Current Assets"},
|
||||||
{id: 284, code: 101110, display_name: "101110 Stock Valuation Account"},
|
{id: 284, code: 101110, name: "101110 Stock Valuation Account"},
|
||||||
{id: 285, code: 101120, display_name: "101120 Stock Interim Account (Received)"},
|
{id: 285, code: 101120, name: "101120 Stock Interim Account (Received)"},
|
||||||
{id: 286, code: 101130, display_name: "101130 Stock Interim Account (Delivered)"},
|
{id: 286, code: 101130, name: "101130 Stock Interim Account (Delivered)"},
|
||||||
{id: 287, code: 101200, display_name: "101200 Account Receivable"},
|
{id: 287, code: 101200, name: "101200 Account Receivable"},
|
||||||
{id: 288, code: 101300, display_name: "101300 Tax Paid"},
|
{id: 288, code: 101300, name: "101300 Tax Paid"},
|
||||||
{id: 308, code: 101401, display_name: "101401 Bank"},
|
{id: 308, code: 101401, name: "101401 Bank"},
|
||||||
|
{id: 500, code: 500, name: "500 Account"},
|
||||||
|
{id: 501, code: 501, name: "501 Account"},
|
||||||
|
{id: 502, code: 502, name: "502 Account"},
|
||||||
|
{id: 503, code: 503, name: "503 Account"},
|
||||||
|
{id: 504, code: 504, name: "504 Account"},
|
||||||
],
|
],
|
||||||
mark_as_reconciled: function () {
|
mark_as_reconciled: function () {
|
||||||
return $.when();
|
return $.when();
|
||||||
@@ -776,13 +781,7 @@ QUnit.module('account', {
|
|||||||
partner_id: false,
|
partner_id: false,
|
||||||
counterpart_aml_dicts:[],
|
counterpart_aml_dicts:[],
|
||||||
payment_aml_ids: [392],
|
payment_aml_ids: [392],
|
||||||
new_aml_dicts: [
|
new_aml_dicts: [],
|
||||||
{
|
|
||||||
"credit": 343.42,
|
|
||||||
"debit": 0,
|
|
||||||
"name": "Bank fees : Open balance"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}]
|
}]
|
||||||
], "should call process_reconciliations with partial reconcile values");
|
], "should call process_reconciliations with partial reconcile values");
|
||||||
}
|
}
|
||||||
@@ -811,14 +810,14 @@ QUnit.module('account', {
|
|||||||
assert.notOk( widget.$('.cell_left .line_info_button').length, "should not display the partial reconciliation alert");
|
assert.notOk( widget.$('.cell_left .line_info_button').length, "should not display the partial reconciliation alert");
|
||||||
widget.$('.accounting_view thead td:first').trigger('click');
|
widget.$('.accounting_view thead td:first').trigger('click');
|
||||||
widget.$('.match .cell_account_code:first').trigger('click');
|
widget.$('.match .cell_account_code:first').trigger('click');
|
||||||
assert.equal( widget.$('.accounting_view tbody .cell_left .line_info_button').length, 0, "should not display the partial reconciliation alert");
|
assert.equal( widget.$('.accounting_view tbody .cell_left .line_info_button').length, 1, "should display the partial reconciliation alert");
|
||||||
assert.ok( widget.$('button.btn-primary:not(hidden)').length, "should not display the reconcile button");
|
assert.ok( widget.$('button.btn-primary:not(hidden)').length, "should not display the reconcile button");
|
||||||
assert.ok( widget.$('.text-danger:not(hidden)').length, "should display counterpart alert");
|
assert.ok( widget.$('.text-danger:not(hidden)').length, "should display counterpart alert");
|
||||||
widget.$('.accounting_view .cell_left .line_info_button').trigger('click');
|
widget.$('.accounting_view .cell_left .line_info_button').trigger('click');
|
||||||
assert.strictEqual(widget.$('.accounting_view .cell_left .line_info_button').length, 0, "should not display a partial reconciliation alert");
|
assert.strictEqual(widget.$('.accounting_view .cell_left .line_info_button').length, 1, "should display a partial reconciliation alert");
|
||||||
assert.notOk(widget.$('.accounting_view .cell_left .line_info_button').hasClass('do_partial_reconcile_false'), "should not display the partial reconciliation information");
|
assert.notOk(widget.$('.accounting_view .cell_left .line_info_button').hasClass('do_partial_reconcile_true'), "should display the partial reconciliation information");
|
||||||
assert.ok( widget.$('button.btn-default:not(hidden)').length, "should display the validate button");
|
assert.ok( widget.$('button.btn-default:not(hidden)').length, "should display the validate button");
|
||||||
assert.strictEqual( widget.$el.data('mode'), "match", "should be inactive mode");
|
assert.strictEqual( widget.$el.data('mode'), "inactive", "should be inactive mode");
|
||||||
widget.$('button.btn-default:not(hidden)').trigger('click');
|
widget.$('button.btn-default:not(hidden)').trigger('click');
|
||||||
|
|
||||||
clientAction.destroy();
|
clientAction.destroy();
|
||||||
@@ -1036,13 +1035,77 @@ QUnit.module('account', {
|
|||||||
clientAction.destroy();
|
clientAction.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('Reconciliation create line (many2one test)', function (assert) {
|
||||||
|
assert.expect(5);
|
||||||
|
|
||||||
|
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
|
||||||
|
var def = $.Deferred();
|
||||||
|
|
||||||
|
testUtils.addMockEnvironment(clientAction, {
|
||||||
|
data: this.params.data,
|
||||||
|
session: {
|
||||||
|
currencies: {
|
||||||
|
3: {
|
||||||
|
digits: [69, 2],
|
||||||
|
position: "before",
|
||||||
|
symbol: "$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
archs: {
|
||||||
|
"account.account,false,list": '<tree string="Account"><field name="code"/><field name="name"/></tree>',
|
||||||
|
"account.account,false,search": '<search string="Account"><field name="code"/></search>',
|
||||||
|
},
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
if (args.method === 'name_get') {
|
||||||
|
return def.then(this._super.bind(this, route, args));
|
||||||
|
}
|
||||||
|
return this._super(route, args);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
clientAction.prependTo($('#qunit-fixture'));
|
||||||
|
|
||||||
|
var widget = clientAction.widgets[0];
|
||||||
|
|
||||||
|
// open the first line in write-off mode
|
||||||
|
widget.$('.accounting_view tfoot td:first').trigger('click');
|
||||||
|
|
||||||
|
// select an account with the many2one (drop down)
|
||||||
|
widget.$('.create .create_account_id input').trigger('click');
|
||||||
|
$('.ui-autocomplete .ui-menu-item a:contains(101200)').trigger('mouseenter').trigger('click');
|
||||||
|
assert.strictEqual(widget.$('.create .create_account_id input').val(), "101200 Account Receivable", "Display the selected account");
|
||||||
|
assert.strictEqual(widget.$('tbody:first .cell_account_code').text(), "101200", "Display the code of the selected account");
|
||||||
|
|
||||||
|
// use the many2one select dialog to change the account
|
||||||
|
widget.$('.create .create_account_id input').trigger('click');
|
||||||
|
$('.ui-autocomplete .ui-menu-item a:contains(Search)').trigger('mouseenter').trigger('click');
|
||||||
|
// select the account who does not appear in the drop drown
|
||||||
|
$('.modal tr.o_data_row:contains(502)').click();
|
||||||
|
assert.strictEqual(widget.$('.create .create_account_id input').val(), "101200 Account Receivable", "Selected account does not change");
|
||||||
|
// wait the name_get to render the changes
|
||||||
|
def.resolve();
|
||||||
|
assert.strictEqual(widget.$('.create .create_account_id input').val(), "502 Account", "Display the selected account");
|
||||||
|
assert.strictEqual(widget.$('tbody:first .cell_account_code').text(), "502", "Display the code of the selected account");
|
||||||
|
clientAction.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test('Reconciliation create line with taxes', function (assert) {
|
QUnit.test('Reconciliation create line with taxes', function (assert) {
|
||||||
assert.expect(13);
|
assert.expect(13);
|
||||||
|
|
||||||
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
|
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
|
||||||
|
|
||||||
testUtils.addMockEnvironment(clientAction, {
|
testUtils.addMockEnvironment(clientAction, {
|
||||||
'data': this.params.data,
|
data: this.params.data,
|
||||||
|
session: {
|
||||||
|
currencies: {
|
||||||
|
3: {
|
||||||
|
digits: [69, 2],
|
||||||
|
position: "before",
|
||||||
|
symbol: "$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
clientAction.appendTo($('#qunit-fixture'));
|
clientAction.appendTo($('#qunit-fixture'));
|
||||||
|
|
||||||
@@ -1054,26 +1117,26 @@ QUnit.module('account', {
|
|||||||
widget.$('.create .create_label input').val('test1').trigger('input');
|
widget.$('.create .create_label input').val('test1').trigger('input');
|
||||||
widget.$('.create .create_amount input').val('1100').trigger('input');
|
widget.$('.create .create_amount input').val('1100').trigger('input');
|
||||||
|
|
||||||
assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text(), "1100.00", "should display the value 1100.00 in left column");
|
assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text(), "$\u00a01100.00", "should display the value 1100.00 in left column");
|
||||||
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'");
|
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'");
|
||||||
assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "75.00", "should display 'Open Balance' with 75.00 in right column");
|
assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$\u00a075.00", "should display 'Open Balance' with 75.00 in right column");
|
||||||
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 1, "should have 1 created reconcile lines");
|
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 1, "should have 1 created reconcile lines");
|
||||||
|
|
||||||
widget.$('.create .create_tax_id input').trigger('click');
|
widget.$('.create .create_tax_id input').trigger('click');
|
||||||
$('.ui-autocomplete .ui-menu-item a:contains(10.00%)').trigger('mouseenter').trigger('click');
|
$('.ui-autocomplete .ui-menu-item a:contains(10.00%)').trigger('mouseenter').trigger('click');
|
||||||
|
|
||||||
assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "1000.00100.00", "should have 2 created reconcile lines with right column values");
|
assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "$\u00a01000.00$\u00a0100.00", "should have 2 created reconcile lines with right column values");
|
||||||
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'");
|
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'");
|
||||||
assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "75.00", "should display 'Open Balance' with 75.00 in right column");
|
assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$\u00a075.00", "should display 'Open Balance' with 75.00 in right column");
|
||||||
assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "", "should display 'Open Balance' without any value in left column");
|
assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "", "should display 'Open Balance' without any value in left column");
|
||||||
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "should have 2 created reconcile lines");
|
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "should have 2 created reconcile lines");
|
||||||
|
|
||||||
widget.$('.create .create_tax_id input').trigger('click');
|
widget.$('.create .create_tax_id input').trigger('click');
|
||||||
$('.ui-autocomplete .ui-menu-item a:contains(20.00%)').trigger('mouseenter').trigger('click');
|
$('.ui-autocomplete .ui-menu-item a:contains(20.00%)').trigger('mouseenter').trigger('click');
|
||||||
|
|
||||||
assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "1100.00220.00", "should have 2 created reconcile lines with right column values");
|
assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "$\u00a01100.00$\u00a0220.00", "should have 2 created reconcile lines with right column values");
|
||||||
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Create Write-off", "should display 'Create Write-off'");
|
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Create Write-off", "should display 'Create Write-off'");
|
||||||
assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "145.00", "should display 'Create Write-off' with 145.00 in right column");
|
assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "$\u00a0145.00", "should display 'Create Write-off' with 145.00 in right column");
|
||||||
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "should have 2 created reconcile lines");
|
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "should have 2 created reconcile lines");
|
||||||
|
|
||||||
clientAction.destroy();
|
clientAction.destroy();
|
||||||
@@ -1085,7 +1148,7 @@ QUnit.module('account', {
|
|||||||
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
|
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
|
||||||
|
|
||||||
testUtils.addMockEnvironment(clientAction, {
|
testUtils.addMockEnvironment(clientAction, {
|
||||||
'data': this.params.data,
|
data: this.params.data,
|
||||||
});
|
});
|
||||||
clientAction.appendTo($('#qunit-fixture'));
|
clientAction.appendTo($('#qunit-fixture'));
|
||||||
|
|
||||||
|
|||||||
@@ -361,3 +361,53 @@ class TestPayment(AccountingTestCase):
|
|||||||
{'account_id': self.account_eur.id, 'debit': 300.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
|
{'account_id': self.account_eur.id, 'debit': 300.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
|
||||||
{'account_id': inv_3.account_id.id, 'debit': 0.0, 'credit': 300.0, 'amount_currency': 0.0, 'currency_id': False},
|
{'account_id': inv_3.account_id.id, 'debit': 0.0, 'credit': 300.0, 'amount_currency': 0.0, 'currency_id': False},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_payment_and_writeoff_in_other_currency(self):
|
||||||
|
# Use case:
|
||||||
|
# Company is in EUR, create a customer invoice for 25 EUR and register payment of 25 USD.
|
||||||
|
# Mark invoice as fully paid with a write_off
|
||||||
|
# Check that all the aml are correctly created.
|
||||||
|
invoice = self.create_invoice(amount=25, type='out_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id)
|
||||||
|
# register payment on invoice
|
||||||
|
payment = self.payment_model.create({'payment_type': 'inbound',
|
||||||
|
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||||
|
'partner_type': 'customer',
|
||||||
|
'partner_id': self.partner_agrolait.id,
|
||||||
|
'amount': 25,
|
||||||
|
'currency_id': self.currency_usd_id,
|
||||||
|
'payment_date': time.strftime('%Y') + '-07-15',
|
||||||
|
'payment_difference_handling': 'reconcile',
|
||||||
|
'writeoff_account_id': self.account_payable.id,
|
||||||
|
'journal_id': self.bank_journal_euro.id,
|
||||||
|
'invoice_ids': [(4, invoice.id, None)]
|
||||||
|
})
|
||||||
|
payment.post()
|
||||||
|
self.check_journal_items(payment.move_line_ids, [
|
||||||
|
{'account_id': self.account_eur.id, 'debit': 16.35, 'credit': 0.0, 'amount_currency': 25.0, 'currency_id': self.currency_usd_id},
|
||||||
|
{'account_id': self.account_payable.id, 'debit': 8.65, 'credit': 0.0, 'amount_currency': 13.22, 'currency_id': self.currency_usd_id},
|
||||||
|
{'account_id': self.account_receivable.id, 'debit': 0.0, 'credit': 25.0, 'amount_currency': -38.22, 'currency_id': self.currency_usd_id},
|
||||||
|
])
|
||||||
|
# Use case:
|
||||||
|
# Company is in EUR, create a vendor bill for 25 EUR and register payment of 25 USD.
|
||||||
|
# Mark invoice as fully paid with a write_off
|
||||||
|
# Check that all the aml are correctly created.
|
||||||
|
invoice = self.create_invoice(amount=25, type='in_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id)
|
||||||
|
# register payment on invoice
|
||||||
|
payment = self.payment_model.create({'payment_type': 'inbound',
|
||||||
|
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||||
|
'partner_type': 'supplier',
|
||||||
|
'partner_id': self.partner_agrolait.id,
|
||||||
|
'amount': 25,
|
||||||
|
'currency_id': self.currency_usd_id,
|
||||||
|
'payment_date': time.strftime('%Y') + '-07-15',
|
||||||
|
'payment_difference_handling': 'reconcile',
|
||||||
|
'writeoff_account_id': self.account_payable.id,
|
||||||
|
'journal_id': self.bank_journal_euro.id,
|
||||||
|
'invoice_ids': [(4, invoice.id, None)]
|
||||||
|
})
|
||||||
|
payment.post()
|
||||||
|
self.check_journal_items(payment.move_line_ids, [
|
||||||
|
{'account_id': self.account_eur.id, 'debit': 16.35, 'credit': 0.0, 'amount_currency': 25.0, 'currency_id': self.currency_usd_id},
|
||||||
|
{'account_id': self.account_payable.id, 'debit': 0.0, 'credit': 8.65, 'amount_currency': -13.22, 'currency_id': self.currency_usd_id},
|
||||||
|
{'account_id': self.account_receivable.id, 'debit': 0.0, 'credit': 7.7, 'amount_currency': -11.78, 'currency_id': self.currency_usd_id},
|
||||||
|
])
|
||||||
|
|||||||
@@ -537,10 +537,8 @@
|
|||||||
<field name="number" string="Invoice" filter_domain="['|','|','|', ('number','ilike',self), ('origin','ilike',self), ('reference', 'ilike', self), ('partner_id', 'child_of', self)]"/>
|
<field name="number" string="Invoice" filter_domain="['|','|','|', ('number','ilike',self), ('origin','ilike',self), ('reference', 'ilike', self), ('partner_id', 'child_of', self)]"/>
|
||||||
<field name="journal_id" />
|
<field name="journal_id" />
|
||||||
<filter name="draft" string="Draft" domain="[('state','=','draft')]"/>
|
<filter name="draft" string="Draft" domain="[('state','=','draft')]"/>
|
||||||
<filter name="invoices" string="Invoices" domain="['&', ('state','in',['draft','open','paid']),('type','in',('out_invoice','in_invoice'))]"/>
|
<filter name="unpaid" string="Open" domain="[('state', '=', 'open')]"/>
|
||||||
<filter name="refunds" string="Credit Notes" domain="['&', ('state','in',['draft','open','paid']),('type','in',('out_refund','in_refund'))]"/>
|
<filter name="paid" string="Paid" domain="[('state', '=', 'paid')]"/>
|
||||||
<separator/>
|
|
||||||
<filter name="unpaid" string="Not Paid" domain="[('state','=','open')]"/>
|
|
||||||
<filter name="late" string="Overdue" domain="['&', ('date_due', '<', time.strftime('%%Y-%%m-%%d')), ('state', '=', 'open')]" help="Overdue invoices, maturity date passed"/>
|
<filter name="late" string="Overdue" domain="['&', ('date_due', '<', time.strftime('%%Y-%%m-%%d')), ('state', '=', 'open')]" help="Overdue invoices, maturity date passed"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<field name="partner_id" operator="child_of"/>
|
<field name="partner_id" operator="child_of"/>
|
||||||
@@ -574,33 +572,25 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- TODO: remove in master -->
|
||||||
<record id="view_account_invoice_filter_inherit_invoices" model="ir.ui.view">
|
<record id="view_account_invoice_filter_inherit_invoices" model="ir.ui.view">
|
||||||
<field name="name">account.invoice.select.invoices</field>
|
<field name="name">account.invoice.select.invoices</field>
|
||||||
<field name="model">account.invoice</field>
|
<field name="model">account.invoice</field>
|
||||||
<field name="mode">primary</field>
|
<field name="mode">primary</field>
|
||||||
<field name="inherit_id" ref="view_account_invoice_filter"/>
|
<field name="inherit_id" ref="view_account_invoice_filter"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<data>
|
<data/>
|
||||||
<xpath expr="filter[@name='invoices']" position="attributes">
|
|
||||||
<attribute name="string">Not Draft</attribute>
|
|
||||||
</xpath>
|
|
||||||
<filter name="refunds" position="replace"/>
|
|
||||||
</data>
|
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- TODO: remove in master -->
|
||||||
<record id="view_account_invoice_filter_inherit_credit_notes" model="ir.ui.view">
|
<record id="view_account_invoice_filter_inherit_credit_notes" model="ir.ui.view">
|
||||||
<field name="name">account.invoice.select.credit.notes</field>
|
<field name="name">account.invoice.select.credit.notes</field>
|
||||||
<field name="model">account.invoice</field>
|
<field name="model">account.invoice</field>
|
||||||
<field name="mode">primary</field>
|
<field name="mode">primary</field>
|
||||||
<field name="inherit_id" ref="view_account_invoice_filter"/>
|
<field name="inherit_id" ref="view_account_invoice_filter"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<data>
|
<data/>
|
||||||
<xpath expr="filter[@name='refunds']" position="attributes">
|
|
||||||
<attribute name="string">Not Draft</attribute>
|
|
||||||
</xpath>
|
|
||||||
<filter name="invoices" position="replace"/>
|
|
||||||
</data>
|
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
@@ -671,7 +661,7 @@
|
|||||||
<field eval="False" name="view_id"/>
|
<field eval="False" name="view_id"/>
|
||||||
<field name="domain">[('type','=','out_invoice')]</field>
|
<field name="domain">[('type','=','out_invoice')]</field>
|
||||||
<field name="context">{'type':'out_invoice', 'journal_type': 'sale'}</field>
|
<field name="context">{'type':'out_invoice', 'journal_type': 'sale'}</field>
|
||||||
<field name="search_view_id" ref="view_account_invoice_filter_inherit_invoices"/>
|
<field name="search_view_id" ref="view_account_invoice_filter"/>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p class="o_view_nocontent_smiling_face">
|
<p class="o_view_nocontent_smiling_face">
|
||||||
Create a customer invoice
|
Create a customer invoice
|
||||||
@@ -706,7 +696,7 @@
|
|||||||
<field eval="False" name="view_id"/>
|
<field eval="False" name="view_id"/>
|
||||||
<field name="domain">[('type','=','out_refund')]</field>
|
<field name="domain">[('type','=','out_refund')]</field>
|
||||||
<field name="context">{'default_type': 'out_refund', 'type': 'out_refund', 'journal_type': 'sale'}</field>
|
<field name="context">{'default_type': 'out_refund', 'type': 'out_refund', 'journal_type': 'sale'}</field>
|
||||||
<field name="search_view_id" ref="view_account_invoice_filter_inherit_credit_notes"/>
|
<field name="search_view_id" ref="view_account_invoice_filter"/>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p class="o_view_nocontent_smiling_face">
|
<p class="o_view_nocontent_smiling_face">
|
||||||
Create a credit note
|
Create a credit note
|
||||||
@@ -744,7 +734,7 @@
|
|||||||
<field eval="False" name="view_id"/>
|
<field eval="False" name="view_id"/>
|
||||||
<field name="domain">[('type','=','in_invoice')]</field>
|
<field name="domain">[('type','=','in_invoice')]</field>
|
||||||
<field name="context">{'default_type': 'in_invoice', 'type': 'in_invoice', 'journal_type': 'purchase'}</field>
|
<field name="context">{'default_type': 'in_invoice', 'type': 'in_invoice', 'journal_type': 'purchase'}</field>
|
||||||
<field name="search_view_id" ref="view_account_invoice_filter_inherit_invoices"/>
|
<field name="search_view_id" ref="view_account_invoice_filter"/>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p class="o_view_nocontent_smiling_face">
|
<p class="o_view_nocontent_smiling_face">
|
||||||
Record a new vendor bill
|
Record a new vendor bill
|
||||||
@@ -780,7 +770,7 @@
|
|||||||
<field eval="False" name="view_id"/>
|
<field eval="False" name="view_id"/>
|
||||||
<field name="domain">[('type','=','in_refund')]</field>
|
<field name="domain">[('type','=','in_refund')]</field>
|
||||||
<field name="context">{'default_type': 'in_refund', 'type': 'in_refund', 'journal_type': 'purchase'}</field>
|
<field name="context">{'default_type': 'in_refund', 'type': 'in_refund', 'journal_type': 'purchase'}</field>
|
||||||
<field name="search_view_id" ref="view_account_invoice_filter_inherit_credit_notes"/>
|
<field name="search_view_id" ref="view_account_invoice_filter"/>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p class="o_view_nocontent_smiling_face">
|
<p class="o_view_nocontent_smiling_face">
|
||||||
Create a vendor credit note
|
Create a vendor credit note
|
||||||
|
|||||||
@@ -117,16 +117,12 @@
|
|||||||
<span>View</span>
|
<span>View</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a type="object" name="open_action" context="{'use_domain': [('type','in',('out_invoice','in_invoice'))]}">
|
<a t-if="journal_type == 'sale'" type="object" name="open_action" context="{'use_domain': [('type', '=', 'out_invoice')]}">Invoices</a>
|
||||||
<span t-if="journal_type == 'sale'">Invoices</span>
|
<a t-if="journal_type == 'purchase'" type="object" name="open_action" context="{'use_domain': [('type', '=', 'in_invoice')]}">Bills</a>
|
||||||
<span t-if="journal_type == 'purchase'">Bills</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a type="object" name="open_action" context="{'use_domain': [('type','in',('out_refund','in_refund'))], 'invoice_type': 'refund'}">
|
<a t-if="journal_type == 'sale'" type="object" name="open_action" context="{'use_domain': [('type', '=', 'out_refund')], 'invoice_type': 'refund'}">Credit Notes</a>
|
||||||
<span t-if="journal_type == 'sale'">Credit Notes</span>
|
<a t-if="journal_type == 'purchase'" type="object" name="open_action" context="{'use_domain': [('type', '=', 'in_refund')], 'invoice_type': 'refund'}">Credit Notes</a>
|
||||||
<span t-if="journal_type == 'purchase'">Credit Notes</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a type="object" name="action_open_reconcile">Payments Matching</a>
|
<a type="object" name="action_open_reconcile">Payments Matching</a>
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
id="action_report_financial"
|
id="action_report_financial"
|
||||||
model="account.financial.report"
|
model="account.financial.report"
|
||||||
string="Financial report"
|
string="Financial report"
|
||||||
|
menu="False"
|
||||||
report_type="qweb-pdf"
|
report_type="qweb-pdf"
|
||||||
name="account.report_financial"
|
name="account.report_financial"
|
||||||
file="account.report_financial"
|
file="account.report_financial"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<field name="code" placeholder="code"/>
|
<field name="code" placeholder="code"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="user_type_id" widget="selection"/>
|
<field name="user_type_id" widget="selection"/>
|
||||||
<field name="tax_ids" widget="many2many_tags"/>
|
<field name="tax_ids" widget="many2many_tags" domain="[('company_id','=',company_id)]"/>
|
||||||
<field name="tag_ids" widget="many2many_tags" domain="[('applicability', '!=', 'taxes')]" context="{'default_applicability': 'accounts'}" options="{'no_create_edit': True}"/>
|
<field name="tag_ids" widget="many2many_tags" domain="[('applicability', '!=', 'taxes')]" context="{'default_applicability': 'accounts'}" options="{'no_create_edit': True}"/>
|
||||||
<field name="group_id"/>
|
<field name="group_id"/>
|
||||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class AccountInvoiceLine(models.Model):
|
|||||||
@api.onchange('product_id')
|
@api.onchange('product_id')
|
||||||
def _onchange_product_id(self):
|
def _onchange_product_id(self):
|
||||||
res = super(AccountInvoiceLine, self)._onchange_product_id()
|
res = super(AccountInvoiceLine, self)._onchange_product_id()
|
||||||
rec = self.env['account.analytic.default'].account_get(self.product_id.id, self.invoice_id.partner_id.id, self.env.uid,
|
rec = self.env['account.analytic.default'].account_get(self.product_id.id, self.invoice_id.commercial_partner_id.id, self.env.uid,
|
||||||
fields.Date.today(), company_id=self.company_id.id)
|
fields.Date.today(), company_id=self.company_id.id)
|
||||||
self.account_analytic_id = rec.analytic_id.id
|
self.account_analytic_id = rec.analytic_id.id
|
||||||
self.analytic_tag_ids = rec.analytic_tag_ids.ids
|
self.analytic_tag_ids = rec.analytic_tag_ids.ids
|
||||||
@@ -75,7 +75,7 @@ class AccountInvoiceLine(models.Model):
|
|||||||
def _set_additional_fields(self, invoice):
|
def _set_additional_fields(self, invoice):
|
||||||
if not self.account_analytic_id or not self.analytic_tag_ids:
|
if not self.account_analytic_id or not self.analytic_tag_ids:
|
||||||
rec = self.env['account.analytic.default'].account_get(
|
rec = self.env['account.analytic.default'].account_get(
|
||||||
self.product_id.id, self.invoice_id.partner_id.id, self.env.uid,
|
self.product_id.id, self.invoice_id.commercial_partner_id.id, self.env.uid,
|
||||||
fields.Date.today(), company_id=self.company_id.id)
|
fields.Date.today(), company_id=self.company_id.id)
|
||||||
if rec:
|
if rec:
|
||||||
if self.account_analytic_id:
|
if self.account_analytic_id:
|
||||||
|
|||||||
@@ -11,3 +11,12 @@ def uninstall_hook(cr, registry):
|
|||||||
('module', '=', 'account')
|
('module', '=', 'account')
|
||||||
]).mapped('res_id')
|
]).mapped('res_id')
|
||||||
env['ir.ui.menu'].browse(res_ids).update({'active': False})
|
env['ir.ui.menu'].browse(res_ids).update({'active': False})
|
||||||
|
|
||||||
|
|
||||||
|
def post_init_hook(cr, registry):
|
||||||
|
env = Environment(cr, SUPERUSER_ID, {})
|
||||||
|
res_ids = env['ir.model.data'].search([
|
||||||
|
('model', '=', 'ir.ui.menu'),
|
||||||
|
('module', '=', 'account'),
|
||||||
|
]).mapped('res_id')
|
||||||
|
env['ir.ui.menu'].browse(res_ids).update({'active': True})
|
||||||
|
|||||||
@@ -28,4 +28,5 @@ You could use this simplified accounting in case you work with an (external) acc
|
|||||||
],
|
],
|
||||||
'application': True,
|
'application': True,
|
||||||
'uninstall_hook': 'uninstall_hook',
|
'uninstall_hook': 'uninstall_hook',
|
||||||
|
'post_init_hook': 'post_init_hook',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ from . import res_config_settings
|
|||||||
from . import ir_http
|
from . import ir_http
|
||||||
from . import res_partner
|
from . import res_partner
|
||||||
from . import res_users
|
from . import res_users
|
||||||
|
from . import ir_model_fields
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details
|
||||||
|
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
|
# This is a nasty hack, targeted for V11 only
|
||||||
|
class IrModelFields(models.Model):
|
||||||
|
_inherit = 'ir.model.fields'
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
# Prevent the deletion of some `shared` fields... -_-
|
||||||
|
self = self.filtered(
|
||||||
|
lambda rec: not (
|
||||||
|
rec.model == 'res.config.settings'
|
||||||
|
and rec.name == 'auth_signup_uninvited'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return super(IrModelFields, self).unlink()
|
||||||
@@ -63,7 +63,7 @@ class BaseGengoTranslations(models.TransientModel):
|
|||||||
'view_type': 'form',
|
'view_type': 'form',
|
||||||
'view_mode': 'form',
|
'view_mode': 'form',
|
||||||
'res_model': 'res.config.settings',
|
'res_model': 'res.config.settings',
|
||||||
'target': 'current',
|
'target': 'inline',
|
||||||
'context': {'module' : 'general_settings'},
|
'context': {'module' : 'general_settings'},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ _map_iban_template = {
|
|||||||
'bg': 'BGkk BBBB SSSS DDCC CCCC CC', # Bulgaria
|
'bg': 'BGkk BBBB SSSS DDCC CCCC CC', # Bulgaria
|
||||||
'bh': 'BHkk BBBB CCCC CCCC CCCC CC', # Bahrain
|
'bh': 'BHkk BBBB CCCC CCCC CCCC CC', # Bahrain
|
||||||
'br': 'BRkk BBBB BBBB SSSS SCCC CCCC CCCT N', # Brazil
|
'br': 'BRkk BBBB BBBB SSSS SCCC CCCC CCCT N', # Brazil
|
||||||
|
'by': 'BYkk BBBB AAAA CCCC CCCC CCCC CCCC', # Belarus
|
||||||
'ch': 'CHkk BBBB BCCC CCCC CCCC C', # Switzerland
|
'ch': 'CHkk BBBB BCCC CCCC CCCC C', # Switzerland
|
||||||
'cr': 'CRkk BBBC CCCC CCCC CCCC C', # Costa Rica
|
'cr': 'CRkk BBBC CCCC CCCC CCCC C', # Costa Rica
|
||||||
'cy': 'CYkk BBBS SSSS CCCC CCCC CCCC CCCC', # Cyprus
|
'cy': 'CYkk BBBS SSSS CCCC CCCC CCCC CCCC', # Cyprus
|
||||||
|
|||||||
@@ -302,7 +302,8 @@ class Import(models.TransientModel):
|
|||||||
try:
|
try:
|
||||||
thousand_separator = decimal_separator = False
|
thousand_separator = decimal_separator = False
|
||||||
for val in preview_values:
|
for val in preview_values:
|
||||||
if val == '':
|
val = val.strip()
|
||||||
|
if not val:
|
||||||
continue
|
continue
|
||||||
# value might have the currency symbol left or right from the value
|
# value might have the currency symbol left or right from the value
|
||||||
val = self._remove_currency_symbol(val)
|
val = self._remove_currency_symbol(val)
|
||||||
@@ -587,6 +588,7 @@ class Import(models.TransientModel):
|
|||||||
thousand_separator = options.get('float_thousand_separator', ' ')
|
thousand_separator = options.get('float_thousand_separator', ' ')
|
||||||
decimal_separator = options.get('float_decimal_separator', '.')
|
decimal_separator = options.get('float_decimal_separator', '.')
|
||||||
for line in data:
|
for line in data:
|
||||||
|
line[index] = line[index].strip()
|
||||||
if not line[index]:
|
if not line[index]:
|
||||||
continue
|
continue
|
||||||
line[index] = line[index].replace(thousand_separator, '').replace(decimal_separator, '.')
|
line[index] = line[index].replace(thousand_separator, '').replace(decimal_separator, '.')
|
||||||
|
|||||||
@@ -145,12 +145,13 @@ def _is_studio_custom(path):
|
|||||||
Returns True if any of the records contains a context with the key
|
Returns True if any of the records contains a context with the key
|
||||||
studio in it, False if none of the records do
|
studio in it, False if none of the records do
|
||||||
"""
|
"""
|
||||||
path = os.path.join(path, 'data')
|
filepaths = []
|
||||||
filenames = next(iter(os.walk(path)))[2]
|
for level in os.walk(path):
|
||||||
filenames = [f for f in filenames if f.lower().endswith('.xml')]
|
filepaths += [os.path.join(level[0], fn) for fn in level[2]]
|
||||||
|
filepaths = [fp for fp in filepaths if fp.lower().endswith('.xml')]
|
||||||
|
|
||||||
for filename in filenames:
|
for fp in filepaths:
|
||||||
root = lxml.etree.parse(os.path.join(path, filename)).getroot()
|
root = lxml.etree.parse(fp).getroot()
|
||||||
|
|
||||||
for record in root:
|
for record in root:
|
||||||
# there might not be a context if it's a non-studio module
|
# there might not be a context if it's a non-studio module
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
crm_alias_prefix = fields.Char('Default Alias Name for Leads')
|
crm_alias_prefix = fields.Char('Default Alias Name for Leads')
|
||||||
generate_lead_from_alias = fields.Boolean('Manual Assignation of Emails', config_parameter='crm.generate_lead_from_alias')
|
generate_lead_from_alias = fields.Boolean('Manual Assignation of Emails', config_parameter='crm.generate_lead_from_alias')
|
||||||
group_use_lead = fields.Boolean(string="Leads", implied_group='crm.group_use_lead')
|
group_use_lead = fields.Boolean(string="Leads", implied_group='crm.group_use_lead')
|
||||||
module_crm_phone_validation = fields.Boolean("Phone Validation")
|
module_crm_phone_validation = fields.Boolean("Phone Formatting")
|
||||||
module_web_clearbit = fields.Boolean("Customer Autocomplete")
|
module_web_clearbit = fields.Boolean("Customer Autocomplete")
|
||||||
|
|
||||||
def _find_default_lead_alias_id(self):
|
def _find_default_lead_alias_id(self):
|
||||||
@@ -33,7 +33,7 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
|
|
||||||
@api.onchange('generate_lead_from_alias')
|
@api.onchange('generate_lead_from_alias')
|
||||||
def _onchange_generate_lead_from_alias(self):
|
def _onchange_generate_lead_from_alias(self):
|
||||||
self.crm_alias_prefix = 'info' if self.generate_lead_from_alias else False
|
self.crm_alias_prefix = (self.crm_alias_prefix or 'info') if self.generate_lead_from_alias else False
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_values(self):
|
def get_values(self):
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<label for="module_crm_phone_validation"/>
|
<label for="module_crm_phone_validation"/>
|
||||||
<div class="text-muted" id="module_crm_phone_validation">
|
<div class="text-muted" id="module_crm_phone_validation">
|
||||||
Formats phone numbers
|
Format phone numbers based on national conventions
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -210,8 +210,11 @@ class DeliveryCarrier(models.Model):
|
|||||||
carrier.product_id.list_price = carrier.fixed_price
|
carrier.product_id.list_price = carrier.fixed_price
|
||||||
|
|
||||||
def fixed_rate_shipment(self, order):
|
def fixed_rate_shipment(self, order):
|
||||||
|
price = self.fixed_price
|
||||||
|
if self.company_id.currency_id.id != order.currency_id.id:
|
||||||
|
price = self.env['res.currency']._compute(self.company_id.currency_id, order.currency_id, price)
|
||||||
return {'success': True,
|
return {'success': True,
|
||||||
'price': self.fixed_price,
|
'price': price,
|
||||||
'error_message': False,
|
'error_message': False,
|
||||||
'warning_message': False}
|
'warning_message': False}
|
||||||
|
|
||||||
|
|||||||
@@ -84,18 +84,6 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:15px 20px 0px 20px;">
|
<td style="padding:15px 20px 0px 20px;">
|
||||||
<table style="width:100%;border-top:1px solid #e1e1e1;">
|
|
||||||
<tr>
|
|
||||||
<td style="padding:25px 0px; text-align:center;">
|
|
||||||
<p>
|
|
||||||
Don't forget to <strong>add it to your calendar</strong>:
|
|
||||||
</p>
|
|
||||||
<a href="https://www.google.com/calendar/render?action=TEMPLATE&text=${object.event_id.name}&dates=${date_begin}/${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Google</a>
|
|
||||||
<a href="https://bay02.calendar.live.com/calendar/calendar.aspx?rru=addevent&summary=${object.event_id.name}&dtstart=${date_begin}&dtend=${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Outlook</a>
|
|
||||||
<a href="https://calendar.yahoo.com/?v=60&view=d&type=20&title=${object.event_id.name}&in_loc=${location}&st=${format_tz(object.event_id.date_begin, tz='UTC', format='%Y%m%dT%H%M%S')}&et=${format_tz(object.event_id.date_end, tz='UTC', format='%Y%m%dT%H%M%S')}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Yahoo</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<table style="width:100%;border-top:1px solid #e1e1e1;">
|
<table style="width:100%;border-top:1px solid #e1e1e1;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:25px 0px;vertical-align:top;">
|
<td style="padding:25px 0px;vertical-align:top;">
|
||||||
@@ -165,6 +153,16 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
% endif
|
% endif
|
||||||
|
<table style="width:100%;border-top:1px solid #e1e1e1;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding:25px 0px;">
|
||||||
|
<strong>Add this event to your calendar</strong>
|
||||||
|
<a href="https://www.google.com/calendar/render?action=TEMPLATE&text=${object.event_id.name}&dates=${date_begin}/${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Google</a>
|
||||||
|
<a href="https://bay02.calendar.live.com/calendar/calendar.aspx?rru=addevent&summary=${object.event_id.name}&dtstart=${date_begin}&dtend=${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Outlook</a>
|
||||||
|
<a href="https://calendar.yahoo.com/?v=60&view=d&type=20&title=${object.event_id.name}&in_loc=${location}&st=${format_tz(object.event_id.date_begin, tz='UTC', format='%Y%m%dT%H%M%S')}&et=${format_tz(object.event_id.date_end, tz='UTC', format='%Y%m%dT%H%M%S')}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Yahoo</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -247,9 +247,11 @@ class FleetVehicle(models.Model):
|
|||||||
@return: the costs log view
|
@return: the costs log view
|
||||||
"""
|
"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
copy_context = dict(self.env.context)
|
||||||
|
copy_context.pop('group_by', None)
|
||||||
res = self.env['ir.actions.act_window'].for_xml_id('fleet', 'fleet_vehicle_costs_action')
|
res = self.env['ir.actions.act_window'].for_xml_id('fleet', 'fleet_vehicle_costs_action')
|
||||||
res.update(
|
res.update(
|
||||||
context=dict(self.env.context, default_vehicle_id=self.id, search_default_parent_false=True),
|
context=dict(copy_context, default_vehicle_id=self.id, search_default_parent_false=True),
|
||||||
domain=[('vehicle_id', '=', self.id)]
|
domain=[('vehicle_id', '=', self.id)]
|
||||||
)
|
)
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -190,7 +190,8 @@ class HrPayslip(models.Model):
|
|||||||
leave_time = (interval[1] - interval[0]).seconds / 3600
|
leave_time = (interval[1] - interval[0]).seconds / 3600
|
||||||
current_leave_struct['number_of_hours'] += leave_time
|
current_leave_struct['number_of_hours'] += leave_time
|
||||||
work_hours = contract.employee_id.get_day_work_hours_count(interval[0].date(), calendar=contract.resource_calendar_id)
|
work_hours = contract.employee_id.get_day_work_hours_count(interval[0].date(), calendar=contract.resource_calendar_id)
|
||||||
current_leave_struct['number_of_days'] += leave_time / work_hours
|
if work_hours:
|
||||||
|
current_leave_struct['number_of_days'] += leave_time / work_hours
|
||||||
|
|
||||||
# compute worked days
|
# compute worked days
|
||||||
work_data = contract.employee_id.get_work_days_data(day_from, day_to, calendar=contract.resource_calendar_id)
|
work_data = contract.employee_id.get_work_days_data(day_from, day_to, calendar=contract.resource_calendar_id)
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class HrContract(models.Model):
|
|||||||
+ 12.0 * contract.internet \
|
+ 12.0 * contract.internet \
|
||||||
+ 12.0 * (contract.mobile + contract.mobile_plus) \
|
+ 12.0 * (contract.mobile + contract.mobile_plus) \
|
||||||
+ 12.0 * contract.transport_employer_cost \
|
+ 12.0 * contract.transport_employer_cost \
|
||||||
+ (1.326 / 1.05 * 12.0) * contract.commission_on_target \
|
+ contract.warrants_cost \
|
||||||
+ 220.0 * contract.meal_voucher_paid_by_employer
|
+ 220.0 * contract.meal_voucher_paid_by_employer
|
||||||
contract.final_yearly_costs = yearly_cost / (1.0 - (contract.holidays - 20.0) / 231.0)
|
contract.final_yearly_costs = yearly_cost / (1.0 - (contract.holidays - 20.0) / 231.0)
|
||||||
contract.wage = contract._get_gross_from_employer_costs(contract.final_yearly_costs)
|
contract.wage = contract._get_gross_from_employer_costs(contract.final_yearly_costs)
|
||||||
@@ -116,8 +116,8 @@ class HrContract(models.Model):
|
|||||||
@api.depends('commission_on_target')
|
@api.depends('commission_on_target')
|
||||||
def _compute_warrants_cost(self):
|
def _compute_warrants_cost(self):
|
||||||
for contract in self:
|
for contract in self:
|
||||||
contract.warrants_cost = contract.commission_on_target * 1.05 * 12.0
|
contract.warrants_cost = contract.commission_on_target * 1.326 * 1.05 * 12.0
|
||||||
contract.warrant_value_employee = contract.commission_on_target * (1.00 - 0.535) * 12.0
|
contract.warrant_value_employee = contract.commission_on_target * 1.326 * (1.00 - 0.535) * 12.0
|
||||||
|
|
||||||
@api.depends('wage', 'fuel_card', 'representation_fees', 'transport_employer_cost',
|
@api.depends('wage', 'fuel_card', 'representation_fees', 'transport_employer_cost',
|
||||||
'internet', 'mobile', 'mobile_plus')
|
'internet', 'mobile', 'mobile_plus')
|
||||||
|
|||||||
@@ -2836,98 +2836,98 @@
|
|||||||
<record id="pcg_501" model="account.account.template">
|
<record id="pcg_501" model="account.account.template">
|
||||||
<field name="name">Valeurs mobilières de placement - Parts dans entreprises liées</field>
|
<field name="name">Valeurs mobilières de placement - Parts dans entreprises liées</field>
|
||||||
<field name="code">501</field>
|
<field name="code">501</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_502" model="account.account.template">
|
<record id="pcg_502" model="account.account.template">
|
||||||
<field name="name">Valeurs mobilières de placement - Actions propres</field>
|
<field name="name">Valeurs mobilières de placement - Actions propres</field>
|
||||||
<field name="code">502</field>
|
<field name="code">502</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5031" model="account.account.template">
|
<record id="pcg_5031" model="account.account.template">
|
||||||
<field name="name">Valeurs mobilières de placement - Titres cotés</field>
|
<field name="name">Valeurs mobilières de placement - Titres cotés</field>
|
||||||
<field name="code">5031</field>
|
<field name="code">5031</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5035" model="account.account.template">
|
<record id="pcg_5035" model="account.account.template">
|
||||||
<field name="name">Valeurs mobilières de placement - Titres non cotés</field>
|
<field name="name">Valeurs mobilières de placement - Titres non cotés</field>
|
||||||
<field name="code">5035</field>
|
<field name="code">5035</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_504" model="account.account.template">
|
<record id="pcg_504" model="account.account.template">
|
||||||
<field name="name">Valeurs mobilières de placement - Autres titres conférant un droit de propriété</field>
|
<field name="name">Valeurs mobilières de placement - Autres titres conférant un droit de propriété</field>
|
||||||
<field name="code">504</field>
|
<field name="code">504</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_505" model="account.account.template">
|
<record id="pcg_505" model="account.account.template">
|
||||||
<field name="name">Obligations et bons émis par la société et rachetés par elle</field>
|
<field name="name">Obligations et bons émis par la société et rachetés par elle</field>
|
||||||
<field name="code">505</field>
|
<field name="code">505</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5061" model="account.account.template">
|
<record id="pcg_5061" model="account.account.template">
|
||||||
<field name="name">Obligations cotés</field>
|
<field name="name">Obligations cotés</field>
|
||||||
<field name="code">5061</field>
|
<field name="code">5061</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5065" model="account.account.template">
|
<record id="pcg_5065" model="account.account.template">
|
||||||
<field name="name">Obligations non cotés</field>
|
<field name="name">Obligations non cotés</field>
|
||||||
<field name="code">5065</field>
|
<field name="code">5065</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_507" model="account.account.template">
|
<record id="pcg_507" model="account.account.template">
|
||||||
<field name="name">Bons du Trésor et bons de caisse à court terme</field>
|
<field name="name">Bons du Trésor et bons de caisse à court terme</field>
|
||||||
<field name="code">507</field>
|
<field name="code">507</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5081" model="account.account.template">
|
<record id="pcg_5081" model="account.account.template">
|
||||||
<field name="name">Autres valeurs mobilières de placement</field>
|
<field name="name">Autres valeurs mobilières de placement</field>
|
||||||
<field name="code">5081</field>
|
<field name="code">5081</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5082" model="account.account.template">
|
<record id="pcg_5082" model="account.account.template">
|
||||||
<field name="name">Bons de souscription</field>
|
<field name="name">Bons de souscription</field>
|
||||||
<field name="code">5082</field>
|
<field name="code">5082</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5088" model="account.account.template">
|
<record id="pcg_5088" model="account.account.template">
|
||||||
<field name="name">Intérêts courus sur obligations, bons et valeurs assimilées</field>
|
<field name="name">Intérêts courus sur obligations, bons et valeurs assimilées</field>
|
||||||
<field name="code">5088</field>
|
<field name="code">5088</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_509" model="account.account.template">
|
<record id="pcg_509" model="account.account.template">
|
||||||
<field name="name">Versements restant à effectuer sur valeurs mobilières de placement non libérées</field>
|
<field name="name">Versements restant à effectuer sur valeurs mobilières de placement non libérées</field>
|
||||||
<field name="code">509</field>
|
<field name="code">509</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5111" model="account.account.template">
|
<record id="pcg_5111" model="account.account.template">
|
||||||
<field name="name">Coupons échus à l'encaissement</field>
|
<field name="name">Coupons échus à l'encaissement</field>
|
||||||
<field name="code">5111</field>
|
<field name="code">5111</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="reconcile" eval="True"/>
|
<field name="reconcile" eval="True"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
@@ -2935,7 +2935,7 @@
|
|||||||
<record id="pcg_5112" model="account.account.template">
|
<record id="pcg_5112" model="account.account.template">
|
||||||
<field name="name">Chèques à encaisser</field>
|
<field name="name">Chèques à encaisser</field>
|
||||||
<field name="code">5112</field>
|
<field name="code">5112</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="reconcile" eval="True"/>
|
<field name="reconcile" eval="True"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
@@ -2943,7 +2943,7 @@
|
|||||||
<record id="pcg_5113" model="account.account.template">
|
<record id="pcg_5113" model="account.account.template">
|
||||||
<field name="name">Effets à l'encaissement</field>
|
<field name="name">Effets à l'encaissement</field>
|
||||||
<field name="code">5113</field>
|
<field name="code">5113</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="reconcile" eval="True"/>
|
<field name="reconcile" eval="True"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
@@ -2951,7 +2951,7 @@
|
|||||||
<record id="pcg_5114" model="account.account.template">
|
<record id="pcg_5114" model="account.account.template">
|
||||||
<field name="name">Effets à l'escompte</field>
|
<field name="name">Effets à l'escompte</field>
|
||||||
<field name="code">5114</field>
|
<field name="code">5114</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="reconcile" eval="True"/>
|
<field name="reconcile" eval="True"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
@@ -2980,56 +2980,56 @@
|
|||||||
<record id="pcg_516" model="account.account.template">
|
<record id="pcg_516" model="account.account.template">
|
||||||
<field name="name">Sociétés de bourse</field>
|
<field name="name">Sociétés de bourse</field>
|
||||||
<field name="code">516</field>
|
<field name="code">516</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_517" model="account.account.template">
|
<record id="pcg_517" model="account.account.template">
|
||||||
<field name="name">Autres organismes financiers</field>
|
<field name="name">Autres organismes financiers</field>
|
||||||
<field name="code">517</field>
|
<field name="code">517</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5181" model="account.account.template">
|
<record id="pcg_5181" model="account.account.template">
|
||||||
<field name="name">Intérêts courus à payer</field>
|
<field name="name">Intérêts courus à payer</field>
|
||||||
<field name="code">5181</field>
|
<field name="code">5181</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5188" model="account.account.template">
|
<record id="pcg_5188" model="account.account.template">
|
||||||
<field name="name">Intérêts courus à recevoir</field>
|
<field name="name">Intérêts courus à recevoir</field>
|
||||||
<field name="code">5188</field>
|
<field name="code">5188</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5191" model="account.account.template">
|
<record id="pcg_5191" model="account.account.template">
|
||||||
<field name="name">Concours bancaires courants - Crédit de mobilisation de créances commerciales (CMCC)</field>
|
<field name="name">Concours bancaires courants - Crédit de mobilisation de créances commerciales (CMCC)</field>
|
||||||
<field name="code">5191</field>
|
<field name="code">5191</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5193" model="account.account.template">
|
<record id="pcg_5193" model="account.account.template">
|
||||||
<field name="name">Concours bancaires courants - Mobilisation de créances nées à l'étranger</field>
|
<field name="name">Concours bancaires courants - Mobilisation de créances nées à l'étranger</field>
|
||||||
<field name="code">5193</field>
|
<field name="code">5193</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5198" model="account.account.template">
|
<record id="pcg_5198" model="account.account.template">
|
||||||
<field name="name">Concours bancaires courants - Intérêts courus sur concours bancaires courants</field>
|
<field name="name">Concours bancaires courants - Intérêts courus sur concours bancaires courants</field>
|
||||||
<field name="code">5198</field>
|
<field name="code">5198</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_52" model="account.account.template">
|
<record id="pcg_52" model="account.account.template">
|
||||||
<field name="name">Instruments de trésorerie</field>
|
<field name="name">Instruments de trésorerie</field>
|
||||||
<field name="code">52</field>
|
<field name="code">52</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
@@ -3064,35 +3064,35 @@
|
|||||||
<record id="pcg_54" model="account.account.template">
|
<record id="pcg_54" model="account.account.template">
|
||||||
<field name="name">Régies d'avances et accréditifs</field>
|
<field name="name">Régies d'avances et accréditifs</field>
|
||||||
<field name="code">54</field>
|
<field name="code">54</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5903" model="account.account.template">
|
<record id="pcg_5903" model="account.account.template">
|
||||||
<field name="name">Provisions pour dépréciation des actions</field>
|
<field name="name">Provisions pour dépréciation des actions</field>
|
||||||
<field name="code">5903</field>
|
<field name="code">5903</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5904" model="account.account.template">
|
<record id="pcg_5904" model="account.account.template">
|
||||||
<field name="name">Provisions pour dépréciation des autres titres conférant un droit de propriété</field>
|
<field name="name">Provisions pour dépréciation des autres titres conférant un droit de propriété</field>
|
||||||
<field name="code">5904</field>
|
<field name="code">5904</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5906" model="account.account.template">
|
<record id="pcg_5906" model="account.account.template">
|
||||||
<field name="name">Provisions pour dépréciation des obligations</field>
|
<field name="name">Provisions pour dépréciation des obligations</field>
|
||||||
<field name="code">5906</field>
|
<field name="code">5906</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="pcg_5908" model="account.account.template">
|
<record id="pcg_5908" model="account.account.template">
|
||||||
<field name="name">Provisions pour dépréciation des autres valeurs mobilières de placement et créances assimilées (provisions)</field>
|
<field name="name">Provisions pour dépréciation des autres valeurs mobilières de placement et créances assimilées (provisions)</field>
|
||||||
<field name="code">5908</field>
|
<field name="code">5908</field>
|
||||||
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
|
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
|
||||||
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
<field name="name">DIOT: 16%</field>
|
<field name="name">DIOT: 16%</field>
|
||||||
<field name="applicability">taxes</field>
|
<field name="applicability">taxes</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="tag_diot_16_imp" model="account.account.tag">
|
||||||
|
<field name="name">DIOT: 16% IMP</field>
|
||||||
|
<field name="applicability">taxes</field>
|
||||||
|
</record>
|
||||||
<record id="tag_diot_0" model="account.account.tag">
|
<record id="tag_diot_0" model="account.account.tag">
|
||||||
<field name="name">DIOT: 0%</field>
|
<field name="name">DIOT: 0%</field>
|
||||||
<field name="applicability">taxes</field>
|
<field name="applicability">taxes</field>
|
||||||
|
|||||||
@@ -5,18 +5,25 @@
|
|||||||
<!-- Smileys -->
|
<!-- Smileys -->
|
||||||
<record id="mail_smiley_normal_smile" model="mail.shortcode">
|
<record id="mail_smiley_normal_smile" model="mail.shortcode">
|
||||||
<field name="source">:)</field>
|
<field name="source">:)</field>
|
||||||
<field name="unicode_source">😊</field>
|
<field name="unicode_source">🙂</field>
|
||||||
<field name="substitution"><img src='/mail/static/src/img/smiley/normal_smile.png' alt='😊'/></field>
|
<field name="substitution"><img src='/mail/static/src/img/smiley/smile.png' alt='🙂'/></field>
|
||||||
<field name="description">Smile</field>
|
<field name="description">Smile</field>
|
||||||
<field name="shortcode_type">image</field>
|
<field name="shortcode_type">image</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="mail_smiley_smile" model="mail.shortcode">
|
<record id="mail_smiley_smile" model="mail.shortcode">
|
||||||
<field name="source">:D</field>
|
<field name="source">:D</field>
|
||||||
<field name="unicode_source">😄</field>
|
<field name="unicode_source">😀</field>
|
||||||
<field name="substitution"><img src='/mail/static/src/img/smiley/smile.png' alt='😄'/></field>
|
<field name="substitution"><img src='/mail/static/src/img/smiley/smile_open_mouth.png' alt='😀'/></field>
|
||||||
<field name="description">Smile with open mouth</field>
|
<field name="description">Smile with open mouth</field>
|
||||||
<field name="shortcode_type">image</field>
|
<field name="shortcode_type">image</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="mail_smiley_smile_eyes" model="mail.shortcode">
|
||||||
|
<field name="source">^^</field>
|
||||||
|
<field name="unicode_source">😊</field>
|
||||||
|
<field name="substitution"><img src='/mail/static/src/img/smiley/normal_smile.png' alt='😊'/></field>
|
||||||
|
<field name="description">Smile with with smiling eyes</field>
|
||||||
|
<field name="shortcode_type">image</field>
|
||||||
|
</record>
|
||||||
<record id="mail_smiley_laugh" model="mail.shortcode">
|
<record id="mail_smiley_laugh" model="mail.shortcode">
|
||||||
<field name="source">xD</field>
|
<field name="source">xD</field>
|
||||||
<field name="unicode_source">😆</field>
|
<field name="unicode_source">😆</field>
|
||||||
@@ -25,7 +32,7 @@
|
|||||||
<field name="shortcode_type">image</field>
|
<field name="shortcode_type">image</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="mail_smiley_tears_of_joy" model="mail.shortcode">
|
<record id="mail_smiley_tears_of_joy" model="mail.shortcode">
|
||||||
<field name="source">:-)</field>
|
<field name="source">:joy</field>
|
||||||
<field name="unicode_source">😂</field>
|
<field name="unicode_source">😂</field>
|
||||||
<field name="substitution"><img src='/mail/static/src/img/smiley/tears_of_joy.png' alt='😂'/></field>
|
<field name="substitution"><img src='/mail/static/src/img/smiley/tears_of_joy.png' alt='😂'/></field>
|
||||||
<field name="description">Tears of joy</field>
|
<field name="description">Tears of joy</field>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class Channel(models.Model):
|
|||||||
('channel', 'Channel')],
|
('channel', 'Channel')],
|
||||||
'Channel Type', default='channel')
|
'Channel Type', default='channel')
|
||||||
description = fields.Text('Description')
|
description = fields.Text('Description')
|
||||||
uuid = fields.Char('UUID', size=50, index=True, default=lambda self: '%s' % uuid.uuid4())
|
uuid = fields.Char('UUID', size=50, index=True, default=lambda self: '%s' % uuid.uuid4(), copy=False)
|
||||||
email_send = fields.Boolean('Send messages by email', default=False)
|
email_send = fields.Boolean('Send messages by email', default=False)
|
||||||
# multi users channel
|
# multi users channel
|
||||||
channel_last_seen_partner_ids = fields.One2many('mail.channel.partner', 'channel_id', string='Last Seen')
|
channel_last_seen_partner_ids = fields.One2many('mail.channel.partner', 'channel_id', string='Last Seen')
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
@@ -591,7 +591,7 @@ var BasicComposer = Widget.extend({
|
|||||||
*/
|
*/
|
||||||
_onKeydown: function (event) {
|
_onKeydown: function (event) {
|
||||||
switch(event.which) {
|
switch(event.which) {
|
||||||
// UP, DOWN: prevent moving cursor if navigation in mention propositions
|
// UP, DOWN, TAB: prevent moving cursor if navigation in mention propositions
|
||||||
case $.ui.keyCode.UP:
|
case $.ui.keyCode.UP:
|
||||||
case $.ui.keyCode.DOWN:
|
case $.ui.keyCode.DOWN:
|
||||||
case $.ui.keyCode.TAB:
|
case $.ui.keyCode.TAB:
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ var DocumentViewer = Widget.extend({
|
|||||||
'click .o_viewer_video': '_onVideoClicked',
|
'click .o_viewer_video': '_onVideoClicked',
|
||||||
'click .move_next': '_onNext',
|
'click .move_next': '_onNext',
|
||||||
'click .move_previous': '_onPrevious',
|
'click .move_previous': '_onPrevious',
|
||||||
|
'click .o_rotate': '_onRotate',
|
||||||
'click .o_zoom_in': '_onZoomIn',
|
'click .o_zoom_in': '_onZoomIn',
|
||||||
'click .o_zoom_out': '_onZoomOut',
|
'click .o_zoom_out': '_onZoomOut',
|
||||||
'click .o_close_btn, .o_viewer_img_wrapper': '_onClose',
|
'click .o_close_btn, .o_viewer_img_wrapper': '_onClose',
|
||||||
@@ -103,6 +104,30 @@ var DocumentViewer = Widget.extend({
|
|||||||
this.$('.o_viewer_img').load(_.bind(this._onImageLoaded, this));
|
this.$('.o_viewer_img').load(_.bind(this._onImageLoaded, this));
|
||||||
this._reset();
|
this._reset();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Get CSS transform property based on scale and angle
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {float} scale
|
||||||
|
* @param {float} angle
|
||||||
|
*/
|
||||||
|
_getTransform: function(scale, angle) {
|
||||||
|
return 'scale3d(' + scale + ', ' + scale + ', 1) rotate(' + angle + 'deg)'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Rotate image clockwise by provided angle
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {float} angle
|
||||||
|
*/
|
||||||
|
_rotate: function (angle) {
|
||||||
|
this._reset();
|
||||||
|
var new_angle = (this.angle || 0) + angle;
|
||||||
|
this.$('.o_viewer_img').css('transform', this._getTransform(this.scale, new_angle));
|
||||||
|
this.$('.o_viewer_img').css('max-width', new_angle % 180 !== 0 ? $(document).height() : '100%');
|
||||||
|
this.$('.o_viewer_img').css('max-height', new_angle % 180 !== 0 ? $(document).width() : '100%');
|
||||||
|
this.angle = new_angle;
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Zoom in/out image by provided scale
|
* Zoom in/out image by provided scale
|
||||||
*
|
*
|
||||||
@@ -111,7 +136,7 @@ var DocumentViewer = Widget.extend({
|
|||||||
*/
|
*/
|
||||||
_zoom: function (scale) {
|
_zoom: function (scale) {
|
||||||
if (scale > 0.5) {
|
if (scale > 0.5) {
|
||||||
this.$('.o_viewer_img').css('transform', 'scale3d(' + scale + ', ' + scale + ', 1)');
|
this.$('.o_viewer_img').css('transform', this._getTransform(scale, this.angle || 0));
|
||||||
this.scale = scale;
|
this.scale = scale;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -280,6 +305,14 @@ var DocumentViewer = Widget.extend({
|
|||||||
videoElement.pause();
|
videoElement.pause();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {MouseEvent} e
|
||||||
|
*/
|
||||||
|
_onRotate: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this._rotate(90);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {MouseEvent} e
|
* @param {MouseEvent} e
|
||||||
|
|||||||
@@ -264,6 +264,7 @@
|
|||||||
img {
|
img {
|
||||||
width: @o-mail-attachment-image-size;
|
width: @o-mail-attachment-image-size;
|
||||||
height: @o-mail-attachment-image-size;
|
height: @o-mail-attachment-image-size;
|
||||||
|
image-orientation: from-image; // Only supported in Firefox
|
||||||
}
|
}
|
||||||
.o_image_overlay{
|
.o_image_overlay{
|
||||||
display: none;
|
display: none;
|
||||||
@@ -365,6 +366,9 @@
|
|||||||
.o-flex-display();
|
.o-flex-display();
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
img {
|
||||||
|
image-orientation: from-image; // Only supported in Firefox
|
||||||
|
}
|
||||||
img, video {
|
img, video {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
|||||||
@@ -50,6 +50,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div t-if="widget.activeAttachment.type == 'image'" class="o_viewer_toolbar">
|
<div t-if="widget.activeAttachment.type == 'image'" class="o_viewer_toolbar">
|
||||||
<ul class="list-inline">
|
<ul class="list-inline">
|
||||||
|
<li>
|
||||||
|
<a class="o_rotate" href="#">
|
||||||
|
<i class="fa fa-2x fa-repeat"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="o_zoom_out" href="#">
|
<a class="o_zoom_out" href="#">
|
||||||
<i class="fa fa-2x fa-search-minus"></i>
|
<i class="fa fa-2x fa-search-minus"></i>
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ class PaymentAcquirer(models.Model):
|
|||||||
help="Capture the amount from Odoo, when the delivery is completed.")
|
help="Capture the amount from Odoo, when the delivery is completed.")
|
||||||
# Formerly associated to `generate_and_pay_invoice` option from auto_confirm
|
# Formerly associated to `generate_and_pay_invoice` option from auto_confirm
|
||||||
journal_id = fields.Many2one(
|
journal_id = fields.Many2one(
|
||||||
'account.journal', 'Payment Journal', domain=[('type', '=', 'bank')],
|
'account.journal', 'Payment Journal', domain=[('type', 'in', ['bank', 'cash'])],
|
||||||
default=lambda self: self.env['account.journal'].search([('type', '=', 'bank')], limit=1),
|
default=lambda self: self.env['account.journal'].search([('type', 'in', ['bank', 'cash'])], limit=1),
|
||||||
help="""Payments will be registered into this journal. If you get paid straight on your bank account,
|
help="""Payments will be registered into this journal. If you get paid straight on your bank account,
|
||||||
select your bank account. If you get paid in batch for several transactions, create a specific
|
select your bank account. If you get paid in batch for several transactions, create a specific
|
||||||
payment journal for this payment acquirer to easily manage the bank reconciliation. You hold
|
payment journal for this payment acquirer to easily manage the bank reconciliation. You hold
|
||||||
@@ -200,22 +200,13 @@ class PaymentAcquirer(models.Model):
|
|||||||
@api.model
|
@api.model
|
||||||
def create(self, vals):
|
def create(self, vals):
|
||||||
image_resize_images(vals)
|
image_resize_images(vals)
|
||||||
vals = self._check_journal_id(vals)
|
|
||||||
return super(PaymentAcquirer, self).create(vals)
|
return super(PaymentAcquirer, self).create(vals)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
image_resize_images(vals)
|
image_resize_images(vals)
|
||||||
vals = self._check_journal_id(vals)
|
|
||||||
return super(PaymentAcquirer, self).write(vals)
|
return super(PaymentAcquirer, self).write(vals)
|
||||||
|
|
||||||
def _check_journal_id(self, vals):
|
|
||||||
if not vals.get('journal_id', False):
|
|
||||||
default_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1)
|
|
||||||
if default_journal:
|
|
||||||
vals.update({'journal_id': default_journal.id})
|
|
||||||
return vals
|
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def toggle_website_published(self):
|
def toggle_website_published(self):
|
||||||
self.write({'website_published': not self.website_published})
|
self.write({'website_published': not self.website_published})
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
</page>
|
</page>
|
||||||
<page string="Configuration">
|
<page string="Configuration">
|
||||||
<group name="acquirer_config">
|
<group name="acquirer_config">
|
||||||
<field name="journal_id" context="{'default_type': 'bank'}"/>
|
<field name="journal_id" context="{'default_type': 'bank'}" required="1"/>
|
||||||
<field name="capture_manually" attrs="{'invisible': [('authorize_implemented', '=', False)]}"/>
|
<field name="capture_manually" attrs="{'invisible': [('authorize_implemented', '=', False)]}"/>
|
||||||
<field name="save_token" widget="radio" attrs="{'invisible': ['|', ('token_implemented', '=', False), ('payment_flow', '=', 's2s')]}"/>
|
<field name="save_token" widget="radio" attrs="{'invisible': ['|', ('token_implemented', '=', False), ('payment_flow', '=', 's2s')]}"/>
|
||||||
<field name="fees_active" attrs="{'invisible': [('fees_implemented', '=', False)]}"/>
|
<field name="fees_active" attrs="{'invisible': [('fees_implemented', '=', False)]}"/>
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ from odoo import api, fields, models, _
|
|||||||
from odoo.addons.payment.models.payment_acquirer import ValidationError
|
from odoo.addons.payment.models.payment_acquirer import ValidationError
|
||||||
from odoo.addons.payment_ogone.controllers.main import OgoneController
|
from odoo.addons.payment_ogone.controllers.main import OgoneController
|
||||||
from odoo.addons.payment_ogone.data import ogone
|
from odoo.addons.payment_ogone.data import ogone
|
||||||
from odoo.tools import float_round, DEFAULT_SERVER_DATE_FORMAT
|
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, ustr
|
||||||
from odoo.tools.float_utils import float_compare, float_repr
|
from odoo.tools.float_utils import float_compare, float_repr, float_round
|
||||||
from odoo.tools.safe_eval import safe_eval
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -476,7 +475,7 @@ class PaymentTxOgone(models.Model):
|
|||||||
self.write({
|
self.write({
|
||||||
'state': new_state,
|
'state': new_state,
|
||||||
'acquirer_reference': tree.get('PAYID'),
|
'acquirer_reference': tree.get('PAYID'),
|
||||||
'html_3ds': base64.b64decode(tree.HTML_ANSWER.decode('ascii')),
|
'html_3ds': ustr(base64.b64decode(tree.HTML_ANSWER.text)),
|
||||||
})
|
})
|
||||||
elif status in self._ogone_wait_tx_status and tries > 0:
|
elif status in self._ogone_wait_tx_status and tries > 0:
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
@@ -585,4 +584,4 @@ class PaymentToken(models.Model):
|
|||||||
'acquirer_ref': alias,
|
'acquirer_ref': alias,
|
||||||
'name': 'XXXXXXXXXXXX%s - %s' % (values['cc_number'][-4:], values['cc_holder_name'])
|
'name': 'XXXXXXXXXXXX%s - %s' % (values['cc_number'][-4:], values['cc_holder_name'])
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
<field name="available_in_pos">True</field>
|
<field name="available_in_pos">True</field>
|
||||||
<field name="list_price">2.83</field>
|
<field name="list_price">2.83</field>
|
||||||
<field name="name">Orange Butterfly</field>
|
<field name="name">Orange Butterfly</field>
|
||||||
|
<field name="taxes_id">False</field>
|
||||||
<field name="pos_categ_id" ref="fruits_vegetables"/>
|
<field name="pos_categ_id" ref="fruits_vegetables"/>
|
||||||
<field name="to_weight">True</field>
|
<field name="to_weight">True</field>
|
||||||
<field name="uom_id" ref="product.product_uom_kgm" />
|
<field name="uom_id" ref="product.product_uom_kgm" />
|
||||||
|
|||||||
@@ -732,7 +732,7 @@ class PosOrder(models.Model):
|
|||||||
# when the pos.config has no picking_type_id set only the moves will be created
|
# when the pos.config has no picking_type_id set only the moves will be created
|
||||||
if moves and not return_picking and not order_picking:
|
if moves and not return_picking and not order_picking:
|
||||||
moves._action_assign()
|
moves._action_assign()
|
||||||
moves.filtered(lambda m: m.state in ['confirmed', 'waiting']).force_assign()
|
moves.filtered(lambda m: m.state in ['confirmed', 'waiting'])._force_assign()
|
||||||
moves.filtered(lambda m: m.product_id.tracking == 'none')._action_done()
|
moves.filtered(lambda m: m.product_id.tracking == 'none')._action_done()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1305,6 +1305,7 @@ exports.Orderline = Backbone.Model.extend({
|
|||||||
this.type = 'unit';
|
this.type = 'unit';
|
||||||
this.selected = false;
|
this.selected = false;
|
||||||
this.id = orderline_id++;
|
this.id = orderline_id++;
|
||||||
|
this.price_manually_set = false;
|
||||||
|
|
||||||
if (options.price) {
|
if (options.price) {
|
||||||
this.set_unit_price(options.price);
|
this.set_unit_price(options.price);
|
||||||
@@ -1345,6 +1346,7 @@ exports.Orderline = Backbone.Model.extend({
|
|||||||
orderline.price = this.price;
|
orderline.price = this.price;
|
||||||
orderline.type = this.type;
|
orderline.type = this.type;
|
||||||
orderline.selected = false;
|
orderline.selected = false;
|
||||||
|
orderline.price_manually_set = this.price_manually_set;
|
||||||
return orderline;
|
return orderline;
|
||||||
},
|
},
|
||||||
set_product_lot: function(product){
|
set_product_lot: function(product){
|
||||||
@@ -1395,7 +1397,7 @@ exports.Orderline = Backbone.Model.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// just like in sale.order changing the quantity will recompute the unit price
|
// just like in sale.order changing the quantity will recompute the unit price
|
||||||
if(! keep_price){
|
if(! keep_price && ! this.price_manually_set){
|
||||||
this.set_unit_price(this.product.get_price(this.order.pricelist, this.get_quantity()));
|
this.set_unit_price(this.product.get_price(this.order.pricelist, this.get_quantity()));
|
||||||
this.order.fix_tax_included_price(this);
|
this.order.fix_tax_included_price(this);
|
||||||
}
|
}
|
||||||
@@ -2232,7 +2234,11 @@ exports.Order = Backbone.Model.extend({
|
|||||||
set_pricelist: function (pricelist) {
|
set_pricelist: function (pricelist) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.pricelist = pricelist;
|
this.pricelist = pricelist;
|
||||||
_.each(this.get_orderlines(), function (line) {
|
|
||||||
|
var lines_to_recompute = _.filter(this.get_orderlines(), function (line) {
|
||||||
|
return ! line.price_manually_set;
|
||||||
|
});
|
||||||
|
_.each(lines_to_recompute, function (line) {
|
||||||
line.set_unit_price(line.product.get_price(self.pricelist, line.get_quantity()));
|
line.set_unit_price(line.product.get_price(self.pricelist, line.get_quantity()));
|
||||||
self.fix_tax_included_price(line);
|
self.fix_tax_included_price(line);
|
||||||
});
|
});
|
||||||
@@ -2355,7 +2361,7 @@ exports.Order = Backbone.Model.extend({
|
|||||||
this.assert_editable();
|
this.assert_editable();
|
||||||
var newPaymentline = new exports.Paymentline({},{order: this, cashregister:cashregister, pos: this.pos});
|
var newPaymentline = new exports.Paymentline({},{order: this, cashregister:cashregister, pos: this.pos});
|
||||||
if(cashregister.journal.type !== 'cash' || this.pos.config.iface_precompute_cash){
|
if(cashregister.journal.type !== 'cash' || this.pos.config.iface_precompute_cash){
|
||||||
newPaymentline.set_amount( Math.max(this.get_due(),0) );
|
newPaymentline.set_amount( this.get_due() );
|
||||||
}
|
}
|
||||||
this.paymentlines.add(newPaymentline);
|
this.paymentlines.add(newPaymentline);
|
||||||
this.select_paymentline(newPaymentline);
|
this.select_paymentline(newPaymentline);
|
||||||
@@ -2519,10 +2525,10 @@ exports.Order = Backbone.Model.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return round_pr(Math.max(0,due), this.pos.currency.rounding);
|
return round_pr(due, this.pos.currency.rounding);
|
||||||
},
|
},
|
||||||
is_paid: function(){
|
is_paid: function(){
|
||||||
return this.get_due() === 0;
|
return this.get_due() <= 0;
|
||||||
},
|
},
|
||||||
is_paid_with_cash: function(){
|
is_paid_with_cash: function(){
|
||||||
return !!this.paymentlines.find( function(pl){
|
return !!this.paymentlines.find( function(pl){
|
||||||
|
|||||||
@@ -502,7 +502,9 @@ var OrderWidget = PosBaseWidget.extend({
|
|||||||
}else if( mode === 'discount'){
|
}else if( mode === 'discount'){
|
||||||
order.get_selected_orderline().set_discount(val);
|
order.get_selected_orderline().set_discount(val);
|
||||||
}else if( mode === 'price'){
|
}else if( mode === 'price'){
|
||||||
order.get_selected_orderline().set_unit_price(val);
|
var selected_orderline = order.get_selected_orderline();
|
||||||
|
selected_orderline.price_manually_set = true;
|
||||||
|
selected_orderline.set_unit_price(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1973,17 +1975,6 @@ var PaymentScreenWidget = ScreenWidget.extend({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var plines = order.get_paymentlines();
|
|
||||||
for (var i = 0; i < plines.length; i++) {
|
|
||||||
if (plines[i].get_type() === 'bank' && plines[i].get_amount() < 0) {
|
|
||||||
this.gui.show_popup('error',{
|
|
||||||
'message': _t('Negative Bank Payment'),
|
|
||||||
'comment': _t('You cannot have a negative amount in a Bank payment. Use a cash payment method to return money to the customer.'),
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!order.is_paid() || this.invoicing) {
|
if (!order.is_paid() || this.invoicing) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,6 +166,18 @@ odoo.define('point_of_sale.tour.pricelist', function (require) {
|
|||||||
}, {
|
}, {
|
||||||
content: "verify that unit price of oranges changed to $1",
|
content: "verify that unit price of oranges changed to $1",
|
||||||
trigger: ".total > .value:contains('$ 2.00')",
|
trigger: ".total > .value:contains('$ 2.00')",
|
||||||
|
}, {
|
||||||
|
content: "order different oranges",
|
||||||
|
trigger: ".product:contains('Orange Butterfly')",
|
||||||
|
}, {
|
||||||
|
content: "change to price mode",
|
||||||
|
trigger: ".numpad button:contains('Price')",
|
||||||
|
}, {
|
||||||
|
content: "manually override the unit price of these oranges to $5",
|
||||||
|
trigger: ".numpad button.input-button:visible:contains('5')",
|
||||||
|
}, {
|
||||||
|
content: "change back to qty mode",
|
||||||
|
trigger: ".numpad button:contains('Qty')",
|
||||||
}, {
|
}, {
|
||||||
content: "click pricelist button",
|
content: "click pricelist button",
|
||||||
trigger: ".control-button.o_pricelist_button",
|
trigger: ".control-button.o_pricelist_button",
|
||||||
@@ -173,8 +185,9 @@ odoo.define('point_of_sale.tour.pricelist', function (require) {
|
|||||||
content: "select public pricelist",
|
content: "select public pricelist",
|
||||||
trigger: ".selection-item:contains('Public Pricelist')",
|
trigger: ".selection-item:contains('Public Pricelist')",
|
||||||
}, {
|
}, {
|
||||||
content: "verify that order has been recomputed",
|
content: "verify that the boni oranges have been recomputed and the\
|
||||||
trigger: ".total > .value:contains('$ 3.96')",
|
butterfly oranges have not (their price was manually overriden)",
|
||||||
|
trigger: ".total > .value:contains('$ 8.96')",
|
||||||
}, {
|
}, {
|
||||||
content: "click pricelist button",
|
content: "click pricelist button",
|
||||||
trigger: ".control-button.o_pricelist_button",
|
trigger: ".control-button.o_pricelist_button",
|
||||||
|
|||||||
@@ -45,4 +45,13 @@
|
|||||||
<field name="context">{'search_default_available_in_pos': 1, 'default_type': 'cash'}</field>
|
<field name="context">{'search_default_available_in_pos': 1, 'default_type': 'cash'}</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- View Taxes -->
|
||||||
|
|
||||||
|
<menuitem id="menu_action_tax_form_open"
|
||||||
|
action="account.action_tax_form"
|
||||||
|
parent="point_of_sale.menu_point_config_product"
|
||||||
|
groups="base.group_no_one"
|
||||||
|
sequence="40"/>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ var ajax = require('web.ajax');
|
|||||||
var core = require('web.core');
|
var core = require('web.core');
|
||||||
var Widget = require('web.Widget');
|
var Widget = require('web.Widget');
|
||||||
var rpc = require('web.rpc');
|
var rpc = require('web.rpc');
|
||||||
|
var time = require('web.time');
|
||||||
|
|
||||||
var qweb = core.qweb;
|
var qweb = core.qweb;
|
||||||
var _t = core._t;
|
var _t = core._t;
|
||||||
@@ -105,7 +106,7 @@ var PortalChatter = Widget.extend({
|
|||||||
preprocessMessages: function(messages){
|
preprocessMessages: function(messages){
|
||||||
_.each(messages, function(m){
|
_.each(messages, function(m){
|
||||||
m['author_avatar_url'] = _.str.sprintf('/web/image/%s/%s/author_avatar/50x50', 'mail.message', m.id);
|
m['author_avatar_url'] = _.str.sprintf('/web/image/%s/%s/author_avatar/50x50', 'mail.message', m.id);
|
||||||
m['published_date_str'] = _.str.sprintf(_t('Published on %s'), moment(m.date).format('MMMM Do YYYY, h:mm:ss a'));
|
m['published_date_str'] = _.str.sprintf(_t('Published on %s'), moment(time.str_to_datetime(m.date)).format('MMMM Do YYYY, h:mm:ss a'));
|
||||||
});
|
});
|
||||||
return messages;
|
return messages;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,17 +7,18 @@ from odoo import api, fields, models
|
|||||||
class PosConfig(models.Model):
|
class PosConfig(models.Model):
|
||||||
_inherit = 'pos.config'
|
_inherit = 'pos.config'
|
||||||
|
|
||||||
def _get_default_discount_product(self):
|
|
||||||
return self.env.ref('point_of_sale.product_product_consumable')
|
|
||||||
|
|
||||||
iface_discount = fields.Boolean(string='Order Discounts', help='Allow the cashier to give discounts on the whole order.')
|
iface_discount = fields.Boolean(string='Order Discounts', help='Allow the cashier to give discounts on the whole order.')
|
||||||
discount_pc = fields.Float(string='Discount Percentage', default=10, help='The default discount percentage')
|
discount_pc = fields.Float(string='Discount Percentage', help='The default discount percentage')
|
||||||
discount_product_id = fields.Many2one('product.product', string='Discount Product', domain="[('available_in_pos', '=', True)]", help='The product used to model the discount.', default=_get_default_discount_product)
|
discount_product_id = fields.Many2one('product.product', string='Discount Product',
|
||||||
|
domain="[('available_in_pos', '=', True), ('sale_ok', '=', True)]", help='The product used to model the discount.')
|
||||||
|
|
||||||
@api.onchange('module_pos_discount')
|
@api.onchange('module_pos_discount')
|
||||||
def _onchange_module_pos_discount(self):
|
def _onchange_module_pos_discount(self):
|
||||||
if self.module_pos_discount:
|
if self.module_pos_discount:
|
||||||
self.discount_product_id = self.env['product.product'].search([('available_in_pos', '=', True)], limit=1)
|
self.discount_product_id = self.env.ref('point_of_sale.product_product_consumable', raise_if_not_found=False)
|
||||||
|
if not self.discount_product_id or not self.discount_product_id.available_in_pos or not self.discount_product_id.sale_ok:
|
||||||
|
domain = [('available_in_pos', '=', True), ('sale_ok', '=', True)]
|
||||||
|
self.discount_product_id = self.env['product.product'].search(domain, limit=1)
|
||||||
self.discount_pc = 10.0
|
self.discount_pc = 10.0
|
||||||
else:
|
else:
|
||||||
self.discount_product_id = False
|
self.discount_product_id = False
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ var DiscountButton = screens.ActionButtonWidget.extend({
|
|||||||
var order = this.pos.get_order();
|
var order = this.pos.get_order();
|
||||||
var lines = order.get_orderlines();
|
var lines = order.get_orderlines();
|
||||||
var product = this.pos.db.get_product_by_id(this.pos.config.discount_product_id[0]);
|
var product = this.pos.db.get_product_by_id(this.pos.config.discount_product_id[0]);
|
||||||
|
if (product === undefined) {
|
||||||
|
this.gui.show_popup('error', {
|
||||||
|
title : _t("No discount product found"),
|
||||||
|
body : _t("The discount product seems misconfigured. Make sure it is flagged as 'Can be Sold' and 'Available in Point of Sale'."),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove existing discounts
|
// Remove existing discounts
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
|||||||
@@ -146,82 +146,82 @@ var lookUpCodeTransaction = {
|
|||||||
'000000': _t('Transaction approved'),
|
'000000': _t('Transaction approved'),
|
||||||
},
|
},
|
||||||
'TimeoutError': {
|
'TimeoutError': {
|
||||||
'001006': _t('Global API Not Initialized'),
|
'001006': 'Global API Not Initialized',
|
||||||
'001007': _t('Timeout on Response'),
|
'001007': 'Timeout on Response',
|
||||||
'003003': _t('Socket Error sending request'),
|
'003003': 'Socket Error sending request',
|
||||||
'003004': _t('Socket already open or in use'),
|
'003004': 'Socket already open or in use',
|
||||||
'003005': _t('Socket Creation Failed'),
|
'003005': 'Socket Creation Failed',
|
||||||
'003006': _t('Socket Connection Failed'),
|
'003006': 'Socket Connection Failed',
|
||||||
'003007': _t('Connection Lost'),
|
'003007': 'Connection Lost',
|
||||||
'003008': _t('TCP/IP Failed to Initialize'),
|
'003008': 'TCP/IP Failed to Initialize',
|
||||||
'003010': _t('Time Out waiting for server response'),
|
'003010': 'Time Out waiting for server response',
|
||||||
'003011': _t('Connect Canceled'),
|
'003011': 'Connect Canceled',
|
||||||
'003053': _t('Initialize Failed'),
|
'003053': 'Initialize Failed',
|
||||||
'009999': _t('Unknown Error'),
|
'009999': 'Unknown Error',
|
||||||
},
|
},
|
||||||
'FatalError': {
|
'FatalError': {
|
||||||
'-1': _t('Timeout error'),
|
'-1': 'Timeout error',
|
||||||
'001001': _t('General Failure'),
|
'001001': 'General Failure',
|
||||||
'001003': _t('Invalid Command Format'),
|
'001003': 'Invalid Command Format',
|
||||||
'001004': _t('Insufficient Fields'),
|
'001004': 'Insufficient Fields',
|
||||||
'001011': _t('Empty Command String'),
|
'001011': 'Empty Command String',
|
||||||
'002000': _t('Password Verified'),
|
'002000': 'Password Verified',
|
||||||
'002001': _t('Queue Full'),
|
'002001': 'Queue Full',
|
||||||
'002002': _t('Password Failed – Disconnecting'),
|
'002002': 'Password Failed – Disconnecting',
|
||||||
'002003': _t('System Going Offline'),
|
'002003': 'System Going Offline',
|
||||||
'002004': _t('Disconnecting Socket'),
|
'002004': 'Disconnecting Socket',
|
||||||
'002006': _t('Refused ‘Max Connections’'),
|
'002006': 'Refused ‘Max Connections’',
|
||||||
'002008': _t('Duplicate Serial Number Detected'),
|
'002008': 'Duplicate Serial Number Detected',
|
||||||
'002009': _t('Password Failed (Client / Server)'),
|
'002009': 'Password Failed (Client / Server)',
|
||||||
'002010': _t('Password failed (Challenge / Response)'),
|
'002010': 'Password failed (Challenge / Response)',
|
||||||
'002011': _t('Internal Server Error – Call Provider'),
|
'002011': 'Internal Server Error – Call Provider',
|
||||||
'003002': _t('In Process with server'),
|
'003002': 'In Process with server',
|
||||||
'003009': _t('Control failed to find branded serial (password lookup failed)'),
|
'003009': 'Control failed to find branded serial (password lookup failed)',
|
||||||
'003012': _t('128 bit CryptoAPI failed'),
|
'003012': '128 bit CryptoAPI failed',
|
||||||
'003014': _t('Threaded Auth Started Expect Response'),
|
'003014': 'Threaded Auth Started Expect Response',
|
||||||
'003017': _t('Failed to start Event Thread.'),
|
'003017': 'Failed to start Event Thread.',
|
||||||
'003050': _t('XML Parse Error'),
|
'003050': 'XML Parse Error',
|
||||||
'003051': _t('All Connections Failed'),
|
'003051': 'All Connections Failed',
|
||||||
'003052': _t('Server Login Failed'),
|
'003052': 'Server Login Failed',
|
||||||
'004001': _t('Global Response Length Error (Too Short)'),
|
'004001': 'Global Response Length Error (Too Short)',
|
||||||
'004002': _t('Unable to Parse Response from Global (Indistinguishable)'),
|
'004002': 'Unable to Parse Response from Global (Indistinguishable)',
|
||||||
'004003': _t('Global String Error'),
|
'004003': 'Global String Error',
|
||||||
'004004': _t('Weak Encryption Request Not Supported'),
|
'004004': 'Weak Encryption Request Not Supported',
|
||||||
'004005': _t('Clear Text Request Not Supported'),
|
'004005': 'Clear Text Request Not Supported',
|
||||||
'004010': _t('Unrecognized Request Format'),
|
'004010': 'Unrecognized Request Format',
|
||||||
'004011': _t('Error Occurred While Decrypting Request'),
|
'004011': 'Error Occurred While Decrypting Request',
|
||||||
'004017': _t('Invalid Check Digit'),
|
'004017': 'Invalid Check Digit',
|
||||||
'004018': _t('Merchant ID Missing'),
|
'004018': 'Merchant ID Missing',
|
||||||
'004019': _t('TStream Type Missing'),
|
'004019': 'TStream Type Missing',
|
||||||
'004020': _t('Could Not Encrypt Response- Call Provider'),
|
'004020': 'Could Not Encrypt Response- Call Provider',
|
||||||
'100201': _t('Invalid Transaction Type'),
|
'100201': 'Invalid Transaction Type',
|
||||||
'100202': _t('Invalid Operator ID'),
|
'100202': 'Invalid Operator ID',
|
||||||
'100203': _t('Invalid Memo'),
|
'100203': 'Invalid Memo',
|
||||||
'100204': _t('Invalid Account Number'),
|
'100204': 'Invalid Account Number',
|
||||||
'100205': _t('Invalid Expiration Date'),
|
'100205': 'Invalid Expiration Date',
|
||||||
'100206': _t('Invalid Authorization Code'),
|
'100206': 'Invalid Authorization Code',
|
||||||
'100207': _t('Invalid Authorization Code'),
|
'100207': 'Invalid Authorization Code',
|
||||||
'100208': _t('Invalid Authorization Amount'),
|
'100208': 'Invalid Authorization Amount',
|
||||||
'100209': _t('Invalid Cash Back Amount'),
|
'100209': 'Invalid Cash Back Amount',
|
||||||
'100210': _t('Invalid Gratuity Amount'),
|
'100210': 'Invalid Gratuity Amount',
|
||||||
'100211': _t('Invalid Purchase Amount'),
|
'100211': 'Invalid Purchase Amount',
|
||||||
'100212': _t('Invalid Magnetic Stripe Data'),
|
'100212': 'Invalid Magnetic Stripe Data',
|
||||||
'100213': _t('Invalid PIN Block Data'),
|
'100213': 'Invalid PIN Block Data',
|
||||||
'100214': _t('Invalid Derived Key Data'),
|
'100214': 'Invalid Derived Key Data',
|
||||||
'100215': _t('Invalid State Code'),
|
'100215': 'Invalid State Code',
|
||||||
'100216': _t('Invalid Date of Birth'),
|
'100216': 'Invalid Date of Birth',
|
||||||
'100217': _t('Invalid Check Type'),
|
'100217': 'Invalid Check Type',
|
||||||
'100218': _t('Invalid Routing Number'),
|
'100218': 'Invalid Routing Number',
|
||||||
'100219': _t('Invalid TranCode'),
|
'100219': 'Invalid TranCode',
|
||||||
'100220': _t('Invalid Merchant ID'),
|
'100220': 'Invalid Merchant ID',
|
||||||
'100221': _t('Invalid TStream Type'),
|
'100221': 'Invalid TStream Type',
|
||||||
'100222': _t('Invalid Batch Number'),
|
'100222': 'Invalid Batch Number',
|
||||||
'100223': _t('Invalid Batch Item Count'),
|
'100223': 'Invalid Batch Item Count',
|
||||||
'100224': _t('Invalid MICR Input Type'),
|
'100224': 'Invalid MICR Input Type',
|
||||||
'100225': _t('Invalid Driver’s License'),
|
'100225': 'Invalid Driver’s License',
|
||||||
'100226': _t('Invalid Sequence Number'),
|
'100226': 'Invalid Sequence Number',
|
||||||
'100227': _t('Invalid Pass Data'),
|
'100227': 'Invalid Pass Data',
|
||||||
'100228': _t('Invalid Card Type'),
|
'100228': 'Invalid Card Type',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Popup to show all transaction state for the payment.
|
// Popup to show all transaction state for the payment.
|
||||||
@@ -339,6 +339,15 @@ PaymentScreenWidget.include({
|
|||||||
|
|
||||||
// Handler to manage the card reader string
|
// Handler to manage the card reader string
|
||||||
credit_code_transaction: function (parsed_result, old_deferred, retry_nr) {
|
credit_code_transaction: function (parsed_result, old_deferred, retry_nr) {
|
||||||
|
var order = this.pos.get_order();
|
||||||
|
if (order.get_due(order.selected_paymentline) < 0) {
|
||||||
|
this.gui.show_popup('error',{
|
||||||
|
'title': _t('Refunds not supported'),
|
||||||
|
'body': _t('Credit card refunds are not supported. Instead select your credit card payment method, click \'Validate\' and refund the original charge manually through the Vantiv backend.'),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(this.pos.getOnlinePaymentJournals().length === 0) {
|
if(this.pos.getOnlinePaymentJournals().length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -643,9 +652,11 @@ PaymentScreenWidget.include({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this._super(id);
|
this._super(id);
|
||||||
order.selected_paymentline.mercury_swipe_pending = true;
|
if (order.get_due(order.selected_paymentline) > 0) {
|
||||||
this.render_paymentlines();
|
order.selected_paymentline.mercury_swipe_pending = true;
|
||||||
order.trigger('change', order); // needed so that export_to_JSON gets triggered
|
this.render_paymentlines();
|
||||||
|
order.trigger('change', order); // needed so that export_to_JSON gets triggered
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._super(id);
|
this._super(id);
|
||||||
|
|||||||
@@ -427,7 +427,7 @@ class ProductTemplate(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# get the value (id) sets of existing variants
|
# get the value (id) sets of existing variants
|
||||||
existing_variants = {frozenset(variant.attribute_value_ids.ids) for variant in tmpl_id.product_variant_ids}
|
existing_variants = {frozenset(variant.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant).ids) for variant in tmpl_id.product_variant_ids}
|
||||||
# -> for each value set, create a recordset of values to create a
|
# -> for each value set, create a recordset of values to create a
|
||||||
# variant for if the value set isn't already a variant
|
# variant for if the value set isn't already a variant
|
||||||
to_create_variants = [
|
to_create_variants = [
|
||||||
|
|||||||
@@ -330,3 +330,23 @@ class TestVariantsNoCreate(common.TestProductCommon):
|
|||||||
{variant.attribute_value_ids for variant in template.product_variant_ids},
|
{variant.attribute_value_ids for variant in template.product_variant_ids},
|
||||||
{self.prod_attr1_v1, self.prod_attr1_v2},
|
{self.prod_attr1_v1, self.prod_attr1_v2},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_update_variant_with_nocreate(self):
|
||||||
|
""" update variants with a 'nocreate' value on variant """
|
||||||
|
template = self.env['product.template'].create({
|
||||||
|
'name': 'Sofax',
|
||||||
|
'uom_id': self.uom_unit.id,
|
||||||
|
'uom_po_id': self.uom_unit.id,
|
||||||
|
'attribute_line_ids': [
|
||||||
|
(0, 0, { # one variant for this one
|
||||||
|
'attribute_id': self.prod_att_1.id,
|
||||||
|
'value_ids': [(4, self.prod_attr1_v1.id)],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
self.assertEqual(len(template.product_variant_ids), 1)
|
||||||
|
|
||||||
|
for variant_id in template.product_variant_ids:
|
||||||
|
variant_id.attribute_value_ids += self.size_S
|
||||||
|
template.attribute_line_ids += template.attribute_line_ids.browse()
|
||||||
|
self.assertEqual(len(template.product_variant_ids), 1)
|
||||||
|
|||||||
@@ -537,6 +537,8 @@ class Task(models.Model):
|
|||||||
working_hours_close = fields.Float(compute='_compute_elapsed', string='Working hours to close', store=True, group_operator="avg")
|
working_hours_close = fields.Float(compute='_compute_elapsed', string='Working hours to close', store=True, group_operator="avg")
|
||||||
working_days_open = fields.Float(compute='_compute_elapsed', string='Working days to assign', store=True, group_operator="avg")
|
working_days_open = fields.Float(compute='_compute_elapsed', string='Working days to assign', store=True, group_operator="avg")
|
||||||
working_days_close = fields.Float(compute='_compute_elapsed', string='Working days to close', store=True, group_operator="avg")
|
working_days_close = fields.Float(compute='_compute_elapsed', string='Working days to close', store=True, group_operator="avg")
|
||||||
|
# customer portal: include comment and incoming emails in communication history
|
||||||
|
website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])])
|
||||||
|
|
||||||
_constraints = [(models.BaseModel._check_recursion, 'Circular references are not permitted between tasks and sub-tasks', ['parent_id'])]
|
_constraints = [(models.BaseModel._check_recursion, 'Circular references are not permitted between tasks and sub-tasks', ['parent_id'])]
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,10 @@
|
|||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
> a {
|
> div {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
&.o_needaction{
|
button.o_needaction {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
|||||||
@@ -139,8 +139,9 @@
|
|||||||
<div class="table-responsive"><table class="table table-hover o_portal_my_doc_table">
|
<div class="table-responsive"><table class="table table-hover o_portal_my_doc_table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="active">
|
<tr class="active">
|
||||||
<th class="col-md-10">Task</th>
|
<th class="col-md-9">Task</th>
|
||||||
<th>Stage</th>
|
<th class="col-md-2">Stage</th>
|
||||||
|
<th>Ref</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -152,6 +153,9 @@
|
|||||||
<td>
|
<td>
|
||||||
<span class="label label-info" title="Current stage of the task" t-esc="task.stage_id.name" />
|
<span class="label label-info" title="Current stage of the task" t-esc="task.stage_id.name" />
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<span t-esc="task.id"/>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</t>
|
</t>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -171,7 +175,7 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h4>
|
<h4>
|
||||||
<t t-call='portal.record_pager'/>
|
<t t-call='portal.record_pager'/>
|
||||||
Task - <span t-field="task.name"/>
|
Task - <span t-field="task.name"/> (#<span t-esc="task.id"/>)
|
||||||
<span t-field="task.stage_id.name" class="pull-right label label-info" title="Current stage of this task"/>
|
<span t-field="task.stage_id.name" class="pull-right label label-info" title="Current stage of this task"/>
|
||||||
<a class="btn btn-info" t-att-href="'/web#return_label=Website&model=project.task&id=%s&view_type=form' % (task.id)" groups="project.group_project_user">Edit Task</a>
|
<a class="btn btn-info" t-att-href="'/web#return_label=Website&model=project.task&id=%s&view_type=form' % (task.id)" groups="project.group_project_user">Edit Task</a>
|
||||||
</h4>
|
</h4>
|
||||||
|
|||||||
@@ -349,9 +349,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<span class="o_value"><t t-esc="record.task_count.value"/></span>
|
<span class="o_value"><t t-esc="record.task_count.value"/></span>
|
||||||
<span class="o_label"><t t-esc="record.label_tasks.value"/></span>
|
<span class="o_label"><t t-esc="record.label_tasks.value"/></span>
|
||||||
<a t-if="record.task_needaction_count.raw_value" class="o_needaction" name="%(act_project_project_2_project_task_all)d" type="action" context="{'search_default_message_needaction': 1}">
|
<button t-if="record.task_needaction_count.raw_value" class="btn-link o_needaction" name="%(act_project_project_2_project_task_all)d" type="action" context="{'search_default_message_needaction': 1}">
|
||||||
<t t-esc="record.task_needaction_count.raw_value"/>
|
<t t-esc="record.task_needaction_count.raw_value"/>
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ Please find in attachment a <strong>${object.state in ('draft', 'sent') and 'req
|
|||||||
% if object.origin:
|
% if object.origin:
|
||||||
(RFQ origin: ${object.origin})
|
(RFQ origin: ${object.origin})
|
||||||
% endif
|
% endif
|
||||||
amounting <strong>${object.amount_total} ${object.currency_id.name}</strong>
|
amounting <strong>${format_amount(object.amount_total, object.currency_id)}</strong>
|
||||||
from ${object.company_id.name}.
|
from ${object.company_id.name}.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ class PurchaseOrder(models.Model):
|
|||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def button_approve(self, force=False):
|
def button_approve(self, force=False):
|
||||||
self.write({'state': 'purchase'})
|
self.write({'state': 'purchase', 'date_approve': fields.Date.context_today(self)})
|
||||||
self._create_picking()
|
self._create_picking()
|
||||||
if self.company_id.po_lock == 'lock':
|
if self.company_id.po_lock == 'lock':
|
||||||
self.write({'state': 'done'})
|
self.write({'state': 'done'})
|
||||||
|
|||||||
@@ -181,3 +181,19 @@ class Orderpoint(models.Model):
|
|||||||
for poline in self.env['purchase.order.line'].search([('state','in',('draft','sent','to approve')),('orderpoint_id','in',self.ids)]):
|
for poline in self.env['purchase.order.line'].search([('state','in',('draft','sent','to approve')),('orderpoint_id','in',self.ids)]):
|
||||||
res[poline.orderpoint_id.id] += poline.product_uom._compute_quantity(poline.product_qty, poline.orderpoint_id.product_uom, round=False)
|
res[poline.orderpoint_id.id] += poline.product_uom._compute_quantity(poline.product_qty, poline.orderpoint_id.product_uom, round=False)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def action_view_purchase(self):
|
||||||
|
""" This function returns an action that display existing
|
||||||
|
purchase orders of given orderpoint.
|
||||||
|
"""
|
||||||
|
action = self.env.ref('purchase.purchase_rfq')
|
||||||
|
result = action.read()[0]
|
||||||
|
|
||||||
|
# Remvove the context since the action basically display RFQ and not PO.
|
||||||
|
result['context'] = {}
|
||||||
|
order_line_ids = self.env['purchase.order.line'].search([('orderpoint_id', '=', self.id)])
|
||||||
|
purchase_ids = order_line_ids.mapped('order_id')
|
||||||
|
|
||||||
|
result['domain'] = "[('id','in',%s)]" % (purchase_ids.ids)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<field name="inherit_id" ref="account.invoice_supplier_form"/>
|
<field name="inherit_id" ref="account.invoice_supplier_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="reference" position="after" >
|
<field name="reference" position="after" >
|
||||||
<field name="purchase_id" attrs="{'readonly': [('state','not in',['draft'])], 'invisible': [('state', '=', 'purchase')]}" class="oe_edit_only"
|
<field name="purchase_id" attrs="{'readonly': [('state','not in',['draft'])], 'invisible': ['|', ('state', '=', 'purchase'), ('type', '=', 'in_refund')]}" class="oe_edit_only"
|
||||||
options="{'no_create': True}" context="{'show_total_amount': True}"/>
|
options="{'no_create': True}" context="{'show_total_amount': True}"/>
|
||||||
</field>
|
</field>
|
||||||
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='company_id']" position="after">
|
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='company_id']" position="after">
|
||||||
|
|||||||
@@ -30,9 +30,12 @@
|
|||||||
<field name="inherit_id" ref="stock.view_warehouse_orderpoint_form"/>
|
<field name="inherit_id" ref="stock.view_warehouse_orderpoint_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<div name="button_box" position="inside">
|
<div name="button_box" position="inside">
|
||||||
<button name="%(purchase.purchase_order_action_generic)d" type="action"
|
<button type="object"
|
||||||
string="Purchases" class="oe_stat_button" icon="fa-refresh"
|
name="action_view_purchase"
|
||||||
domain="[('orderpoint_id','=',active_id)]"/>
|
class="oe_stat_button"
|
||||||
|
string="Purchases"
|
||||||
|
icon="fa-refresh">
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -743,7 +743,7 @@ class ResourceCalendarLeaves(models.Model):
|
|||||||
date_from = fields.Datetime('Start Date', required=True)
|
date_from = fields.Datetime('Start Date', required=True)
|
||||||
date_to = fields.Datetime('End Date', required=True)
|
date_to = fields.Datetime('End Date', required=True)
|
||||||
tz = fields.Selection(
|
tz = fields.Selection(
|
||||||
_tz_get, string='Timezone', default=lambda self: self._context.get('tz', self.env.user.tz or 'UTC'),
|
_tz_get, string='Timezone', default=lambda self: self._context.get('tz') or self.env.user.tz or 'UTC',
|
||||||
help="Timezone used when encoding the leave. It is used to correctly "
|
help="Timezone used when encoding the leave. It is used to correctly "
|
||||||
"localize leave hours when computing time intervals.")
|
"localize leave hours when computing time intervals.")
|
||||||
resource_id = fields.Many2one(
|
resource_id = fields.Many2one(
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ class ResourceMixin(models.AbstractModel):
|
|||||||
theoric_hours = self.get_day_work_hours_count(day_intervals[0][0].date(), calendar=calendar)
|
theoric_hours = self.get_day_work_hours_count(day_intervals[0][0].date(), calendar=calendar)
|
||||||
work_time = sum((interval[1] - interval[0] for interval in day_intervals), timedelta())
|
work_time = sum((interval[1] - interval[0] for interval in day_intervals), timedelta())
|
||||||
total_work_time += work_time
|
total_work_time += work_time
|
||||||
days_count += float_utils.round((work_time.total_seconds() / 3600 / theoric_hours) * 4) / 4
|
if theoric_hours:
|
||||||
|
days_count += float_utils.round((work_time.total_seconds() / 3600 / theoric_hours) * 4) / 4
|
||||||
return {
|
return {
|
||||||
'days': days_count,
|
'days': days_count,
|
||||||
'hours': total_work_time.total_seconds() / 3600,
|
'hours': total_work_time.total_seconds() / 3600,
|
||||||
@@ -80,7 +81,8 @@ class ResourceMixin(models.AbstractModel):
|
|||||||
for day_intervals in calendar._iter_leave_intervals(from_datetime, to_datetime, self.resource_id.id):
|
for day_intervals in calendar._iter_leave_intervals(from_datetime, to_datetime, self.resource_id.id):
|
||||||
theoric_hours = self.get_day_work_hours_count(day_intervals[0][0].date(), calendar=calendar)
|
theoric_hours = self.get_day_work_hours_count(day_intervals[0][0].date(), calendar=calendar)
|
||||||
leave_time = sum((interval[1] - interval[0] for interval in day_intervals), timedelta())
|
leave_time = sum((interval[1] - interval[0] for interval in day_intervals), timedelta())
|
||||||
days_count += float_utils.round((leave_time.total_seconds() / 3600 / theoric_hours) * 4) / 4
|
if theoric_hours:
|
||||||
|
days_count += float_utils.round((leave_time.total_seconds() / 3600 / theoric_hours) * 4) / 4
|
||||||
return days_count
|
return days_count
|
||||||
|
|
||||||
def iter_leaves(self, from_datetime, to_datetime, calendar=None):
|
def iter_leaves(self, from_datetime, to_datetime, calendar=None):
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import odoo
|
|||||||
from odoo import api, SUPERUSER_ID
|
from odoo import api, SUPERUSER_ID
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def uninstall_hook(cr, registry):
|
def uninstall_hook(cr, registry):
|
||||||
def update_dashboard_graph_model(dbname):
|
def update_dashboard_graph_model(dbname):
|
||||||
db_registry = odoo.modules.registry.Registry.new(dbname)
|
db_registry = odoo.modules.registry.Registry.new(dbname)
|
||||||
with api.Environment.manage(), db_registry.cursor() as cr:
|
with api.Environment.manage(), db_registry.cursor() as cr:
|
||||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||||
|
env['account.invoice.report'].init()
|
||||||
if 'crm.team' in env:
|
if 'crm.team' in env:
|
||||||
recs = env['crm.team'].search([])
|
recs = env['crm.team'].search([])
|
||||||
for rec in recs:
|
for rec in recs:
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ from . import sale
|
|||||||
from . import res_config_settings
|
from . import res_config_settings
|
||||||
from . import sale_layout
|
from . import sale_layout
|
||||||
from . import sales_team
|
from . import sales_team
|
||||||
|
from . import ir_model_fields
|
||||||
|
|||||||
@@ -96,15 +96,6 @@ class AccountInvoiceLine(models.Model):
|
|||||||
_inherit = 'account.invoice.line'
|
_inherit = 'account.invoice.line'
|
||||||
_order = 'invoice_id, layout_category_id, sequence, id'
|
_order = 'invoice_id, layout_category_id, sequence, id'
|
||||||
|
|
||||||
@api.depends('price_unit', 'discount', 'invoice_line_tax_ids', 'quantity',
|
|
||||||
'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id', 'invoice_id.company_id',
|
|
||||||
'invoice_id.date_invoice')
|
|
||||||
def _compute_total_price(self):
|
|
||||||
for line in self:
|
|
||||||
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
|
|
||||||
taxes = line.invoice_line_tax_ids.compute_all(price, line.invoice_id.currency_id, line.quantity, product=line.product_id, partner=line.invoice_id.partner_id)
|
|
||||||
line.price_total = taxes['total_included']
|
|
||||||
|
|
||||||
sale_line_ids = fields.Many2many(
|
sale_line_ids = fields.Many2many(
|
||||||
'sale.order.line',
|
'sale.order.line',
|
||||||
'sale_order_line_invoice_rel',
|
'sale_order_line_invoice_rel',
|
||||||
@@ -113,4 +104,3 @@ class AccountInvoiceLine(models.Model):
|
|||||||
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
||||||
layout_category_sequence = fields.Integer(string='Layout Sequence')
|
layout_category_sequence = fields.Integer(string='Layout Sequence')
|
||||||
# TODO: remove layout_category_sequence in master or make it work properly
|
# TODO: remove layout_category_sequence in master or make it work properly
|
||||||
price_total = fields.Monetary(compute='_compute_total_price', string='Total Amount', store=True)
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details
|
||||||
|
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
|
# This is a nasty hack, targeted for V11 only
|
||||||
|
class IrModelFields(models.Model):
|
||||||
|
_inherit = 'ir.model.fields'
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
# Prevent the deletion of some `shared` fields... -_-
|
||||||
|
self = self.filtered(
|
||||||
|
lambda rec: not (
|
||||||
|
rec.model == 'res.config.settings'
|
||||||
|
and rec.name == 'auth_signup_uninvited'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return super(IrModelFields, self).unlink()
|
||||||
@@ -10,6 +10,8 @@ class ProductProduct(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def _sales_count(self):
|
def _sales_count(self):
|
||||||
r = {}
|
r = {}
|
||||||
|
if not self.user_has_groups('sales_team.group_sale_salesman'):
|
||||||
|
return r
|
||||||
domain = [
|
domain = [
|
||||||
('state', 'in', ['sale', 'done']),
|
('state', 'in', ['sale', 'done']),
|
||||||
('product_id', 'in', self.ids),
|
('product_id', 'in', self.ids),
|
||||||
|
|||||||
@@ -501,7 +501,7 @@ class SaleOrder(models.Model):
|
|||||||
@api.returns('self', lambda value: value.id)
|
@api.returns('self', lambda value: value.id)
|
||||||
def message_post(self, **kwargs):
|
def message_post(self, **kwargs):
|
||||||
if self.env.context.get('mark_so_as_sent'):
|
if self.env.context.get('mark_so_as_sent'):
|
||||||
self.filtered(lambda o: o.state == 'draft').write({'state': 'sent'})
|
self.filtered(lambda o: o.state == 'draft').with_context(tracking_disable=True).write({'state': 'sent'})
|
||||||
return super(SaleOrder, self.with_context(mail_post_autofollow=True)).message_post(**kwargs)
|
return super(SaleOrder, self.with_context(mail_post_autofollow=True)).message_post(**kwargs)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
|||||||
@@ -644,7 +644,7 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<button name="toggle_active" position="before">
|
<button name="toggle_active" position="before">
|
||||||
<button class="oe_stat_button" name="%(action_product_sale_list)d"
|
<button class="oe_stat_button" name="%(action_product_sale_list)d"
|
||||||
type="action" icon="fa-usd">
|
type="action" icon="fa-usd" groups="sales_team.group_sale_salesman">
|
||||||
<field string="Sales" name="sales_count" widget="statinfo" />
|
<field string="Sales" name="sales_count" widget="statinfo" />
|
||||||
</button>
|
</button>
|
||||||
</button>
|
</button>
|
||||||
@@ -666,7 +666,8 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<button name="toggle_active" position="before">
|
<button name="toggle_active" position="before">
|
||||||
<button class="oe_stat_button" name="action_view_sales"
|
<button class="oe_stat_button" name="action_view_sales"
|
||||||
type="object" icon="fa-usd" attrs="{'invisible': [('sale_ok', '=', False)]}">
|
type="object" icon="fa-usd" attrs="{'invisible': [('sale_ok', '=', False)]}"
|
||||||
|
groups="sales_team.group_sale_salesman">
|
||||||
<field string="Sales" name="sales_count" widget="statinfo" />
|
<field string="Sales" name="sales_count" widget="statinfo" />
|
||||||
</button>
|
</button>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -11,3 +11,12 @@ def uninstall_hook(cr, registry):
|
|||||||
('module', '=', 'sale')
|
('module', '=', 'sale')
|
||||||
]).mapped('res_id')
|
]).mapped('res_id')
|
||||||
env['ir.ui.menu'].browse(res_ids).update({'active': False})
|
env['ir.ui.menu'].browse(res_ids).update({'active': False})
|
||||||
|
|
||||||
|
|
||||||
|
def post_init_hook(cr, registry):
|
||||||
|
env = Environment(cr, SUPERUSER_ID, {})
|
||||||
|
res_ids = env['ir.model.data'].search([
|
||||||
|
('model', '=', 'ir.ui.menu'),
|
||||||
|
('module', '=', 'sale'),
|
||||||
|
]).mapped('res_id')
|
||||||
|
env['ir.ui.menu'].browse(res_ids).update({'active': True})
|
||||||
|
|||||||
@@ -48,4 +48,5 @@ The Dashboard for the Sales Manager will include
|
|||||||
],
|
],
|
||||||
'application': True,
|
'application': True,
|
||||||
'uninstall_hook': 'uninstall_hook',
|
'uninstall_hook': 'uninstall_hook',
|
||||||
|
'post_init_hook': 'post_init_hook',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,8 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<button name="action_view_orderpoints" position="after">
|
<button name="action_view_orderpoints" position="after">
|
||||||
<button class="oe_stat_button" name="action_view_sales"
|
<button class="oe_stat_button" name="action_view_sales"
|
||||||
type="object" icon="fa-usd" attrs="{'invisible': [('sale_ok', '=', False)]}">
|
type="object" icon="fa-usd" attrs="{'invisible': [('sale_ok', '=', False)]}"
|
||||||
|
groups="sales_team.group_sale_salesman">
|
||||||
<field string="Sales" name="sales_count" widget="statinfo" />
|
<field string="Sales" name="sales_count" widget="statinfo" />
|
||||||
</button>
|
</button>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class AccountAnalyticLine(models.Model):
|
|||||||
values['timesheet_invoice_type'] = 'billable_fixed'
|
values['timesheet_invoice_type'] = 'billable_fixed'
|
||||||
# if the so line is already invoiced, and the delivered qty is still smaller than the ordered, then link the timesheet to the invoice
|
# if the so line is already invoiced, and the delivered qty is still smaller than the ordered, then link the timesheet to the invoice
|
||||||
if so_line.invoice_status == 'invoiced':
|
if so_line.invoice_status == 'invoiced':
|
||||||
values['timesheet_invoice_id'] = so_line.invoice_lines[0].invoice_id.id
|
values['timesheet_invoice_id'] = so_line.invoice_lines and so_line.invoice_lines[0].invoice_id.id
|
||||||
elif so_line.product_id.invoice_policy == 'order' and so_line.product_id.service_type != 'timesheet':
|
elif so_line.product_id.invoice_policy == 'order' and so_line.product_id.service_type != 'timesheet':
|
||||||
values['timesheet_invoice_type'] = 'billable_fixed'
|
values['timesheet_invoice_type'] = 'billable_fixed'
|
||||||
|
|
||||||
|
|||||||
@@ -52,9 +52,7 @@
|
|||||||
<a type="action" t-att-domain="json.dumps(domain)" context="{'pivot_row_groupby': ['timesheet_invoice_id'], 'pivot_column_groupby': ['date:month'],'pivot_measures': ['amount', 'timesheet_revenue']}">Profitability</a>
|
<a type="action" t-att-domain="json.dumps(domain)" context="{'pivot_row_groupby': ['timesheet_invoice_id'], 'pivot_column_groupby': ['date:month'],'pivot_measures': ['amount', 'timesheet_revenue']}">Profitability</a>
|
||||||
</th>
|
</th>
|
||||||
<td class="o_timesheet_plan_dashboard_cell">
|
<td class="o_timesheet_plan_dashboard_cell">
|
||||||
<t t-if="currency.position == 'before'" t-esc="currency.symbol"/>
|
<t t-esc="dashboard['money_amount']['invoiced']" t-options='{"widget": "monetary", "display_currency": currency}'/>
|
||||||
<t t-esc="dashboard['money_amount']['invoiced']"/>
|
|
||||||
<t t-if="currency.position == 'after'" t-esc="currency.symbol"/>
|
|
||||||
</td>
|
</td>
|
||||||
<td>Invoiced</td>
|
<td>Invoiced</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -68,9 +66,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>Fixed</td>
|
<td>Fixed</td>
|
||||||
<td class="o_timesheet_plan_dashboard_cell">
|
<td class="o_timesheet_plan_dashboard_cell">
|
||||||
<t t-if="currency.position == 'before'" t-esc="currency.symbol"/>
|
<t t-esc="dashboard['money_amount']['to_invoice']" t-options='{"widget": "monetary", "display_currency": currency}'/>
|
||||||
<t t-esc="dashboard['money_amount']['to_invoice']"/>
|
|
||||||
<t t-if="currency.position == 'after'" t-esc="currency.symbol"/>
|
|
||||||
</td>
|
</td>
|
||||||
<td>To invoice</td>
|
<td>To invoice</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -84,9 +80,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>No task found</td>
|
<td>No task found</td>
|
||||||
<td class="o_timesheet_plan_dashboard_cell">
|
<td class="o_timesheet_plan_dashboard_cell">
|
||||||
<t t-if="currency.position == 'before'" t-esc="currency.symbol"/>
|
<t t-esc="dashboard['money_amount']['cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
|
||||||
<t t-esc="dashboard['money_amount']['cost']"/>
|
|
||||||
<t t-if="currency.position == 'after'" t-esc="currency.symbol"/>
|
|
||||||
</td>
|
</td>
|
||||||
<td>Cost</td>
|
<td>Cost</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -110,9 +104,7 @@
|
|||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td class="o_timesheet_plan_dashboard_total">
|
<td class="o_timesheet_plan_dashboard_total">
|
||||||
<t t-if="currency.position == 'before'" t-esc="currency.symbol"/>
|
<t t-esc="dashboard['money_amount']['total']" t-options='{"widget": "monetary", "display_currency": currency}'/>
|
||||||
<t t-esc="dashboard['money_amount']['total']"/>
|
|
||||||
<t t-if="currency.position == 'after'" t-esc="currency.symbol"/>
|
|
||||||
</td>
|
</td>
|
||||||
<td><b>Total</b></td>
|
<td><b>Total</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -4,4 +4,16 @@
|
|||||||
from . import controllers
|
from . import controllers
|
||||||
from . import models
|
from . import models
|
||||||
from . import report
|
from . import report
|
||||||
from . import wizard
|
from . import wizard
|
||||||
|
|
||||||
|
|
||||||
|
from odoo import api, SUPERUSER_ID
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Apply proper fix & remove in master
|
||||||
|
def pre_init_hook(cr):
|
||||||
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||||
|
env['ir.model.data'].search([
|
||||||
|
('model', 'like', '%stock%'),
|
||||||
|
('module', '=', 'stock')
|
||||||
|
]).unlink()
|
||||||
|
|||||||
@@ -74,4 +74,5 @@
|
|||||||
'installable': True,
|
'installable': True,
|
||||||
'application': True,
|
'application': True,
|
||||||
'auto_install': False,
|
'auto_install': False,
|
||||||
|
'pre_init_hook': 'pre_init_hook',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,14 @@ class StockScrap(models.Model):
|
|||||||
_order = 'id desc'
|
_order = 'id desc'
|
||||||
|
|
||||||
def _get_default_scrap_location_id(self):
|
def _get_default_scrap_location_id(self):
|
||||||
return self.env['stock.location'].search([('scrap_location', '=', True)], limit=1).id
|
return self.env['stock.location'].search([('scrap_location', '=', True), ('company_id', 'in', [self.env.user.company_id.id, False])], limit=1).id
|
||||||
|
|
||||||
def _get_default_location_id(self):
|
def _get_default_location_id(self):
|
||||||
return self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
|
company_user = self.env.user.company_id
|
||||||
|
warehouse = self.env['stock.warehouse'].search([('company_id', '=', company_user.id)], limit=1)
|
||||||
|
if warehouse:
|
||||||
|
return warehouse.lot_stock_id.id
|
||||||
|
return None
|
||||||
|
|
||||||
name = fields.Char(
|
name = fields.Char(
|
||||||
'Reference', default=lambda self: _('New'),
|
'Reference', default=lambda self: _('New'),
|
||||||
|
|||||||
@@ -117,7 +117,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-right" t-if="move_line.state == 'done'">
|
<td class="text-right" t-if="move_line.state == 'done'">
|
||||||
<t t-if="move_line.ordered_qty != move_line.qty_done">
|
<t t-if="move_line.ordered_qty != move_line.qty_done">
|
||||||
<t t-set="backorder" t-value="True"/>
|
<t t-set="backorder" t-value="o.search([('backorder_id', '=', o.id), ('state', 'not in', ['cancel', 'done'])])"/>
|
||||||
</t>
|
</t>
|
||||||
<span t-field="move_line.qty_done"/>
|
<span t-field="move_line.qty_done"/>
|
||||||
<span t-field="move_line.product_uom_id"/>
|
<span t-field="move_line.product_uom_id"/>
|
||||||
|
|||||||
@@ -402,6 +402,13 @@ class StockMove(models.Model):
|
|||||||
if remaining_value_before_vacuum < 0:
|
if remaining_value_before_vacuum < 0:
|
||||||
corrected_value += remaining_value_before_vacuum
|
corrected_value += remaining_value_before_vacuum
|
||||||
|
|
||||||
|
# If `corrected_value` is 0, absolutely do *not* call `_account_entry_move`. We
|
||||||
|
# force the amount in the context, but in the case it is 0 it'll create an entry
|
||||||
|
# for the entire cost of the move. This case happens when the candidates moves
|
||||||
|
# entirely compensate the problematic move.
|
||||||
|
if not corrected_value:
|
||||||
|
continue
|
||||||
|
|
||||||
if move._is_in():
|
if move._is_in():
|
||||||
# If we just compensated an IN move that has a negative remaining
|
# If we just compensated an IN move that has a negative remaining
|
||||||
# quantity, it means the move has returned more items than it received.
|
# quantity, it means the move has returned more items than it received.
|
||||||
|
|||||||
@@ -1330,7 +1330,8 @@ class TestStockValuation(TransactionCase):
|
|||||||
|
|
||||||
def test_fifo_negative_2(self):
|
def test_fifo_negative_2(self):
|
||||||
""" Receives 10 units, send more, the extra quantity should be valued at the last fifo
|
""" Receives 10 units, send more, the extra quantity should be valued at the last fifo
|
||||||
price, running the vacuum should not do anything.
|
price, running the vacuum should not do anything. Receive 2 units at the price the two
|
||||||
|
extra units were sent, check that no accounting entries are created.
|
||||||
"""
|
"""
|
||||||
self.product1.product_tmpl_id.cost_method = 'fifo'
|
self.product1.product_tmpl_id.cost_method = 'fifo'
|
||||||
|
|
||||||
@@ -1429,9 +1430,6 @@ class TestStockValuation(TransactionCase):
|
|||||||
self.assertEqual(len(move1.account_move_ids), 1)
|
self.assertEqual(len(move1.account_move_ids), 1)
|
||||||
self.assertEqual(len(move2.account_move_ids), 1)
|
self.assertEqual(len(move2.account_move_ids), 1)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Ending
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
self.assertEqual(self.product1.qty_available, -2)
|
self.assertEqual(self.product1.qty_available, -2)
|
||||||
self.assertEqual(self.product1.stock_value, -20)
|
self.assertEqual(self.product1.stock_value, -20)
|
||||||
self.assertEqual(sum(self._get_stock_input_move_lines().mapped('debit')), 0)
|
self.assertEqual(sum(self._get_stock_input_move_lines().mapped('debit')), 0)
|
||||||
@@ -1441,6 +1439,64 @@ class TestStockValuation(TransactionCase):
|
|||||||
self.assertEqual(sum(self._get_stock_output_move_lines().mapped('debit')), 120)
|
self.assertEqual(sum(self._get_stock_output_move_lines().mapped('debit')), 120)
|
||||||
self.assertEqual(sum(self._get_stock_output_move_lines().mapped('credit')), 0)
|
self.assertEqual(sum(self._get_stock_output_move_lines().mapped('credit')), 0)
|
||||||
|
|
||||||
|
# Now receive exactly the extra units at exactly the price sent, no
|
||||||
|
# accounting entries should be created after the vacuum.
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
# Receive 2@10
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
move3 = self.env['stock.move'].create({
|
||||||
|
'name': '10 in',
|
||||||
|
'location_id': self.supplier_location.id,
|
||||||
|
'location_dest_id': self.stock_location.id,
|
||||||
|
'product_id': self.product1.id,
|
||||||
|
'product_uom': self.uom_unit.id,
|
||||||
|
'product_uom_qty': 2.0,
|
||||||
|
'price_unit': 10,
|
||||||
|
'move_line_ids': [(0, 0, {
|
||||||
|
'product_id': self.product1.id,
|
||||||
|
'location_id': self.supplier_location.id,
|
||||||
|
'location_dest_id': self.stock_location.id,
|
||||||
|
'product_uom_id': self.uom_unit.id,
|
||||||
|
'qty_done': 2.0,
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
move3._action_confirm()
|
||||||
|
move3._action_done()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
# Run the vacuum
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
self.env['stock.move']._run_fifo_vacuum()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
# Ending
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
self.assertEqual(move1.value, 100.0)
|
||||||
|
self.assertEqual(move1.remaining_qty, 0.0)
|
||||||
|
self.assertEqual(move1.price_unit, 10.0)
|
||||||
|
self.assertEqual(move1.remaining_value, 0.0)
|
||||||
|
self.assertEqual(move2.value, -120.0)
|
||||||
|
self.assertEqual(move2.remaining_qty, 0)
|
||||||
|
self.assertEqual(move2.remaining_value, 0)
|
||||||
|
self.assertEqual(move3.value, 20)
|
||||||
|
self.assertEqual(move3.remaining_qty, 0.0)
|
||||||
|
self.assertEqual(move3.price_unit, 10.0)
|
||||||
|
self.assertEqual(move3.remaining_value, 0.0)
|
||||||
|
|
||||||
|
self.assertEqual(len(move1.account_move_ids), 1)
|
||||||
|
self.assertEqual(len(move2.account_move_ids), 1)
|
||||||
|
self.assertEqual(len(move3.account_move_ids), 1) # the created account move is due to the receipt
|
||||||
|
|
||||||
|
# nothing should have changed in the accounting regarding the output
|
||||||
|
self.assertEqual(self.product1.qty_available, 0)
|
||||||
|
self.assertEqual(self.product1.stock_value, 0)
|
||||||
|
self.assertEqual(sum(self._get_stock_input_move_lines().mapped('debit')), 0)
|
||||||
|
self.assertEqual(sum(self._get_stock_input_move_lines().mapped('credit')), 120)
|
||||||
|
self.assertEqual(sum(self._get_stock_valuation_move_lines().mapped('debit')), 120)
|
||||||
|
self.assertEqual(sum(self._get_stock_valuation_move_lines().mapped('credit')), 120)
|
||||||
|
self.assertEqual(sum(self._get_stock_output_move_lines().mapped('debit')), 120)
|
||||||
|
self.assertEqual(sum(self._get_stock_output_move_lines().mapped('credit')), 0)
|
||||||
|
|
||||||
def test_fifo_negative_3(self):
|
def test_fifo_negative_3(self):
|
||||||
""" Receives 10 units, send 10 units, then send more: the extra quantity should be valued
|
""" Receives 10 units, send 10 units, then send more: the extra quantity should be valued
|
||||||
at the last fifo price, running the vacuum should not do anything.
|
at the last fifo price, running the vacuum should not do anything.
|
||||||
|
|||||||
@@ -1008,7 +1008,10 @@ var FieldX2Many = AbstractField.extend({
|
|||||||
* @param {OdooEvent} ev
|
* @param {OdooEvent} ev
|
||||||
*/
|
*/
|
||||||
_onDiscardChanges: function (ev) {
|
_onDiscardChanges: function (ev) {
|
||||||
ev.data.fieldName = this.name;
|
if (ev.target !== this) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.trigger_up('discard_changes', _.extend({}, ev.data, {fieldName: this.name}));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Called when the renderer asks to edit a line, in that case simply tells
|
* Called when the renderer asks to edit a line, in that case simply tells
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ var CrashManager = core.Class.extend({
|
|||||||
if (!this.active) {
|
if (!this.active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new Dialog(this, {
|
return new Dialog(this, {
|
||||||
size: 'medium',
|
size: 'medium',
|
||||||
title: _.str.capitalize(error.type || error.message) || _t("Odoo Warning"),
|
title: _.str.capitalize(error.type || error.message) || _t("Odoo Warning"),
|
||||||
subtitle: error.data.title,
|
subtitle: error.data.title,
|
||||||
@@ -149,10 +149,10 @@ var CrashManager = core.Class.extend({
|
|||||||
clipboard.destroy();
|
clipboard.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.open();
|
return dialog.open();
|
||||||
},
|
},
|
||||||
show_message: function(exception) {
|
show_message: function(exception) {
|
||||||
this.show_error({
|
return this.show_error({
|
||||||
type: _t("Odoo Client Error"),
|
type: _t("Odoo Client Error"),
|
||||||
message: exception,
|
message: exception,
|
||||||
data: {debug: ""}
|
data: {debug: ""}
|
||||||
|
|||||||
@@ -274,6 +274,13 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
|||||||
* @returns {Deferred}
|
* @returns {Deferred}
|
||||||
*/
|
*/
|
||||||
_confirmChange: function (id, fields, e) {
|
_confirmChange: function (id, fields, e) {
|
||||||
|
if (e.name === 'discard_changes' && e.target.reset) {
|
||||||
|
// the target of the discard event is a field widget. In that
|
||||||
|
// case, we simply want to reset the specific field widget,
|
||||||
|
// not the full view
|
||||||
|
return e.target.reset(this.model.get(e.target.dataPointID), e, true);
|
||||||
|
}
|
||||||
|
|
||||||
var state = this.model.get(this.handle);
|
var state = this.model.get(this.handle);
|
||||||
return this.renderer.confirmChange(state, id, fields, e);
|
return this.renderer.confirmChange(state, id, fields, e);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -189,6 +189,90 @@ var BasicModel = AbstractModel.extend({
|
|||||||
return id;
|
return id;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Add and process default values for a given record. Those values are
|
||||||
|
* parsed and stored in the '_changes' key of the record. For relational
|
||||||
|
* fields, sub-dataPoints are created, and missing relational data is
|
||||||
|
* fetched. Also generate default values for fields with no given value.
|
||||||
|
* Typically, this function is called with the result of a 'default_get'
|
||||||
|
* RPC, to populate a newly created dataPoint. It may also be called when a
|
||||||
|
* one2many subrecord is open in a form view (dialog), to generate the
|
||||||
|
* default values for the fields displayed in the o2m form view, but not in
|
||||||
|
* the list or kanban (mainly to correctly create sub-dataPoints for
|
||||||
|
* relational fields).
|
||||||
|
*
|
||||||
|
* @param {string} recordID local id for a record
|
||||||
|
* @param {Object} values dict of default values for the given record
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.viewType] current viewType. If not set, we will
|
||||||
|
* assume main viewType from the record
|
||||||
|
* @param {Array} [options.fieldNames] list of field names for which a
|
||||||
|
* default value must be generated (used to complete the values dict)
|
||||||
|
* @returns {Deferred}
|
||||||
|
*/
|
||||||
|
applyDefaultValues: function (recordID, values, options) {
|
||||||
|
options = options || {};
|
||||||
|
var record = this.localData[recordID];
|
||||||
|
var viewType = options.viewType || record.viewType;
|
||||||
|
var fieldNames = options.fieldNames || Object.keys(record.fieldsInfo[viewType]);
|
||||||
|
var field;
|
||||||
|
var fieldName;
|
||||||
|
record._changes = record._changes || {};
|
||||||
|
|
||||||
|
// ignore values for non requested fields (for instance, fields that are
|
||||||
|
// not in the view)
|
||||||
|
values = _.pick(values, fieldNames);
|
||||||
|
|
||||||
|
// fill default values for missing fields
|
||||||
|
for (var i = 0; i < fieldNames.length; i++) {
|
||||||
|
fieldName = fieldNames[i];
|
||||||
|
if (!(fieldName in values) && !(fieldName in record._changes)) {
|
||||||
|
field = record.fields[fieldName];
|
||||||
|
if (field.type === 'float' ||
|
||||||
|
field.type === 'integer' ||
|
||||||
|
field.type === 'monetary') {
|
||||||
|
values[fieldName] = 0;
|
||||||
|
} else if (field.type === 'one2many' || field.type === 'many2many') {
|
||||||
|
values[fieldName] = [];
|
||||||
|
} else {
|
||||||
|
values[fieldName] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse each value and create dataPoints for relational fields
|
||||||
|
var defs = [];
|
||||||
|
for (fieldName in values) {
|
||||||
|
field = record.fields[fieldName];
|
||||||
|
record.data[fieldName] = null;
|
||||||
|
var dp;
|
||||||
|
if (field.type === 'many2one' && values[fieldName]) {
|
||||||
|
dp = this._makeDataPoint({
|
||||||
|
context: record.context,
|
||||||
|
data: {id: values[fieldName]},
|
||||||
|
modelName: field.relation,
|
||||||
|
parentID: record.id,
|
||||||
|
});
|
||||||
|
record._changes[fieldName] = dp.id;
|
||||||
|
} else if (field.type === 'reference' && values[fieldName]) {
|
||||||
|
var ref = values[fieldName].split(',');
|
||||||
|
dp = this._makeDataPoint({
|
||||||
|
context: record.context,
|
||||||
|
data: {id: parseInt(ref[1])},
|
||||||
|
modelName: ref[0],
|
||||||
|
parentID: record.id,
|
||||||
|
});
|
||||||
|
defs.push(this._fetchNameGet(dp));
|
||||||
|
record._changes[fieldName] = dp.id;
|
||||||
|
} else if (field.type === 'one2many' || field.type === 'many2many') {
|
||||||
|
defs.push(this._processX2ManyCommands(record, fieldName, values[fieldName], options));
|
||||||
|
} else {
|
||||||
|
record._changes[fieldName] = this._parseServerValue(field, values[fieldName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $.when.apply($, defs);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Onchange RPCs may return values for fields that are not in the current
|
* Onchange RPCs may return values for fields that are not in the current
|
||||||
* view. Those fields might even be unknown when the onchange returns (e.g.
|
* view. Those fields might even be unknown when the onchange returns (e.g.
|
||||||
@@ -1397,6 +1481,7 @@ var BasicModel = AbstractModel.extend({
|
|||||||
list = self._makeDataPoint({
|
list = self._makeDataPoint({
|
||||||
fields: view ? view.fields : fieldInfo.relatedFields,
|
fields: view ? view.fields : fieldInfo.relatedFields,
|
||||||
fieldsInfo: view ? view.fieldsInfo : fieldInfo.fieldsInfo,
|
fieldsInfo: view ? view.fieldsInfo : fieldInfo.fieldsInfo,
|
||||||
|
limit: fieldInfo.limit,
|
||||||
modelName: field.relation,
|
modelName: field.relation,
|
||||||
parentID: record.id,
|
parentID: record.id,
|
||||||
static: true,
|
static: true,
|
||||||
@@ -3228,27 +3313,8 @@ var BasicModel = AbstractModel.extend({
|
|||||||
context: params.context,
|
context: params.context,
|
||||||
})
|
})
|
||||||
.then(function (result) {
|
.then(function (result) {
|
||||||
// fill default values for missing fields
|
|
||||||
for (var i = 0; i < fieldNames.length; i++) {
|
|
||||||
var fieldName = fieldNames[i];
|
|
||||||
if (!(fieldName in result)) {
|
|
||||||
var field = params.fields[fieldName];
|
|
||||||
if (field.type === 'float' ||
|
|
||||||
field.type === 'integer' ||
|
|
||||||
field.type === 'monetary') {
|
|
||||||
result[fieldName] = 0;
|
|
||||||
} else if (field.type === 'one2many' || field.type === 'many2many') {
|
|
||||||
result[fieldName] = [];
|
|
||||||
} else {
|
|
||||||
result[fieldName] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = {};
|
|
||||||
var record = self._makeDataPoint({
|
var record = self._makeDataPoint({
|
||||||
modelName: modelName,
|
modelName: modelName,
|
||||||
data: data,
|
|
||||||
fields: params.fields,
|
fields: params.fields,
|
||||||
fieldsInfo: params.fieldsInfo,
|
fieldsInfo: params.fieldsInfo,
|
||||||
context: params.context,
|
context: params.context,
|
||||||
@@ -3257,37 +3323,7 @@ var BasicModel = AbstractModel.extend({
|
|||||||
viewType: params.viewType,
|
viewType: params.viewType,
|
||||||
});
|
});
|
||||||
|
|
||||||
var defs = [];
|
return self.applyDefaultValues(record.id, result)
|
||||||
_.each(fieldNames, function (name) {
|
|
||||||
var field = params.fields[name];
|
|
||||||
data[name] = null;
|
|
||||||
record._changes = record._changes || {};
|
|
||||||
var dp;
|
|
||||||
if (field.type === 'many2one' && result[name]) {
|
|
||||||
dp = self._makeDataPoint({
|
|
||||||
context: record.context,
|
|
||||||
data: {id: result[name]},
|
|
||||||
modelName: field.relation,
|
|
||||||
parentID: record.id,
|
|
||||||
});
|
|
||||||
record._changes[name] = dp.id;
|
|
||||||
} else if (field.type === 'reference' && result[name]) {
|
|
||||||
var ref = result[name].split(',');
|
|
||||||
dp = self._makeDataPoint({
|
|
||||||
context: record.context,
|
|
||||||
data: {id: parseInt(ref[1])},
|
|
||||||
modelName: ref[0],
|
|
||||||
parentID: record.id,
|
|
||||||
});
|
|
||||||
defs.push(self._fetchNameGet(dp));
|
|
||||||
record._changes[name] = dp.id;
|
|
||||||
} else if (field.type === 'one2many' || field.type === 'many2many') {
|
|
||||||
defs.push(self._processX2ManyCommands(record, name, result[name]));
|
|
||||||
} else {
|
|
||||||
record._changes[name] = self._parseServerValue(field, result[name]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return $.when.apply($, defs)
|
|
||||||
.then(function () {
|
.then(function () {
|
||||||
var def = $.Deferred();
|
var def = $.Deferred();
|
||||||
self._performOnChange(record, fields_key).always(function () {
|
self._performOnChange(record, fields_key).always(function () {
|
||||||
@@ -3470,13 +3506,17 @@ var BasicModel = AbstractModel.extend({
|
|||||||
* @param {Object} record
|
* @param {Object} record
|
||||||
* @param {string} fieldName
|
* @param {string} fieldName
|
||||||
* @param {Array[Array]} commands
|
* @param {Array[Array]} commands
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.viewType] current viewType. If not set, we will
|
||||||
|
* assume main viewType from the record
|
||||||
* @returns {Deferred}
|
* @returns {Deferred}
|
||||||
*/
|
*/
|
||||||
_processX2ManyCommands: function (record, fieldName, commands) {
|
_processX2ManyCommands: function (record, fieldName, commands, options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
options = options || {};
|
||||||
var defs = [];
|
var defs = [];
|
||||||
var field = record.fields[fieldName];
|
var field = record.fields[fieldName];
|
||||||
var fieldInfo = record.fieldsInfo[record.viewType][fieldName];
|
var fieldInfo = record.fieldsInfo[options.viewType || record.viewType][fieldName];
|
||||||
var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode];
|
var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode];
|
||||||
var fieldsInfo = view ? view.fieldsInfo : fieldInfo.fieldsInfo;
|
var fieldsInfo = view ? view.fieldsInfo : fieldInfo.fieldsInfo;
|
||||||
var fields = view ? view.fields : fieldInfo.relatedFields;
|
var fields = view ? view.fields : fieldInfo.relatedFields;
|
||||||
@@ -3770,14 +3810,14 @@ var BasicModel = AbstractModel.extend({
|
|||||||
|
|
||||||
// for multi-pages list datapoints, we might need to read the
|
// for multi-pages list datapoints, we might need to read the
|
||||||
// order field first to apply the order on all pages
|
// order field first to apply the order on all pages
|
||||||
if (list.res_ids.length > list.limit) {
|
if (list.res_ids.length > list.limit && list.orderedBy.length) {
|
||||||
if (!list.orderedResIDs && list.orderedBy.length) {
|
if (!list.orderedResIDs) {
|
||||||
var fieldNames = _.pluck(list.orderedBy, 'name');
|
var fieldNames = _.pluck(list.orderedBy, 'name');
|
||||||
def = this._readMissingFields(list, _.filter(list.res_ids, _.isNumber), fieldNames);
|
def = this._readMissingFields(list, _.filter(list.res_ids, _.isNumber), fieldNames);
|
||||||
def.then(function () {
|
|
||||||
self._sortList(list);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
def.then(function () {
|
||||||
|
self._sortList(list);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return def.then(function () {
|
return def.then(function () {
|
||||||
var resIDs = [];
|
var resIDs = [];
|
||||||
|
|||||||
@@ -161,7 +161,12 @@ var BasicView = AbstractView.extend({
|
|||||||
// (because those fields were unknow at that time). So we ask
|
// (because those fields were unknow at that time). So we ask
|
||||||
// the model to process them.
|
// the model to process them.
|
||||||
def = this.model.applyRawChanges(record.id, viewType).then(function () {
|
def = this.model.applyRawChanges(record.id, viewType).then(function () {
|
||||||
if (!self.model.isNew(record.id)) {
|
if (self.model.isNew(record.id)) {
|
||||||
|
return self.model.applyDefaultValues(record.id, {}, {
|
||||||
|
fieldNames: fieldNames,
|
||||||
|
viewType: viewType,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
return self.model.reload(record.id, {
|
return self.model.reload(record.id, {
|
||||||
fieldNames: fieldNames,
|
fieldNames: fieldNames,
|
||||||
keepChanges: true,
|
keepChanges: true,
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ var CalendarController = AbstractController.extend({
|
|||||||
// When clicking on a random day of a random other week, switch to week view
|
// When clicking on a random day of a random other week, switch to week view
|
||||||
this.model.setScale('week');
|
this.model.setScale('week');
|
||||||
}
|
}
|
||||||
this.model.setDate(event.data.date, true);
|
this.model.setDate(event.data.date);
|
||||||
this.reload();
|
this.reload();
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -216,9 +216,9 @@ return AbstractModel.extend({
|
|||||||
filters: params.filters,
|
filters: params.filters,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.setDate(params.initialDate);
|
||||||
// Use mode attribute in xml file to specify zoom timeline (day,week,month)
|
// Use mode attribute in xml file to specify zoom timeline (day,week,month)
|
||||||
// by default month.
|
// by default month.
|
||||||
this.setDate(params.initialDate, true);
|
|
||||||
this.setScale(params.mode);
|
this.setScale(params.mode);
|
||||||
|
|
||||||
_.each(this.data.filters, function (filter) {
|
_.each(this.data.filters, function (filter) {
|
||||||
@@ -251,9 +251,8 @@ return AbstractModel.extend({
|
|||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @param {Moment} start
|
* @param {Moment} start
|
||||||
* @param {boolean} highlight
|
|
||||||
*/
|
*/
|
||||||
setDate: function (start, highlight) {
|
setDate: function (start) {
|
||||||
this.data.start_date = this.data.end_date = this.data.target_date = this.data.highlight_date = start;
|
this.data.start_date = this.data.end_date = this.data.target_date = this.data.highlight_date = start;
|
||||||
this.data.start_date.utc().add(this.getSession().getTZOffset(this.data.start_date), 'minutes');
|
this.data.start_date.utc().add(this.getSession().getTZOffset(this.data.start_date), 'minutes');
|
||||||
|
|
||||||
@@ -270,9 +269,6 @@ return AbstractModel.extend({
|
|||||||
this.data.start_date = this.data.start_date.clone().startOf('day');
|
this.data.start_date = this.data.start_date.clone().startOf('day');
|
||||||
this.data.end_date = this.data.end_date.clone().endOf('day');
|
this.data.end_date = this.data.end_date.clone().endOf('day');
|
||||||
}
|
}
|
||||||
if (highlight) {
|
|
||||||
this.data.highlight_date = this.data.target_date;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setScale: function (scale) {
|
setScale: function (scale) {
|
||||||
if (!_.contains(scales, scale)) {
|
if (!_.contains(scales, scale)) {
|
||||||
|
|||||||
@@ -422,11 +422,6 @@ return AbstractRenderer.extend({
|
|||||||
this.target_date = this.state.target_date.toString();
|
this.target_date = this.state.target_date.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
var highlightDate = moment(this.state.highlight_date).format('YYYY-MM-DD');
|
|
||||||
$calendar.find('.o_target_date').removeClass('o_target_date');
|
|
||||||
$calendar.find('.fc-bg .fc-day[data-date="'+highlightDate+'"]')
|
|
||||||
.addClass('o_target_date');
|
|
||||||
|
|
||||||
this.$small_calendar.datepicker("setDate", this.state.highlight_date.toDate())
|
this.$small_calendar.datepicker("setDate", this.state.highlight_date.toDate())
|
||||||
.find('.o_selected_range')
|
.find('.o_selected_range')
|
||||||
.removeClass('o_color o_selected_range');
|
.removeClass('o_color o_selected_range');
|
||||||
|
|||||||
@@ -40,7 +40,11 @@ return Dialog.extend({
|
|||||||
this.domainSelector = new DomainSelector(this, model, domain, options);
|
this.domainSelector = new DomainSelector(this, model, domain, options);
|
||||||
},
|
},
|
||||||
start: function () {
|
start: function () {
|
||||||
this.$el.css("overflow", "visible").closest(".modal-dialog").css("height", "auto"); // This restores default modal height (bootstrap) and allows field selector to overflow
|
var self = this;
|
||||||
|
this.opened().then(function () {
|
||||||
|
// this restores default modal height (bootstrap) and allows field selector to overflow
|
||||||
|
self.$el.css('overflow', 'visible').closest('.modal-dialog').css('height', 'auto');
|
||||||
|
});
|
||||||
return $.when(
|
return $.when(
|
||||||
this._super.apply(this, arguments),
|
this._super.apply(this, arguments),
|
||||||
this.domainSelector.appendTo(this.$el)
|
this.domainSelector.appendTo(this.$el)
|
||||||
|
|||||||
@@ -68,6 +68,13 @@ var Notification = Widget.extend({
|
|||||||
self.$el.animate({height: 0}, 400, "swing", self.destroy.bind(self));
|
self.$el.animate({height: 0}, 400, "swing", self.destroy.bind(self));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
start: function () {
|
||||||
|
this.$el.addClass('o_error');
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Handlers
|
// Handlers
|
||||||
|
|||||||
@@ -71,11 +71,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.o_target_date:not(.fc-today) {
|
|
||||||
background-color: @odoo-brand-primary;
|
|
||||||
opacity: 0.1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.o_calendar_sidebar_container {
|
.o_calendar_sidebar_container {
|
||||||
|
|||||||
@@ -125,7 +125,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-name="Warning" t-extend="Notification">
|
<t t-name="Warning" t-extend="Notification">
|
||||||
<t t-jquery=".o_notification">$(this).addClass('o_error');</t>
|
|
||||||
<t t-jquery=".o_icon" t-operation="replace">
|
<t t-jquery=".o_icon" t-operation="replace">
|
||||||
<span class="o_icon fa fa-3x fa-exclamation"/>
|
<span class="o_icon fa fa-3x fa-exclamation"/>
|
||||||
</t>
|
</t>
|
||||||
|
|||||||
@@ -368,10 +368,6 @@ QUnit.module('relational_fields', {
|
|||||||
"should have an open record button");
|
"should have an open record button");
|
||||||
|
|
||||||
form.$('input.o_input').click();
|
form.$('input.o_input').click();
|
||||||
form.$('input.o_input').trigger($.Event('keyup', {
|
|
||||||
which: $.ui.keyCode.ESC,
|
|
||||||
keyCode: $.ui.keyCode.ESC,
|
|
||||||
}));
|
|
||||||
|
|
||||||
assert.strictEqual(form.$('button.o_external_button:visible').length, 1,
|
assert.strictEqual(form.$('button.o_external_button:visible').length, 1,
|
||||||
"should still have an open record button");
|
"should still have an open record button");
|
||||||
@@ -3064,6 +3060,108 @@ QUnit.module('relational_fields', {
|
|||||||
form.destroy();
|
form.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('onchange on one2many containing x2many in form view', function (assert) {
|
||||||
|
assert.expect(16);
|
||||||
|
|
||||||
|
this.data.partner.onchanges = {
|
||||||
|
foo: function (obj) {
|
||||||
|
obj.turtles = [[0, false, {turtle_foo: 'new record'}]];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var form = createView({
|
||||||
|
View: FormView,
|
||||||
|
model: 'partner',
|
||||||
|
data: this.data,
|
||||||
|
arch:'<form>' +
|
||||||
|
'<field name="foo"/>' +
|
||||||
|
'<field name="turtles">' +
|
||||||
|
'<tree>' +
|
||||||
|
'<field name="turtle_foo"/>' +
|
||||||
|
'</tree>' +
|
||||||
|
'<form>' +
|
||||||
|
'<field name="partner_ids">' +
|
||||||
|
'<tree editable="top">' +
|
||||||
|
'<field name="foo"/>' +
|
||||||
|
'</tree>' +
|
||||||
|
'</field>' +
|
||||||
|
'</form>' +
|
||||||
|
'</field>' +
|
||||||
|
'</form>',
|
||||||
|
archs: {
|
||||||
|
'partner,false,list': '<tree><field name="foo"/></tree>',
|
||||||
|
'partner,false,search': '<search></search>',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(form.$('.o_data_row').length, 1,
|
||||||
|
"the onchange should have created one record in the relation");
|
||||||
|
|
||||||
|
// open the created o2m record in a form view, and add a m2m subrecord
|
||||||
|
// in its relation
|
||||||
|
form.$('.o_data_row').click();
|
||||||
|
|
||||||
|
assert.strictEqual($('.modal').length, 1, "should have opened a dialog");
|
||||||
|
assert.strictEqual($('.modal .o_data_row').length, 0,
|
||||||
|
"there should be no record in the one2many in the dialog");
|
||||||
|
|
||||||
|
// add a many2many subrecord
|
||||||
|
$('.modal .o_field_x2many_list_row_add a').click();
|
||||||
|
|
||||||
|
assert.strictEqual($('.modal').length, 2,
|
||||||
|
"should have opened a second dialog");
|
||||||
|
|
||||||
|
// select a many2many subrecord
|
||||||
|
$('.modal:nth(1) .o_list_view .o_data_cell:first').click();
|
||||||
|
|
||||||
|
assert.strictEqual($('.modal').length, 1,
|
||||||
|
"second dialog should be closed");
|
||||||
|
assert.strictEqual($('.modal .o_data_row').length, 1,
|
||||||
|
"there should be one record in the one2many in the dialog");
|
||||||
|
assert.notOk($('.modal .o_x2m_control_panel .o_cp_pager div').is(':visible'),
|
||||||
|
'm2m pager should be hidden');
|
||||||
|
|
||||||
|
// click on 'Save & Close'
|
||||||
|
$('.modal .modal-footer .btn-primary:first').click();
|
||||||
|
|
||||||
|
assert.strictEqual($('.modal').length, 0, "dialog should be closed");
|
||||||
|
|
||||||
|
// reopen o2m record, and another m2m subrecord in its relation, but
|
||||||
|
// discard the changes
|
||||||
|
form.$('.o_data_row').click();
|
||||||
|
|
||||||
|
assert.strictEqual($('.modal').length, 1, "should have opened a dialog");
|
||||||
|
assert.strictEqual($('.modal .o_data_row').length, 1,
|
||||||
|
"there should be one record in the one2many in the dialog");
|
||||||
|
|
||||||
|
// add another m2m subrecord
|
||||||
|
$('.modal .o_field_x2many_list_row_add a').click();
|
||||||
|
|
||||||
|
assert.strictEqual($('.modal').length, 2,
|
||||||
|
"should have opened a second dialog");
|
||||||
|
|
||||||
|
$('.modal:nth(1) .o_list_view .o_data_cell:first').click();
|
||||||
|
|
||||||
|
assert.strictEqual($('.modal').length, 1,
|
||||||
|
"second dialog should be closed");
|
||||||
|
assert.strictEqual($('.modal .o_data_row').length, 2,
|
||||||
|
"there should be two records in the one2many in the dialog");
|
||||||
|
|
||||||
|
// click on 'Discard'
|
||||||
|
$('.modal .modal-footer .btn-default').click();
|
||||||
|
|
||||||
|
assert.strictEqual($('.modal').length, 0, "dialog should be closed");
|
||||||
|
|
||||||
|
// reopen o2m record to check that second changes have properly been discarded
|
||||||
|
form.$('.o_data_row').click();
|
||||||
|
|
||||||
|
assert.strictEqual($('.modal').length, 1, "should have opened a dialog");
|
||||||
|
assert.strictEqual($('.modal .o_data_row').length, 1,
|
||||||
|
"there should be one record in the one2many in the dialog");
|
||||||
|
|
||||||
|
form.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test('embedded one2many with handle widget with minimum setValue calls', function (assert) {
|
QUnit.test('embedded one2many with handle widget with minimum setValue calls', function (assert) {
|
||||||
var done = assert.async();
|
var done = assert.async();
|
||||||
assert.expect(20);
|
assert.expect(20);
|
||||||
@@ -8017,6 +8115,80 @@ QUnit.module('relational_fields', {
|
|||||||
form.destroy();
|
form.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('one2many with several pages, onchange and default order', function (assert) {
|
||||||
|
// This test reproduces a specific scenario where a one2many is displayed
|
||||||
|
// over several pages, and has a default order such that a record that
|
||||||
|
// would normally be on page 1 is actually on another page. Moreover,
|
||||||
|
// there is an onchange on that one2many which converts all commands 4
|
||||||
|
// (LINK_TO) into commands 1 (UPDATE), which is standard in the ORM.
|
||||||
|
// This test ensures that the record displayed on page 2 is never fully
|
||||||
|
// read.
|
||||||
|
assert.expect(8);
|
||||||
|
|
||||||
|
var data = this.data;
|
||||||
|
data.partner.records[0].turtles = [1, 2, 3];
|
||||||
|
data.turtle.records[0].partner_ids = [1];
|
||||||
|
data.partner.onchanges = {
|
||||||
|
turtles: function (obj) {
|
||||||
|
var res = _.map(obj.turtles, function (command) {
|
||||||
|
if (command[0] === 1) { // already an UPDATE command: do nothing
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
// convert LINK_TO commands to UPDATE commands
|
||||||
|
var id = command[1];
|
||||||
|
var record = _.findWhere(data.turtle.records, {id: id});
|
||||||
|
return [1, id, _.pick(record, ['turtle_int', 'turtle_foo', 'partner_ids'])];
|
||||||
|
});
|
||||||
|
obj.turtles = [[5]].concat(res);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var form = createView({
|
||||||
|
View: FormView,
|
||||||
|
model: 'partner',
|
||||||
|
data: data,
|
||||||
|
arch: '<form string="Partners">' +
|
||||||
|
'<field name="turtles">' +
|
||||||
|
'<tree editable="top" limit="2" default_order="turtle_foo">' +
|
||||||
|
'<field name="turtle_int"/>' +
|
||||||
|
'<field name="turtle_foo" class="foo"/>' +
|
||||||
|
'<field name="partner_ids" widget="many2many_tags"/>' +
|
||||||
|
'</tree>' +
|
||||||
|
'</field>' +
|
||||||
|
'</form>',
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
var ids = args.method === 'read' ? ' [' + args.args[0] + ']' : '';
|
||||||
|
assert.step(args.method + ids);
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
res_id: 1,
|
||||||
|
viewOptions: {
|
||||||
|
mode: 'edit',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(form.$('.o_data_cell.foo').text(), 'blipkawa',
|
||||||
|
"should display two records out of three, in the correct order");
|
||||||
|
|
||||||
|
// edit turtle_int field of first row
|
||||||
|
form.$('.o_data_cell:first').click();
|
||||||
|
form.$('.o_field_widget[name=turtle_int]').val(3).trigger('input');
|
||||||
|
form.$el.click(); // leave edition
|
||||||
|
|
||||||
|
assert.strictEqual(form.$('.o_data_cell.foo').text(), 'blipkawa',
|
||||||
|
"should still display the same two records");
|
||||||
|
|
||||||
|
assert.verifySteps([
|
||||||
|
'read [1]', // main record
|
||||||
|
'read [1,2,3]', // one2many (turtle_foo, all records)
|
||||||
|
'read [2,3]', // one2many (all fields in view, records of first page)
|
||||||
|
'read [2,4]', // many2many inside one2many (partner_ids), first page only
|
||||||
|
'onchange',
|
||||||
|
]);
|
||||||
|
|
||||||
|
form.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test('new record, with one2many with more default values than limit', function (assert) {
|
QUnit.test('new record, with one2many with more default values than limit', function (assert) {
|
||||||
assert.expect(2);
|
assert.expect(2);
|
||||||
|
|
||||||
|
|||||||
@@ -565,9 +565,13 @@ var MockServer = Class.extend({
|
|||||||
return record.display_name.indexOf(str) !== -1;
|
return record.display_name.indexOf(str) !== -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return _.map(records, function (record) {
|
var result = _.map(records, function (record) {
|
||||||
return [record.id, record.display_name];
|
return [record.id, record.display_name];
|
||||||
});
|
});
|
||||||
|
if (args.limit) {
|
||||||
|
return result.slice(0, args.limit);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Simulate an 'onchange' rpc
|
* Simulate an 'onchange' rpc
|
||||||
|
|||||||
@@ -669,6 +669,42 @@ QUnit.module('Views', {
|
|||||||
model.destroy();
|
model.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('default_get returning a non requested field', function (assert) {
|
||||||
|
// 'default_get' returns a default value for the fields given in
|
||||||
|
// arguments. It should not return a value for fields that have not be
|
||||||
|
// requested. However, it happens (e.g. res.users), and the webclient
|
||||||
|
// should not crash when this situation occurs (the field should simply
|
||||||
|
// be ignored).
|
||||||
|
assert.expect(2);
|
||||||
|
|
||||||
|
this.params.context = {};
|
||||||
|
this.params.fieldNames = ['category'];
|
||||||
|
this.params.res_id = undefined;
|
||||||
|
this.params.type = 'record';
|
||||||
|
|
||||||
|
var model = createModel({
|
||||||
|
Model: BasicModel,
|
||||||
|
data: this.data,
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
var result = this._super(route, args);
|
||||||
|
if (args.method === 'default_get') {
|
||||||
|
result.product_ids = [[6, 0, [37, 41]]];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
model.load(this.params).then(function (resultID) {
|
||||||
|
var record = model.get(resultID);
|
||||||
|
assert.ok('category' in record.data,
|
||||||
|
"should have processed 'category'");
|
||||||
|
assert.notOk('product_ids' in record.data,
|
||||||
|
"should have ignored 'product_ids'");
|
||||||
|
});
|
||||||
|
|
||||||
|
model.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test('can make a default_record with default relational values', function (assert) {
|
QUnit.test('can make a default_record with default relational values', function (assert) {
|
||||||
assert.expect(7);
|
assert.expect(7);
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,41 @@ QUnit.module('Views', {
|
|||||||
parent.destroy();
|
parent.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('formviewdialog buttons in footer are not duplicated', function (assert) {
|
||||||
|
assert.expect(2);
|
||||||
|
this.data.partner.fields.poney_ids = {string: "Poneys", type: "one2many", relation: 'partner'};
|
||||||
|
this.data.partner.records[0].poney_ids = [];
|
||||||
|
|
||||||
|
var parent = createParent({
|
||||||
|
data: this.data,
|
||||||
|
archs: {
|
||||||
|
'partner,false,form':
|
||||||
|
'<form string="Partner">' +
|
||||||
|
'<field name="poney_ids"><tree editable="top"><field name="display_name"/></tree></field>' +
|
||||||
|
'<footer><button string="Custom Button" type="object" class="btn-primary"/></footer>' +
|
||||||
|
'</form>',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
new dialogs.FormViewDialog(parent, {
|
||||||
|
res_model: 'partner',
|
||||||
|
res_id: 1,
|
||||||
|
}).open();
|
||||||
|
|
||||||
|
assert.strictEqual($('div.modal button.btn-primary').length, 1,
|
||||||
|
"should have 1 buttons in modal");
|
||||||
|
|
||||||
|
$('.o_field_x2many_list_row_add a').click();
|
||||||
|
$('input.o_input').trigger($.Event('keydown', {
|
||||||
|
which: $.ui.keyCode.ESCAPE,
|
||||||
|
keyCode: $.ui.keyCode.ESCAPE,
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert.strictEqual($('div.modal button.btn-primary').length, 1,
|
||||||
|
"should still have 1 buttons in modal");
|
||||||
|
parent.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test('SelectCreateDialog use domain, group_by and search default', function (assert) {
|
QUnit.test('SelectCreateDialog use domain, group_by and search default', function (assert) {
|
||||||
assert.expect(3);
|
assert.expect(3);
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user