[MERGE] forward port branch saas-12 up to 6f8e40c
This commit is contained in:
@@ -745,7 +745,12 @@ class AccountTax(models.Model):
|
|||||||
|
|
||||||
if not round_tax:
|
if not round_tax:
|
||||||
prec += 5
|
prec += 5
|
||||||
total_excluded = total_included = base = round(price_unit * quantity, prec)
|
|
||||||
|
base_values = self.env.context.get('base_values')
|
||||||
|
if not base_values:
|
||||||
|
total_excluded = total_included = base = round(price_unit * quantity, prec)
|
||||||
|
else:
|
||||||
|
total_excluded, total_included, base = base_values
|
||||||
|
|
||||||
# Sorting key is mandatory in this case. When no key is provided, sorted() will perform a
|
# Sorting key is mandatory in this case. When no key is provided, sorted() will perform a
|
||||||
# search. However, the search method is overridden in account.tax in order to add a domain
|
# search. However, the search method is overridden in account.tax in order to add a domain
|
||||||
@@ -753,9 +758,10 @@ class AccountTax(models.Model):
|
|||||||
# case of group taxes.
|
# case of group taxes.
|
||||||
for tax in self.sorted(key=lambda r: r.sequence):
|
for tax in self.sorted(key=lambda r: r.sequence):
|
||||||
if tax.amount_type == 'group':
|
if tax.amount_type == 'group':
|
||||||
ret = tax.children_tax_ids.compute_all(price_unit, currency, quantity, product, partner)
|
children = tax.children_tax_ids.with_context(base_values=(total_excluded, total_included, base))
|
||||||
|
ret = children.compute_all(price_unit, currency, quantity, product, partner)
|
||||||
total_excluded = ret['total_excluded']
|
total_excluded = ret['total_excluded']
|
||||||
base = ret['base']
|
base = ret['base'] if tax.include_base_amount else base
|
||||||
total_included = ret['total_included']
|
total_included = ret['total_included']
|
||||||
tax_amount = total_included - total_excluded
|
tax_amount = total_included - total_excluded
|
||||||
taxes += ret['taxes']
|
taxes += ret['taxes']
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ class AccountBankStatementLine(models.Model):
|
|||||||
'details': {
|
'details': {
|
||||||
'name': _("Automatically reconciled items"),
|
'name': _("Automatically reconciled items"),
|
||||||
'model': 'account.move',
|
'model': 'account.move',
|
||||||
'ids': automatic_reconciliation_entries.ids
|
'ids': automatic_reconciliation_entries.mapped('journal_entry_ids').ids
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -612,6 +612,14 @@ class AccountInvoice(models.Model):
|
|||||||
'account_analytic_id': tax['analytic'] and line.account_analytic_id.id or False,
|
'account_analytic_id': tax['analytic'] and line.account_analytic_id.id or False,
|
||||||
'account_id': self.type in ('out_invoice', 'in_invoice') and (tax['account_id'] or line.account_id.id) or (tax['refund_account_id'] or line.account_id.id),
|
'account_id': self.type in ('out_invoice', 'in_invoice') and (tax['account_id'] or line.account_id.id) or (tax['refund_account_id'] or line.account_id.id),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# If the taxes generate moves on the same financial account as the invoice line,
|
||||||
|
# propagate the analytic account from the invoice line to the tax line.
|
||||||
|
# This is necessary in situations were (part of) the taxes cannot be reclaimed,
|
||||||
|
# to ensure the tax move is allocated to the proper analytic account.
|
||||||
|
if not vals.get('account_analytic_id') and line.account_analytic_id and vals['account_id'] == line.account_id.id:
|
||||||
|
vals['account_analytic_id'] = line.account_analytic_id.id
|
||||||
|
|
||||||
return vals
|
return vals
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@@ -622,14 +630,6 @@ class AccountInvoice(models.Model):
|
|||||||
taxes = line.invoice_line_tax_ids.compute_all(price_unit, self.currency_id, line.quantity, line.product_id, self.partner_id)['taxes']
|
taxes = line.invoice_line_tax_ids.compute_all(price_unit, self.currency_id, line.quantity, line.product_id, self.partner_id)['taxes']
|
||||||
for tax in taxes:
|
for tax in taxes:
|
||||||
val = self._prepare_tax_line_vals(line, tax)
|
val = self._prepare_tax_line_vals(line, tax)
|
||||||
|
|
||||||
# If the taxes generate moves on the same financial account as the invoice line,
|
|
||||||
# propagate the analytic account from the invoice line to the tax line.
|
|
||||||
# This is necessary in situations were (part of) the taxes cannot be reclaimed,
|
|
||||||
# to ensure the tax move is allocated to the proper analytic account.
|
|
||||||
if not val.get('account_analytic_id') and line.account_analytic_id and val['account_id'] == line.account_id.id:
|
|
||||||
val['account_analytic_id'] = line.account_analytic_id.id
|
|
||||||
|
|
||||||
key = self.env['account.tax'].browse(tax['id']).get_grouping_key(val)
|
key = self.env['account.tax'].browse(tax['id']).get_grouping_key(val)
|
||||||
|
|
||||||
if key not in tax_grouped:
|
if key not in tax_grouped:
|
||||||
@@ -1350,15 +1350,17 @@ class AccountInvoiceTax(models.Model):
|
|||||||
for invoice in self.mapped('invoice_id'):
|
for invoice in self.mapped('invoice_id'):
|
||||||
tax_grouped[invoice.id] = invoice.get_taxes_values()
|
tax_grouped[invoice.id] = invoice.get_taxes_values()
|
||||||
for tax in self:
|
for tax in self:
|
||||||
key = self.env['account.tax'].browse(tax.tax_id.id).get_grouping_key({
|
tax.base = 0.0
|
||||||
'tax_id': tax.tax_id.id,
|
if tax.tax_id:
|
||||||
'account_id': tax.account_id.id,
|
key = tax.tax_id.get_grouping_key({
|
||||||
'account_analytic_id': tax.account_analytic_id.id,
|
'tax_id': tax.tax_id.id,
|
||||||
})
|
'account_id': tax.account_id.id,
|
||||||
if tax.invoice_id and key in tax_grouped[tax.invoice_id.id]:
|
'account_analytic_id': tax.account_analytic_id.id,
|
||||||
tax.base = tax_grouped[tax.invoice_id.id][key]['base']
|
})
|
||||||
else:
|
if tax.invoice_id and key in tax_grouped[tax.invoice_id.id]:
|
||||||
_logger.warning('Tax Base Amount not computable probably due to a change in an underlying tax (%s).', tax.tax_id.name)
|
tax.base = tax_grouped[tax.invoice_id.id][key]['base']
|
||||||
|
else:
|
||||||
|
_logger.warning('Tax Base Amount not computable probably due to a change in an underlying tax (%s).', tax.tax_id.name)
|
||||||
|
|
||||||
invoice_id = fields.Many2one('account.invoice', string='Invoice', ondelete='cascade', index=True)
|
invoice_id = fields.Many2one('account.invoice', string='Invoice', ondelete='cascade', index=True)
|
||||||
name = fields.Char(string='Tax Description', required=True)
|
name = fields.Char(string='Tax Description', required=True)
|
||||||
|
|||||||
@@ -981,6 +981,9 @@ class AccountMoveLine(models.Model):
|
|||||||
return True
|
return True
|
||||||
rec_move_ids = self.env['account.partial.reconcile']
|
rec_move_ids = self.env['account.partial.reconcile']
|
||||||
for account_move_line in self:
|
for account_move_line in self:
|
||||||
|
for invoice in account_move_line.payment_id.invoice_ids:
|
||||||
|
if account_move_line in invoice.payment_move_line_ids:
|
||||||
|
account_move_line.payment_id.write({'invoice_ids': [(3, invoice.id, None)]})
|
||||||
rec_move_ids += account_move_line.matched_debit_ids
|
rec_move_ids += account_move_line.matched_debit_ids
|
||||||
rec_move_ids += account_move_line.matched_credit_ids
|
rec_move_ids += account_move_line.matched_credit_ids
|
||||||
return rec_move_ids.unlink()
|
return rec_move_ids.unlink()
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class AccountInvoiceReport(models.Model):
|
|||||||
user_currency_price_total = fields.Float(string="Total Without Tax", compute='_compute_amounts_in_user_currency', digits=0)
|
user_currency_price_total = fields.Float(string="Total Without Tax", compute='_compute_amounts_in_user_currency', digits=0)
|
||||||
price_average = fields.Float(string='Average Price', readonly=True, group_operator="avg")
|
price_average = fields.Float(string='Average Price', readonly=True, group_operator="avg")
|
||||||
user_currency_price_average = fields.Float(string="Average Price", compute='_compute_amounts_in_user_currency', digits=0)
|
user_currency_price_average = fields.Float(string="Average Price", compute='_compute_amounts_in_user_currency', digits=0)
|
||||||
currency_rate = fields.Float(string='Currency Rate', readonly=True)
|
currency_rate = fields.Float(string='Currency Rate', readonly=True, group_operator="avg")
|
||||||
nbr = fields.Integer(string='# of Lines', readonly=True) # TDE FIXME master: rename into nbr_lines
|
nbr = fields.Integer(string='# of Lines', readonly=True) # TDE FIXME master: rename into nbr_lines
|
||||||
type = fields.Selection([
|
type = fields.Selection([
|
||||||
('out_invoice', 'Customer Invoice'),
|
('out_invoice', 'Customer Invoice'),
|
||||||
|
|||||||
@@ -43,10 +43,46 @@ class TestTax(AccountTestUsers):
|
|||||||
(4, self.percent_tax.id, 0)
|
(4, self.percent_tax.id, 0)
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
self.group_tax_bis = self.tax_model.create({
|
||||||
|
'name': "Group tax bis",
|
||||||
|
'amount_type': 'group',
|
||||||
|
'amount': 0,
|
||||||
|
'sequence': 6,
|
||||||
|
'children_tax_ids': [
|
||||||
|
(4, self.fixed_tax.id, 0),
|
||||||
|
(4, self.percent_tax.id, 0)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
self.group_of_group_tax = self.tax_model.create({
|
||||||
|
'name': "Group of group tax",
|
||||||
|
'amount_type': 'group',
|
||||||
|
'amount': 0,
|
||||||
|
'sequence': 7,
|
||||||
|
'children_tax_ids': [
|
||||||
|
(4, self.group_tax.id, 0),
|
||||||
|
(4, self.group_tax_bis.id, 0)
|
||||||
|
]
|
||||||
|
})
|
||||||
self.bank_journal = self.env['account.journal'].search([('type', '=', 'bank'), ('company_id', '=', self.account_manager.company_id.id)])[0]
|
self.bank_journal = self.env['account.journal'].search([('type', '=', 'bank'), ('company_id', '=', self.account_manager.company_id.id)])[0]
|
||||||
self.bank_account = self.bank_journal.default_debit_account_id
|
self.bank_account = self.bank_journal.default_debit_account_id
|
||||||
self.expense_account = self.env['account.account'].search([('user_type_id.type', '=', 'payable')], limit=1) #Should be done by onchange later
|
self.expense_account = self.env['account.account'].search([('user_type_id.type', '=', 'payable')], limit=1) #Should be done by onchange later
|
||||||
|
|
||||||
|
def test_tax_group_of_group_tax(self):
|
||||||
|
self.fixed_tax.include_base_amount = True
|
||||||
|
self.group_tax.include_base_amount = True
|
||||||
|
self.group_of_group_tax.include_base_amount = True
|
||||||
|
res = self.group_of_group_tax.compute_all(200.0)
|
||||||
|
self.assertEquals(res['total_excluded'], 200.0)
|
||||||
|
# After calculation of first group
|
||||||
|
# base = 210
|
||||||
|
# total_included = 231
|
||||||
|
# Base of the first grouped is passed
|
||||||
|
# Base after the second group (220) is dropped.
|
||||||
|
# Base of the group of groups is passed out,
|
||||||
|
# so we obtain base as after first group
|
||||||
|
self.assertEquals(res['base'], 210.0)
|
||||||
|
self.assertEquals(res['total_included'], 263.0)
|
||||||
|
|
||||||
def test_tax_group(self):
|
def test_tax_group(self):
|
||||||
res = self.group_tax.compute_all(200.0)
|
res = self.group_tax.compute_all(200.0)
|
||||||
self.assertEquals(res['total_excluded'], 200.0)
|
self.assertEquals(res['total_excluded'], 200.0)
|
||||||
|
|||||||
@@ -135,8 +135,8 @@
|
|||||||
<field name="user_id"/>
|
<field name="user_id"/>
|
||||||
<field name="date_due"/>
|
<field name="date_due"/>
|
||||||
<field name="origin"/>
|
<field name="origin"/>
|
||||||
<field name="amount_total_signed" string="Total"/>
|
<field name="amount_total_signed" string="Total" sum="Total"/>
|
||||||
<field name="residual_signed" string="Amount Due"/>
|
<field name="residual_signed" string="Amount Due" sum="Amount Due"/>
|
||||||
<field name="currency_id" invisible="1"/>
|
<field name="currency_id" invisible="1"/>
|
||||||
<field name="company_currency_id" invisible="1"/>
|
<field name="company_currency_id" invisible="1"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
@@ -200,8 +200,8 @@
|
|||||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||||
<field name="date_due"/>
|
<field name="date_due"/>
|
||||||
<field name="origin"/>
|
<field name="origin"/>
|
||||||
<field name="amount_total_signed" string="Total"/>
|
<field name="amount_total_signed" string="Total" sum="Total"/>
|
||||||
<field name="residual_signed" string="To Pay"/>
|
<field name="residual_signed" string="To Pay" sum="To pay"/>
|
||||||
<field name="currency_id" invisible="1"/>
|
<field name="currency_id" invisible="1"/>
|
||||||
<field name="company_currency_id" invisible="1"/>
|
<field name="company_currency_id" invisible="1"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<field name="journal_id"/>
|
<field name="journal_id"/>
|
||||||
<field name="payment_method_id"/>
|
<field name="payment_method_id"/>
|
||||||
<field name="partner_id" string="Customer"/>
|
<field name="partner_id" string="Customer"/>
|
||||||
<field name="amount"/>
|
<field name="amount" sum="Amount"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
<field name="company_id" groups="base.group_multi_company"/>
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
<field name="currency_id" invisible="1"/>
|
<field name="currency_id" invisible="1"/>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<field name="journal_id"/>
|
<field name="journal_id"/>
|
||||||
<field name="payment_method_id"/>
|
<field name="payment_method_id"/>
|
||||||
<field name="partner_id" string="Vendor"/>
|
<field name="partner_id" string="Vendor"/>
|
||||||
<field name="amount"/>
|
<field name="amount" sum="Amount"/>
|
||||||
<field name="company_id" groups="base.group_multi_company"/>
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
<field name="currency_id" invisible="1"/>
|
<field name="currency_id" invisible="1"/>
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="action_account_payments_payable" model="ir.actions.act_window">
|
<record id="action_account_payments_payable" model="ir.actions.act_window">
|
||||||
<field name="name">Payments</field>
|
<field name="name">Payments</field>
|
||||||
<field name="res_model">account.payment</field>
|
<field name="res_model">account.payment</field>
|
||||||
|
|||||||
@@ -409,7 +409,7 @@
|
|||||||
<span class="fa fa-tasks fa-4x"></span>
|
<span class="fa fa-tasks fa-4x"></span>
|
||||||
<h3><strong>Revenue Recognition</strong></h3>
|
<h3><strong>Revenue Recognition</strong></h3>
|
||||||
<p>Use in conjunction with contracts to calculate your monthly revenue for multi-month contracts.</p>
|
<p>Use in conjunction with contracts to calculate your monthly revenue for multi-month contracts.</p>
|
||||||
<a t-att-href="prepare_backend_url('base.open_module_tree', 'form', 'account_asset')"
|
<a t-att-href="prepare_backend_url('base.open_module_tree', 'form', 'account_deferred_revenue')"
|
||||||
class="btn odoo_purple">
|
class="btn odoo_purple">
|
||||||
<span class="fa fa-arrow-circle-o-down"/> Install Now
|
<span class="fa fa-arrow-circle-o-down"/> Install Now
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -139,7 +139,11 @@ var DashBoard = form_common.FormWidget.extend({
|
|||||||
},
|
},
|
||||||
on_close_action: function(e) {
|
on_close_action: function(e) {
|
||||||
if (confirm(_t("Are you sure you want to remove this item ?"))) {
|
if (confirm(_t("Are you sure you want to remove this item ?"))) {
|
||||||
$(e.currentTarget).parents('.oe_action:first').remove();
|
var $container = $(e.currentTarget).parents('.oe_action:first');
|
||||||
|
var am = _.findWhere(this.action_managers, { am_id: $container.data('am_id') });
|
||||||
|
am.destroy();
|
||||||
|
this.action_managers.splice(_.indexOf(this.action_managers, am), 1);
|
||||||
|
$container.remove();
|
||||||
this.do_save_dashboard();
|
this.do_save_dashboard();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -223,7 +227,13 @@ var DashBoard = form_common.FormWidget.extend({
|
|||||||
var am = new ActionManager(this),
|
var am = new ActionManager(this),
|
||||||
// FIXME: ideally the dashboard view shall be refactored like kanban.
|
// FIXME: ideally the dashboard view shall be refactored like kanban.
|
||||||
$action = $('#' + this.view.element_id + '_action_' + index);
|
$action = $('#' + this.view.element_id + '_action_' + index);
|
||||||
$action.parent().data('action_attrs', action_attrs);
|
var $action_container = $action.closest('.oe_action');
|
||||||
|
var am_id = _.uniqueId('action_manager_');
|
||||||
|
am.am_id = am_id;
|
||||||
|
$action_container.data({
|
||||||
|
action_attrs: action_attrs,
|
||||||
|
am_id: am_id,
|
||||||
|
});
|
||||||
this.action_managers.push(am);
|
this.action_managers.push(am);
|
||||||
am.appendTo($action).then(function () {
|
am.appendTo($action).then(function () {
|
||||||
am.do_action(action).then(function () {
|
am.do_action(action).then(function () {
|
||||||
|
|||||||
@@ -171,79 +171,71 @@ class HrExpense(models.Model):
|
|||||||
'''
|
'''
|
||||||
main function that is called when trying to create the accounting entries related to an expense
|
main function that is called when trying to create the accounting entries related to an expense
|
||||||
'''
|
'''
|
||||||
journal_dict = {}
|
|
||||||
for expense in self:
|
for expense in self:
|
||||||
acc_date = expense.sheet_id.accounting_date or fields.Date.context_today(self)
|
journal = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id
|
||||||
jrn = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id
|
|
||||||
journal_dict.setdefault(jrn, self.env['hr.expense'])
|
|
||||||
journal_dict[jrn] |= expense
|
|
||||||
|
|
||||||
for journal, expense_list in journal_dict.items():
|
|
||||||
#create the move that will contain the accounting entries
|
#create the move that will contain the accounting entries
|
||||||
# reference if the same for every expense
|
acc_date = expense.sheet_id.accounting_date or expense.date
|
||||||
ref = len(expense_list.mapped('employee_id.address_home_id.ref')) == 1 and expense_list[0].employee_id.address_home_id.ref or False
|
|
||||||
move = self.env['account.move'].create({
|
move = self.env['account.move'].create({
|
||||||
'journal_id': journal.id,
|
'journal_id': journal.id,
|
||||||
'company_id': self.env.user.company_id.id,
|
'company_id': self.env.user.company_id.id,
|
||||||
'date': acc_date,
|
'date': acc_date,
|
||||||
'ref': ref,
|
'ref': expense.sheet_id.name,
|
||||||
# force the name to the default value, to avoid an eventual 'default_name' in the context
|
# force the name to the default value, to avoid an eventual 'default_name' in the context
|
||||||
# to set it to '' which cause no number to be given to the account.move when posted.
|
# to set it to '' which cause no number to be given to the account.move when posted.
|
||||||
'name': '/',
|
'name': '/',
|
||||||
})
|
})
|
||||||
for expense in expense_list:
|
company_currency = expense.company_id.currency_id
|
||||||
company_currency = expense.company_id.currency_id
|
diff_currency_p = expense.currency_id != company_currency
|
||||||
diff_currency_p = expense.currency_id != company_currency
|
#one account.move.line per expense (+taxes..)
|
||||||
#one account.move.line per expense (+taxes..)
|
move_lines = expense._move_line_get()
|
||||||
move_lines = expense._move_line_get()
|
|
||||||
|
|
||||||
#create one more move line, a counterline for the total on payable account
|
#create one more move line, a counterline for the total on payable account
|
||||||
payment_id = False
|
payment_id = False
|
||||||
total, total_currency, move_lines = expense._compute_expense_totals(company_currency, move_lines, acc_date)
|
total, total_currency, move_lines = expense._compute_expense_totals(company_currency, move_lines, acc_date)
|
||||||
if expense.payment_mode == 'company_account':
|
if expense.payment_mode == 'company_account':
|
||||||
if not expense.sheet_id.bank_journal_id.default_credit_account_id:
|
if not expense.sheet_id.bank_journal_id.default_credit_account_id:
|
||||||
raise UserError(_("No credit account found for the %s journal, please configure one.") % (expense.sheet_id.bank_journal_id.name))
|
raise UserError(_("No credit account found for the %s journal, please configure one.") % (expense.sheet_id.bank_journal_id.name))
|
||||||
emp_account = expense.sheet_id.bank_journal_id.default_credit_account_id.id
|
emp_account = expense.sheet_id.bank_journal_id.default_credit_account_id.id
|
||||||
journal = expense.bank_journal_id
|
journal = expense.sheet_id.bank_journal_id
|
||||||
#create payment
|
#create payment
|
||||||
payment_methods = (total < 0) and journal.outbound_payment_method_ids or journal.inbound_payment_method_ids
|
payment_methods = (total < 0) and journal.outbound_payment_method_ids or journal.inbound_payment_method_ids
|
||||||
journal_currency = journal.currency_id or journal.company_id.currency_id
|
journal_currency = journal.currency_id or journal.company_id.currency_id
|
||||||
payment = self.env['account.payment'].create({
|
payment = self.env['account.payment'].create({
|
||||||
'payment_method_id': payment_methods and payment_methods[0].id or False,
|
'payment_method_id': payment_methods and payment_methods[0].id or False,
|
||||||
'payment_type': total < 0 and 'outbound' or 'inbound',
|
'payment_type': total < 0 and 'outbound' or 'inbound',
|
||||||
'partner_id': expense.employee_id.address_home_id.commercial_partner_id.id,
|
'partner_id': expense.employee_id.address_home_id.commercial_partner_id.id,
|
||||||
'partner_type': 'supplier',
|
'partner_type': 'supplier',
|
||||||
'journal_id': journal.id,
|
'journal_id': journal.id,
|
||||||
'payment_date': expense.date,
|
'payment_date': expense.date,
|
||||||
'state': 'reconciled',
|
'state': 'reconciled',
|
||||||
'currency_id': diff_currency_p and expense.currency_id.id or journal_currency.id,
|
'currency_id': diff_currency_p and expense.currency_id.id or journal_currency.id,
|
||||||
'amount': diff_currency_p and abs(total_currency) or abs(total),
|
'amount': diff_currency_p and abs(total_currency) or abs(total),
|
||||||
'name': expense.name,
|
'name': expense.name,
|
||||||
|
})
|
||||||
|
payment_id = payment.id
|
||||||
|
else:
|
||||||
|
if not expense.employee_id.address_home_id:
|
||||||
|
raise UserError(_("No Home Address found for the employee %s, please configure one.") % (expense.employee_id.name))
|
||||||
|
emp_account = expense.employee_id.address_home_id.property_account_payable_id.id
|
||||||
|
|
||||||
|
move_lines.append({
|
||||||
|
'type': 'dest',
|
||||||
|
'name': expense.employee_id.name,
|
||||||
|
'price': total,
|
||||||
|
'account_id': emp_account,
|
||||||
|
'date_maturity': acc_date,
|
||||||
|
'amount_currency': diff_currency_p and total_currency or False,
|
||||||
|
'currency_id': diff_currency_p and expense.currency_id.id or False,
|
||||||
|
'payment_id': payment_id,
|
||||||
})
|
})
|
||||||
payment_id = payment.id
|
|
||||||
else:
|
|
||||||
if not expense.employee_id.address_home_id:
|
|
||||||
raise UserError(_("No Home Address found for the employee %s, please configure one.") % (expense.employee_id.name))
|
|
||||||
emp_account = expense.employee_id.address_home_id.property_account_payable_id.id
|
|
||||||
|
|
||||||
move_lines.append({
|
#convert eml into an osv-valid format
|
||||||
'type': 'dest',
|
lines = map(lambda x: (0, 0, expense._prepare_move_line(x)), move_lines)
|
||||||
'name': expense.employee_id.name,
|
move.write({'line_ids': lines})
|
||||||
'price': total,
|
expense.sheet_id.write({'account_move_id': move.id})
|
||||||
'account_id': emp_account,
|
|
||||||
'date_maturity': acc_date,
|
|
||||||
'amount_currency': diff_currency_p and total_currency or False,
|
|
||||||
'currency_id': diff_currency_p and expense.currency_id.id or False,
|
|
||||||
'payment_id': payment_id,
|
|
||||||
})
|
|
||||||
|
|
||||||
#convert eml into an osv-valid format
|
|
||||||
lines = map(lambda x: (0, 0, expense._prepare_move_line(x)), move_lines)
|
|
||||||
move.write({'line_ids': lines})
|
|
||||||
expense.sheet_id.write({'account_move_id': move.id})
|
|
||||||
if expense.payment_mode == 'company_account':
|
|
||||||
expense.sheet_id.paid_expense_sheets()
|
|
||||||
move.post()
|
move.post()
|
||||||
|
if expense.payment_mode == 'company_account':
|
||||||
|
expense.sheet_id.paid_expense_sheets()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@@ -526,3 +518,11 @@ class HrExpenseSheet(models.Model):
|
|||||||
res['domain'] = [('res_model', '=', 'hr.expense'), ('res_id', 'in', self.expense_line_ids.ids)]
|
res['domain'] = [('res_model', '=', 'hr.expense'), ('res_id', 'in', self.expense_line_ids.ids)]
|
||||||
res['context'] = {'default_res_model': 'hr.expense.sheet', 'default_res_id': self.id}
|
res['context'] = {'default_res_model': 'hr.expense.sheet', 'default_res_id': self.id}
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_open_journal_entries(self):
|
||||||
|
res = self.env['ir.actions.act_window'].for_xml_id('account', 'action_move_journal_line')
|
||||||
|
refs = [x.name for x in self]
|
||||||
|
res['domain'] = [('ref', 'in', refs)]
|
||||||
|
res['context'] = {}
|
||||||
|
return res
|
||||||
|
|||||||
@@ -422,6 +422,13 @@
|
|||||||
type="object">
|
type="object">
|
||||||
<field name="attachment_number" widget="statinfo" string="Documents"/>
|
<field name="attachment_number" widget="statinfo" string="Documents"/>
|
||||||
</button>
|
</button>
|
||||||
|
<button name="action_open_journal_entries"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-book"
|
||||||
|
string="Accounting Entries"
|
||||||
|
attrs="{'invisible': [('account_move_id', '=', False)]}"
|
||||||
|
type="object">
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="oe_title">
|
<div class="oe_title">
|
||||||
<label for="name" class="oe_edit_only"/>
|
<label for="name" class="oe_edit_only"/>
|
||||||
@@ -444,7 +451,7 @@
|
|||||||
<field name="journal_id" domain="[('type', '=', 'purchase')]" groups="account.group_account_user" options="{'no_open': True, 'no_create': True}" attrs="{'invisible': [('payment_mode', '!=', 'own_account')]}"/>
|
<field name="journal_id" domain="[('type', '=', 'purchase')]" groups="account.group_account_user" options="{'no_open': True, 'no_create': True}" attrs="{'invisible': [('payment_mode', '!=', 'own_account')]}"/>
|
||||||
<field name="bank_journal_id" groups="account.group_account_user" options="{'no_open': True, 'no_create': True}" attrs="{'invisible': [('payment_mode', '!=', 'company_account')]}" domain="[('type', 'in', ['cash', 'bank'])]"/>
|
<field name="bank_journal_id" groups="account.group_account_user" options="{'no_open': True, 'no_create': True}" attrs="{'invisible': [('payment_mode', '!=', 'company_account')]}" domain="[('type', 'in', ['cash', 'bank'])]"/>
|
||||||
<field name="accounting_date" groups="account.group_account_user" attrs="{'invisible': [('state', 'not in', ['approve', 'post', 'done'])]}"/>
|
<field name="accounting_date" groups="account.group_account_user" attrs="{'invisible': [('state', 'not in', ['approve', 'post', 'done'])]}"/>
|
||||||
<field name="account_move_id" attrs="{'invisible': [('state', 'not in', ['post', 'done'])]}" groups="account.group_account_user" readonly="1"/>
|
<field name="account_move_id" invisible="1"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<field name="expense_line_ids" nolabel="1" widget="many2many" domain="[('state', '=', 'draft')]" options="{'no_delete': True}">
|
<field name="expense_line_ids" nolabel="1" widget="many2many" domain="[('state', '=', 'draft')]" options="{'no_delete': True}">
|
||||||
|
|||||||
@@ -605,7 +605,7 @@
|
|||||||
<field name="account_id" ref="p112341"/>
|
<field name="account_id" ref="p112341"/>
|
||||||
<field name="refund_account_id" ref="p20011"/>
|
<field name="refund_account_id" ref="p20011"/>
|
||||||
<field name="chart_template_id" ref="indian_chart_template_standard"/>
|
<field name="chart_template_id" ref="indian_chart_template_standard"/>
|
||||||
<field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_25'), ref('st_tag_in_63')])]"/>
|
<field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_25')])]"/>
|
||||||
</record>
|
</record>
|
||||||
<record id="sale_edu_cess_service_tax_2" model="account.tax.template">
|
<record id="sale_edu_cess_service_tax_2" model="account.tax.template">
|
||||||
<field name="name">(standard)Input Education Cess @ 2% On Service Tax</field>
|
<field name="name">(standard)Input Education Cess @ 2% On Service Tax</field>
|
||||||
@@ -665,7 +665,7 @@
|
|||||||
<field name="account_id" ref="p11232"/>
|
<field name="account_id" ref="p11232"/>
|
||||||
<field name="refund_account_id" ref="p20011"/>
|
<field name="refund_account_id" ref="p20011"/>
|
||||||
<field name="chart_template_id" ref="indian_chart_template_standard"/>
|
<field name="chart_template_id" ref="indian_chart_template_standard"/>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('st_tag_in_33'), ref('st_tag_in_86')])]"/>
|
<field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_33'), ref('st_tag_in_74')])]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!--output purchase tax-->
|
<!--output purchase tax-->
|
||||||
@@ -679,7 +679,7 @@
|
|||||||
<field name="account_id" ref="p10050"/>
|
<field name="account_id" ref="p10050"/>
|
||||||
<field name="refund_account_id" ref="p2107"/>
|
<field name="refund_account_id" ref="p2107"/>
|
||||||
<field name="chart_template_id" ref="indian_chart_template_standard"/>
|
<field name="chart_template_id" ref="indian_chart_template_standard"/>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('st_tag_in_71'), ref('st_tag_in_22')])]"/>
|
<field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_22'), ref('st_tag_in_67')])]"/>
|
||||||
</record>
|
</record>
|
||||||
<record id="output_tax_12" model="account.tax.template">
|
<record id="output_tax_12" model="account.tax.template">
|
||||||
<field name="name">(standard)Output Purchase Tax @ 12%</field>
|
<field name="name">(standard)Output Purchase Tax @ 12%</field>
|
||||||
@@ -691,7 +691,7 @@
|
|||||||
<field name="account_id" ref="p10050"/>
|
<field name="account_id" ref="p10050"/>
|
||||||
<field name="refund_account_id" ref="p2107"/>
|
<field name="refund_account_id" ref="p2107"/>
|
||||||
<field name="chart_template_id" ref="indian_chart_template_standard"/>
|
<field name="chart_template_id" ref="indian_chart_template_standard"/>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('st_tag_in_19'), ref('st_tag_in_67')])]"/>
|
<field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_19'), ref('st_tag_in_64')])]"/>
|
||||||
</record>
|
</record>
|
||||||
<record id="output_tax_4" model="account.tax.template">
|
<record id="output_tax_4" model="account.tax.template">
|
||||||
<field name="name">(standard)Output Purchase Tax @ 4%</field>
|
<field name="name">(standard)Output Purchase Tax @ 4%</field>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ var utils = require('mail.utils');
|
|||||||
var config = require('web.config');
|
var config = require('web.config');
|
||||||
var core = require('web.core');
|
var core = require('web.core');
|
||||||
var form_common = require('web.form_common');
|
var form_common = require('web.form_common');
|
||||||
|
var framework = require('web.framework');
|
||||||
var web_utils = require('web.utils');
|
var web_utils = require('web.utils');
|
||||||
|
|
||||||
var _t = core._t;
|
var _t = core._t;
|
||||||
@@ -430,7 +431,14 @@ var Chatter = form_common.AbstractField.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
load_more_messages: function () {
|
load_more_messages: function () {
|
||||||
this.fetch_and_render_thread(this.msg_ids, {force_fetch: true});
|
var self = this;
|
||||||
|
var top_msg_id = this.$('.o_thread_message').first().data('messageId');
|
||||||
|
var top_msg_selector = '.o_thread_message[data-message-id="' + top_msg_id + '"]';
|
||||||
|
var offset = -framework.getPosition(document.querySelector(top_msg_selector)).top;
|
||||||
|
this.fetch_and_render_thread(this.msg_ids, {force_fetch: true}).then(function(){
|
||||||
|
offset += framework.getPosition(document.querySelector(top_msg_selector)).top;
|
||||||
|
self.thread.scroll_to({offset: offset});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -170,21 +170,31 @@ class StockMove(models.Model):
|
|||||||
''' Creates an extra move if necessary depending on extra quantities than foreseen or extra moves'''
|
''' Creates an extra move if necessary depending on extra quantities than foreseen or extra moves'''
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
quantity_to_split = 0
|
quantity_to_split = 0
|
||||||
|
uom_qty_to_split = 0
|
||||||
extra_move = self.env['stock.move']
|
extra_move = self.env['stock.move']
|
||||||
rounding = self.product_uom.rounding
|
rounding = self.product_uom.rounding
|
||||||
link_procurement = False
|
link_procurement = False
|
||||||
|
# If more produced than the procurement linked, you should create an extra move
|
||||||
if self.procurement_id and self.production_id and float_compare(self.production_id.qty_produced, self.procurement_id.product_qty, precision_rounding=rounding) > 0:
|
if self.procurement_id and self.production_id and float_compare(self.production_id.qty_produced, self.procurement_id.product_qty, precision_rounding=rounding) > 0:
|
||||||
done_moves_total = sum(self.production_id.move_finished_ids.filtered(lambda x: x.product_id == self.product_id and x.state == 'done').mapped('product_uom_qty'))
|
done_moves_total = sum(self.production_id.move_finished_ids.filtered(lambda x: x.product_id == self.product_id and x.state == 'done').mapped('product_uom_qty'))
|
||||||
|
# If you depassed the quantity before, you don't need to split anymore, but adapt the quantities
|
||||||
if float_compare(done_moves_total, self.procurement_id.product_qty, precision_rounding=rounding) >= 0:
|
if float_compare(done_moves_total, self.procurement_id.product_qty, precision_rounding=rounding) >= 0:
|
||||||
quantity_to_split = 0
|
quantity_to_split = 0
|
||||||
self.product_uom_qty = self.quantity_done #TODO: could change qty on move_dest_id also (in case of 2-step in/out)
|
if float_compare(self.product_uom_qty, self.quantity_done, precision_rounding=rounding) < 0:
|
||||||
|
self.product_uom_qty = self.quantity_done #TODO: could change qty on move_dest_id also (in case of 2-step in/out)
|
||||||
else:
|
else:
|
||||||
quantity_to_split = done_moves_total + self.quantity_done - self.procurement_id.product_qty
|
quantity_to_split = done_moves_total + self.quantity_done - self.procurement_id.product_qty
|
||||||
|
uom_qty_to_split = self.product_uom_qty - (self.quantity_done - quantity_to_split)#self.product_uom_qty - (self.procurement_id.product_qty + done_moves_total)
|
||||||
|
if float_compare(uom_qty_to_split, quantity_to_split, precision_rounding=rounding) < 0:
|
||||||
|
uom_qty_to_split = quantity_to_split
|
||||||
|
self.product_uom_qty = self.quantity_done - quantity_to_split
|
||||||
|
# You split also simply when the quantity done is bigger than foreseen
|
||||||
elif float_compare(self.quantity_done, self.product_uom_qty, precision_rounding=rounding) > 0:
|
elif float_compare(self.quantity_done, self.product_uom_qty, precision_rounding=rounding) > 0:
|
||||||
quantity_to_split = self.quantity_done - self.product_uom_qty
|
quantity_to_split = self.quantity_done - self.product_uom_qty
|
||||||
|
uom_qty_to_split = quantity_to_split # + no need to change existing self.product_uom_qty
|
||||||
link_procurement = True
|
link_procurement = True
|
||||||
if quantity_to_split:
|
if quantity_to_split:
|
||||||
extra_move = self.copy(default={'quantity_done': quantity_to_split, 'product_uom_qty': quantity_to_split, 'production_id': self.production_id.id,
|
extra_move = self.copy(default={'quantity_done': quantity_to_split, 'product_uom_qty': uom_qty_to_split, 'production_id': self.production_id.id,
|
||||||
'raw_material_production_id': self.raw_material_production_id.id,
|
'raw_material_production_id': self.raw_material_production_id.id,
|
||||||
'procurement_id': link_procurement and self.procurement_id.id or False})
|
'procurement_id': link_procurement and self.procurement_id.id or False})
|
||||||
extra_move.action_confirm()
|
extra_move.action_confirm()
|
||||||
@@ -203,8 +213,7 @@ class StockMove(models.Model):
|
|||||||
else:
|
else:
|
||||||
movelot.move_id = extra_move.id
|
movelot.move_id = extra_move.id
|
||||||
else:
|
else:
|
||||||
self.quantity_done = self.product_uom_qty
|
self.quantity_done -= quantity_to_split
|
||||||
self.product_uom_qty = self.quantity_done - quantity_to_split
|
|
||||||
return extra_move
|
return extra_move
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
|||||||
@@ -118,8 +118,8 @@ class StockWarehouse(models.Model):
|
|||||||
return routes
|
return routes
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _handle_renaming(self, name, code):
|
def _update_name_and_code(self, name, code):
|
||||||
res = super(StockWarehouse, self)._handle_renaming(name, code)
|
res = super(StockWarehouse, self)._update_name_and_code(name, code)
|
||||||
# change the manufacture procurement rule name
|
# change the manufacture procurement rule name
|
||||||
for warehouse in self:
|
for warehouse in self:
|
||||||
if warehouse.manufacture_pull_id:
|
if warehouse.manufacture_pull_id:
|
||||||
|
|||||||
@@ -214,8 +214,10 @@ class PosOrder(models.Model):
|
|||||||
partner_id = ResPartner._find_accounting_partner(order.partner_id).id or False
|
partner_id = ResPartner._find_accounting_partner(order.partner_id).id or False
|
||||||
if move is None:
|
if move is None:
|
||||||
# Create an entry for the sale
|
# Create an entry for the sale
|
||||||
|
journal_id = self.env['ir.config_parameter'].sudo().get_param(
|
||||||
|
'pos.closing.journal_id', default=order.sale_journal.id)
|
||||||
move = self._create_account_move(
|
move = self._create_account_move(
|
||||||
order.session_id.start_at, order.name, order.sale_journal.id, order.company_id.id)
|
order.session_id.start_at, order.name, int(journal_id), order.company_id.id)
|
||||||
|
|
||||||
def insert_data(data_type, values):
|
def insert_data(data_type, values):
|
||||||
# if have_to_group_by:
|
# if have_to_group_by:
|
||||||
@@ -540,7 +542,10 @@ class PosOrder(models.Model):
|
|||||||
for order in self:
|
for order in self:
|
||||||
address = order.partner_id.address_get(['delivery']) or {}
|
address = order.partner_id.address_get(['delivery']) or {}
|
||||||
picking_type = order.picking_type_id
|
picking_type = order.picking_type_id
|
||||||
picking_id = False
|
return_pick_type = order.picking_type_id.return_picking_type_id or order.picking_type_id
|
||||||
|
order_picking = Picking
|
||||||
|
return_picking = Picking
|
||||||
|
moves = Move
|
||||||
location_id = order.location_id.id
|
location_id = order.location_id.id
|
||||||
if order.partner_id:
|
if order.partner_id:
|
||||||
destination_id = order.partner_id.property_stock_customer.id
|
destination_id = order.partner_id.property_stock_customer.id
|
||||||
@@ -550,9 +555,10 @@ class PosOrder(models.Model):
|
|||||||
destination_id = customerloc.id
|
destination_id = customerloc.id
|
||||||
else:
|
else:
|
||||||
destination_id = picking_type.default_location_dest_id.id
|
destination_id = picking_type.default_location_dest_id.id
|
||||||
|
|
||||||
if picking_type:
|
if picking_type:
|
||||||
pos_qty = all([x.qty >= 0 for x in order.lines])
|
message = _("This transfer has been created from the point of sale session: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>") % (order.id, order.name)
|
||||||
picking_id = Picking.create({
|
picking_vals = {
|
||||||
'origin': order.name,
|
'origin': order.name,
|
||||||
'partner_id': address.get('delivery', False),
|
'partner_id': address.get('delivery', False),
|
||||||
'date_done': order.date_order,
|
'date_done': order.date_order,
|
||||||
@@ -560,44 +566,72 @@ class PosOrder(models.Model):
|
|||||||
'company_id': order.company_id.id,
|
'company_id': order.company_id.id,
|
||||||
'move_type': 'direct',
|
'move_type': 'direct',
|
||||||
'note': order.note or "",
|
'note': order.note or "",
|
||||||
'location_id': location_id if pos_qty else destination_id,
|
'location_id': location_id,
|
||||||
'location_dest_id': destination_id if pos_qty else location_id,
|
'location_dest_id': destination_id,
|
||||||
})
|
}
|
||||||
message = _("This transfer has been created from the point of sale session: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>") % (order.id, order.name)
|
pos_qty = any([x.qty >= 0 for x in order.lines])
|
||||||
picking_id.message_post(body=message)
|
if pos_qty:
|
||||||
order.write({'picking_id': picking_id.id})
|
order_picking = Picking.create(picking_vals.copy())
|
||||||
|
order_picking.message_post(body=message)
|
||||||
|
neg_qty = any([x.qty < 0 for x in order.lines])
|
||||||
|
if neg_qty:
|
||||||
|
return_vals = picking_vals.copy()
|
||||||
|
return_vals.update({
|
||||||
|
'location_id': destination_id,
|
||||||
|
'location_dest_id': return_pick_type != picking_type and return_pick_type.default_location_dest_id.id or location_id,
|
||||||
|
'picking_type_id': return_pick_type.id
|
||||||
|
})
|
||||||
|
return_picking = Picking.create(return_vals)
|
||||||
|
return_picking.message_post(body=message)
|
||||||
|
|
||||||
for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu']):
|
for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu']):
|
||||||
Move += Move.create({
|
moves |= Move.create({
|
||||||
'name': line.name,
|
'name': line.name,
|
||||||
'product_uom': line.product_id.uom_id.id,
|
'product_uom': line.product_id.uom_id.id,
|
||||||
'picking_id': picking_id and picking_id.id or False,
|
'picking_id': order_picking.id if line.qty >= 0 else return_picking.id,
|
||||||
'picking_type_id': picking_type.id,
|
'picking_type_id': picking_type.id if line.qty >= 0 else return_pick_type.id,
|
||||||
'product_id': line.product_id.id,
|
'product_id': line.product_id.id,
|
||||||
'product_uom_qty': abs(line.qty),
|
'product_uom_qty': abs(line.qty),
|
||||||
'state': 'draft',
|
'state': 'draft',
|
||||||
'location_id': location_id if line.qty >= 0 else destination_id,
|
'location_id': location_id if line.qty >= 0 else destination_id,
|
||||||
'location_dest_id': destination_id if line.qty >= 0 else location_id,
|
'location_dest_id': destination_id if line.qty >= 0 else return_pick_type != picking_type and return_pick_type.default_location_dest_id.id or location_id,
|
||||||
})
|
})
|
||||||
if picking_id:
|
|
||||||
picking_id.action_confirm()
|
# prefer associating the regular order picking, not the return
|
||||||
picking_id.force_assign()
|
order.write({'picking_id': order_picking.id or return_picking.id})
|
||||||
order.set_pack_operation_lot()
|
|
||||||
picking_id.action_done()
|
if return_picking:
|
||||||
elif Move:
|
order._force_picking_done(return_picking)
|
||||||
Move.action_confirm()
|
if order_picking:
|
||||||
Move.force_assign()
|
order._force_picking_done(order_picking)
|
||||||
Move.action_done()
|
|
||||||
|
# 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:
|
||||||
|
moves.action_confirm()
|
||||||
|
moves.force_assign()
|
||||||
|
moves.action_done()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_pack_operation_lot(self):
|
def _force_picking_done(self, picking):
|
||||||
|
"""Force picking in order to be set as done."""
|
||||||
|
self.ensure_one()
|
||||||
|
picking.action_confirm()
|
||||||
|
picking.force_assign()
|
||||||
|
self.set_pack_operation_lot(picking)
|
||||||
|
picking.action_done()
|
||||||
|
|
||||||
|
def set_pack_operation_lot(self, picking=None):
|
||||||
"""Set Serial/Lot number in pack operations to mark the pack operation done."""
|
"""Set Serial/Lot number in pack operations to mark the pack operation done."""
|
||||||
|
|
||||||
StockProductionLot = self.env['stock.production.lot']
|
StockProductionLot = self.env['stock.production.lot']
|
||||||
PosPackOperationLot = self.env['pos.pack.operation.lot']
|
PosPackOperationLot = self.env['pos.pack.operation.lot']
|
||||||
|
|
||||||
|
if not picking:
|
||||||
|
picking = order.picking_id
|
||||||
|
|
||||||
for order in self:
|
for order in self:
|
||||||
for pack_operation in order.picking_id.pack_operation_ids:
|
for pack_operation in picking.pack_operation_ids:
|
||||||
qty = 0
|
qty = 0
|
||||||
qty_done = 0
|
qty_done = 0
|
||||||
pack_lots = []
|
pack_lots = []
|
||||||
@@ -868,15 +902,15 @@ class ReportSaleDetails(models.AbstractModel):
|
|||||||
'payments': payments,
|
'payments': payments,
|
||||||
'company_name': self.env.user.company_id.name,
|
'company_name': self.env.user.company_id.name,
|
||||||
'taxes': taxes.values(),
|
'taxes': taxes.values(),
|
||||||
'products': [{
|
'products': sorted([{
|
||||||
'product_id': product.id,
|
'product_id': product.id,
|
||||||
'product_name': product.name[:20],
|
'product_name': product.name,
|
||||||
'code': product.default_code,
|
'code': product.default_code,
|
||||||
'quantity': qty,
|
'quantity': qty,
|
||||||
'price_unit': price_unit,
|
'price_unit': price_unit,
|
||||||
'discount': discount,
|
'discount': discount,
|
||||||
'uom': product.uom_id.name
|
'uom': product.uom_id.name
|
||||||
} for (product, price_unit, discount), qty in products_sold.items()]
|
} for (product, price_unit, discount), qty in products_sold.items()], key=lambda l: l['product_name'])
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ class PosSession(models.Model):
|
|||||||
for session in self:
|
for session in self:
|
||||||
company_id = session.config_id.journal_id.company_id.id
|
company_id = session.config_id.journal_id.company_id.id
|
||||||
orders = session.order_ids.filtered(lambda order: order.state == 'paid')
|
orders = session.order_ids.filtered(lambda order: order.state == 'paid')
|
||||||
move = self.env['pos.order'].with_context(force_company=company_id)._create_account_move(session.start_at, session.name, session.config_id.journal_id.id, company_id)
|
journal_id = self.env['ir.config_parameter'].sudo().get_param(
|
||||||
|
'pos.closing.journal_id', default=session.config_id.journal_id.id)
|
||||||
|
move = self.env['pos.order'].with_context(force_company=company_id)._create_account_move(session.start_at, session.name, int(journal_id), company_id)
|
||||||
orders.with_context(force_company=company_id)._create_account_move_line(session, move)
|
orders.with_context(force_company=company_id)._create_account_move_line(session, move)
|
||||||
for order in session.order_ids.filtered(lambda o: o.state != 'done'):
|
for order in session.order_ids.filtered(lambda o: o.state != 'done'):
|
||||||
if order.state not in ('paid', 'invoiced'):
|
if order.state not in ('paid', 'invoiced'):
|
||||||
|
|||||||
@@ -871,7 +871,7 @@
|
|||||||
<div class='orderlines' line-ratio='0.6'>
|
<div class='orderlines' line-ratio='0.6'>
|
||||||
<t t-foreach='products' t-as='line'>
|
<t t-foreach='products' t-as='line'>
|
||||||
<line>
|
<line>
|
||||||
<left><t t-esc='line.product_name' /></left>
|
<left><t t-esc='line.product_name.substr(0,20)' /></left>
|
||||||
<right>
|
<right>
|
||||||
<value value-decimals='2' value-autoint='on'>
|
<value value-decimals='2' value-autoint='on'>
|
||||||
<t t-esc='line.quantity' />
|
<t t-esc='line.quantity' />
|
||||||
|
|||||||
@@ -119,9 +119,208 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
|||||||
# I test that the generated journal entry is attached to the PoS order
|
# I test that the generated journal entry is attached to the PoS order
|
||||||
self.assertTrue(self.pos_order_pos0.account_move, "Journal entry has not been attached to Pos order.")
|
self.assertTrue(self.pos_order_pos0.account_move, "Journal entry has not been attached to Pos order.")
|
||||||
|
|
||||||
|
def test_order_to_picking(self):
|
||||||
|
"""
|
||||||
|
In order to test the Point of Sale in module, I will do three orders from the sale to the payment,
|
||||||
|
invoicing + picking, but will only check the picking consistency in the end.
|
||||||
|
|
||||||
|
TODO: Check the negative picking after changing the picking relation to One2many (also for a mixed use case),
|
||||||
|
check the quantity, the locations and return picking logic
|
||||||
|
"""
|
||||||
|
# I click on create a new session button
|
||||||
|
self.pos_config.open_session_cb()
|
||||||
|
|
||||||
|
# I create a PoS order with 2 units of PCSC234 at 450 EUR
|
||||||
|
# and 3 units of PCSC349 at 300 EUR.
|
||||||
|
self.pos_order_pos1 = self.PosOrder.create({
|
||||||
|
'company_id': self.company_id,
|
||||||
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
||||||
|
'partner_id': self.partner1.id,
|
||||||
|
'lines': [(0, 0, {
|
||||||
|
'name': "OL/0001",
|
||||||
|
'product_id': self.product3.id,
|
||||||
|
'price_unit': 450,
|
||||||
|
'discount': 0.0,
|
||||||
|
'qty': 2.0,
|
||||||
|
}), (0, 0, {
|
||||||
|
'name': "OL/0002",
|
||||||
|
'product_id': self.product4.id,
|
||||||
|
'price_unit': 300,
|
||||||
|
'discount': 0.0,
|
||||||
|
'qty': 3.0,
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
|
||||||
|
# I click on the "Make Payment" wizard to pay the PoS order with the total amount (2*450 + 3*300 = 1800)
|
||||||
|
context_make_payment = {
|
||||||
|
"active_ids": [self.pos_order_pos1.id],
|
||||||
|
"active_id": self.pos_order_pos1.id
|
||||||
|
}
|
||||||
|
self.pos_make_payment_2 = self.PosMakePayment.with_context(context_make_payment).create({
|
||||||
|
'amount': 1800
|
||||||
|
})
|
||||||
|
|
||||||
|
# I click on the validate button to register the payment.
|
||||||
|
context_payment = {'active_id': self.pos_order_pos1.id}
|
||||||
|
self.pos_make_payment_2.with_context(context_payment).check()
|
||||||
|
|
||||||
|
# I check that the order is marked as paid
|
||||||
|
self.assertEqual(
|
||||||
|
self.pos_order_pos1.state,
|
||||||
|
'paid',
|
||||||
|
'Order should be in paid state.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# I generate the journal entries
|
||||||
|
self.pos_order_pos1._create_account_move_line()
|
||||||
|
|
||||||
|
# I test that the generated journal entry is attached to the PoS order
|
||||||
|
self.assertTrue(
|
||||||
|
self.pos_order_pos1.account_move,
|
||||||
|
"Journal entry has not been attached to Pos order."
|
||||||
|
)
|
||||||
|
|
||||||
|
# I test that the pickings are created as expected
|
||||||
|
# One picking attached and having all the positive move lines in the correct state
|
||||||
|
self.pos_order_pos1.create_picking()
|
||||||
|
self.assertEqual(
|
||||||
|
self.pos_order_pos1.picking_id.state,
|
||||||
|
'done',
|
||||||
|
'Picking should be in done state.'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.pos_order_pos1.picking_id.move_lines.mapped('state'),
|
||||||
|
['done', 'done'],
|
||||||
|
'Move Lines should be in done state.'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.pos_order_pos2 = self.PosOrder.create({
|
||||||
|
'company_id': self.company_id,
|
||||||
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
||||||
|
'partner_id': self.partner1.id,
|
||||||
|
'lines': [(0, 0, {
|
||||||
|
'name': "OL/0003",
|
||||||
|
'product_id': self.product3.id,
|
||||||
|
'price_unit': 450,
|
||||||
|
'discount': 0.0,
|
||||||
|
'qty': (-2.0),
|
||||||
|
}), (0, 0, {
|
||||||
|
'name': "OL/0004",
|
||||||
|
'product_id': self.product4.id,
|
||||||
|
'price_unit': 300,
|
||||||
|
'discount': 0.0,
|
||||||
|
'qty': (-3.0),
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
|
||||||
|
# I click on the "Make Payment" wizard to pay the PoS order with the total amount (-2*450 + -3*300 = -1800)
|
||||||
|
context_make_payment = {
|
||||||
|
"active_ids": [self.pos_order_pos2.id],
|
||||||
|
"active_id": self.pos_order_pos2.id
|
||||||
|
}
|
||||||
|
self.pos_make_payment_3 = self.PosMakePayment.with_context(context_make_payment).create({
|
||||||
|
'amount': (-1800)
|
||||||
|
})
|
||||||
|
|
||||||
|
# I click on the validate button to register the payment.
|
||||||
|
context_payment = {'active_id': self.pos_order_pos2.id}
|
||||||
|
self.pos_make_payment_3.with_context(context_payment).check()
|
||||||
|
|
||||||
|
# I check that the order is marked as paid
|
||||||
|
self.assertEqual(
|
||||||
|
self.pos_order_pos2.state,
|
||||||
|
'paid',
|
||||||
|
'Order should be in paid state.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# I generate the journal entries
|
||||||
|
self.pos_order_pos2._create_account_move_line()
|
||||||
|
|
||||||
|
# I test that the generated journal entry is attached to the PoS order
|
||||||
|
self.assertTrue(
|
||||||
|
self.pos_order_pos2.account_move,
|
||||||
|
"Journal entry has not been attached to PoS order."
|
||||||
|
)
|
||||||
|
|
||||||
|
# I test that the pickings are created as expected
|
||||||
|
# One picking attached and having all the positive move lines in the correct state
|
||||||
|
self.pos_order_pos2.create_picking()
|
||||||
|
self.assertEqual(
|
||||||
|
self.pos_order_pos2.picking_id.state,
|
||||||
|
'done',
|
||||||
|
'Picking should be in done state.'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.pos_order_pos2.picking_id.move_lines.mapped('state'),
|
||||||
|
['done', 'done'],
|
||||||
|
'Move Lines should be in done state.'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.pos_order_pos3 = self.PosOrder.create({
|
||||||
|
'company_id': self.company_id,
|
||||||
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
||||||
|
'partner_id': self.partner1.id,
|
||||||
|
'lines': [(0, 0, {
|
||||||
|
'name': "OL/0005",
|
||||||
|
'product_id': self.product3.id,
|
||||||
|
'price_unit': 450,
|
||||||
|
'discount': 0.0,
|
||||||
|
'qty': (-2.0),
|
||||||
|
}), (0, 0, {
|
||||||
|
'name': "OL/0006",
|
||||||
|
'product_id': self.product4.id,
|
||||||
|
'price_unit': 300,
|
||||||
|
'discount': 0.0,
|
||||||
|
'qty': 3.0,
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
|
||||||
|
# I click on the "Make Payment" wizard to pay the PoS order with the total amount (-2*450 + 3*300 = 0)
|
||||||
|
context_make_payment = {
|
||||||
|
"active_ids": [self.pos_order_pos3.id],
|
||||||
|
"active_id": self.pos_order_pos3.id
|
||||||
|
}
|
||||||
|
self.pos_make_payment_4 = self.PosMakePayment.with_context(context_make_payment).create({
|
||||||
|
'amount': 0
|
||||||
|
})
|
||||||
|
|
||||||
|
# I click on the validate button to register the payment.
|
||||||
|
context_payment = {'active_id': self.pos_order_pos3.id}
|
||||||
|
self.pos_make_payment_4.with_context(context_payment).check()
|
||||||
|
|
||||||
|
# I check that the order is marked as paid
|
||||||
|
self.assertEqual(
|
||||||
|
self.pos_order_pos3.state,
|
||||||
|
'paid',
|
||||||
|
'Order should be in paid state.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# I generate the journal entries
|
||||||
|
self.pos_order_pos3._create_account_move_line()
|
||||||
|
|
||||||
|
# I test that the generated journal entry is attached to the PoS order
|
||||||
|
self.assertTrue(
|
||||||
|
self.pos_order_pos3.account_move,
|
||||||
|
"Journal entry has not been attached to PoS order."
|
||||||
|
)
|
||||||
|
|
||||||
|
# I test that the pickings are created as expected
|
||||||
|
# One picking attached and having all the positive move lines in the correct state
|
||||||
|
self.pos_order_pos3.create_picking()
|
||||||
|
self.assertEqual(
|
||||||
|
self.pos_order_pos3.picking_id.state,
|
||||||
|
'done',
|
||||||
|
'Picking should be in done state.'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.pos_order_pos3.picking_id.move_lines.mapped('state'),
|
||||||
|
['done'],
|
||||||
|
'Move Lines should be in done state.'
|
||||||
|
)
|
||||||
|
|
||||||
def test_order_to_invoice(self):
|
def test_order_to_invoice(self):
|
||||||
|
|
||||||
#I create a new PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl)
|
# I create a new PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl)
|
||||||
self.pos_order_pos1 = self.PosOrder.create({
|
self.pos_order_pos1 = self.PosOrder.create({
|
||||||
'company_id': self.company_id,
|
'company_id': self.company_id,
|
||||||
'partner_id': self.partner1.id,
|
'partner_id': self.partner1.id,
|
||||||
|
|||||||
@@ -392,8 +392,7 @@ class ProductProduct(models.Model):
|
|||||||
name = variant and "%s (%s)" % (product.name, variant) or product.name
|
name = variant and "%s (%s)" % (product.name, variant) or product.name
|
||||||
sellers = []
|
sellers = []
|
||||||
if partner_ids:
|
if partner_ids:
|
||||||
if variant:
|
sellers = [x for x in product.seller_ids if (x.name.id in partner_ids) and (x.product_id == product)]
|
||||||
sellers = [x for x in product.seller_ids if (x.name.id in partner_ids) and (x.product_id == product)]
|
|
||||||
if not sellers:
|
if not sellers:
|
||||||
sellers = [x for x in product.seller_ids if (x.name.id in partner_ids) and not x.product_id]
|
sellers = [x for x in product.seller_ids if (x.name.id in partner_ids) and not x.product_id]
|
||||||
if sellers:
|
if sellers:
|
||||||
|
|||||||
@@ -124,8 +124,8 @@ class StockWarehouse(models.Model):
|
|||||||
return routes
|
return routes
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _handle_renaming(self, name, code):
|
def _update_name_and_code(self, name, code):
|
||||||
res = super(StockWarehouse, self)._handle_renaming(name, code)
|
res = super(StockWarehouse, self)._update_name_and_code(name, code)
|
||||||
warehouse = self[0]
|
warehouse = self[0]
|
||||||
#change the buy procurement rule name
|
#change the buy procurement rule name
|
||||||
if warehouse.buy_pull_id:
|
if warehouse.buy_pull_id:
|
||||||
@@ -133,8 +133,8 @@ class StockWarehouse(models.Model):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def change_route(self):
|
def _update_routes(self):
|
||||||
res = super(StockWarehouse, self).change_route()
|
res = super(StockWarehouse, self)._update_routes()
|
||||||
for warehouse in self:
|
for warehouse in self:
|
||||||
if warehouse.in_type_id.default_location_dest_id != warehouse.buy_pull_id.location_id:
|
if warehouse.in_type_id.default_location_dest_id != warehouse.buy_pull_id.location_id:
|
||||||
warehouse.buy_pull_id.write({'location_id': warehouse.in_type_id.default_location_dest_id.id})
|
warehouse.buy_pull_id.write({'location_id': warehouse.in_type_id.default_location_dest_id.id})
|
||||||
|
|||||||
@@ -653,7 +653,7 @@ class ResourceCalendarAttendance(models.Model):
|
|||||||
date_to = fields.Date(string='End Date')
|
date_to = fields.Date(string='End Date')
|
||||||
hour_from = fields.Float(string='Work from', required=True, index=True, help="Start and End time of working.")
|
hour_from = fields.Float(string='Work from', required=True, index=True, help="Start and End time of working.")
|
||||||
hour_to = fields.Float(string='Work to', required=True)
|
hour_to = fields.Float(string='Work to', required=True)
|
||||||
calendar_id = fields.Many2one("resource.calendar", string="Resource's Calendar", required=True)
|
calendar_id = fields.Many2one("resource.calendar", string="Resource's Calendar", required=True, ondelete='cascade')
|
||||||
|
|
||||||
|
|
||||||
def hours_time_string(hours):
|
def hours_time_string(hours):
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ class SaleOrder(models.Model):
|
|||||||
return super(SaleOrder, self)._track_subtype(init_values)
|
return super(SaleOrder, self)._track_subtype(init_values)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@api.onchange('partner_shipping_id')
|
@api.onchange('partner_shipping_id', 'partner_id')
|
||||||
def onchange_partner_shipping_id(self):
|
def onchange_partner_shipping_id(self):
|
||||||
"""
|
"""
|
||||||
Trigger the change of fiscal position when the shipping address is modified.
|
Trigger the change of fiscal position when the shipping address is modified.
|
||||||
|
|||||||
@@ -67,9 +67,8 @@ class SaleAdvancePaymentInv(models.TransientModel):
|
|||||||
if self.product_id.id:
|
if self.product_id.id:
|
||||||
account_id = self.product_id.property_account_income_id.id
|
account_id = self.product_id.property_account_income_id.id
|
||||||
if not account_id:
|
if not account_id:
|
||||||
prop = ir_property_obj.get('property_account_income_categ_id', 'product.category')
|
inc_acc = ir_property_obj.get('property_account_income_categ_id', 'product.category')
|
||||||
prop_id = prop and prop.id or False
|
account_id = order.fiscal_position_id.map_account(inc_acc).id if inc_acc else False
|
||||||
account_id = order.fiscal_position_id.map_account(prop_id)
|
|
||||||
if not account_id:
|
if not account_id:
|
||||||
raise UserError(
|
raise UserError(
|
||||||
_('There is no income account defined for this product: "%s". You may have to install a chart of account from Accounting app, settings menu.') %
|
_('There is no income account defined for this product: "%s". You may have to install a chart of account from Accounting app, settings menu.') %
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ from odoo import api, fields, models, registry, _
|
|||||||
from odoo.osv import expression
|
from odoo.osv import expression
|
||||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare, float_round
|
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare, float_round
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class ProcurementGroup(models.Model):
|
class ProcurementGroup(models.Model):
|
||||||
_inherit = 'procurement.group'
|
_inherit = 'procurement.group'
|
||||||
@@ -172,8 +175,11 @@ class ProcurementOrder(models.Model):
|
|||||||
'propagate': self.rule_id.propagate,
|
'propagate': self.rule_id.propagate,
|
||||||
'priority': self.priority,
|
'priority': self.priority,
|
||||||
}
|
}
|
||||||
# compatibility
|
|
||||||
_run_move_create = _get_stock_move_values
|
def _run_move_create(self):
|
||||||
|
# FIXME - remove me in master/saas-14
|
||||||
|
_logger.warning("'_run_move_create' has been renamed into '_get_stock_move_values'... Overrides are ignored")
|
||||||
|
return self._get_stock_move_values()
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _run(self):
|
def _run(self):
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ from odoo.tools.translate import _
|
|||||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Quant(models.Model):
|
class Quant(models.Model):
|
||||||
""" Quants are the smallest unit of stock physical instances """
|
""" Quants are the smallest unit of stock physical instances """
|
||||||
@@ -261,8 +265,17 @@ class Quant(models.Model):
|
|||||||
|
|
||||||
# create the quant as superuser, because we want to restrict the creation of quant manually: we should always use this method to create quants
|
# create the quant as superuser, because we want to restrict the creation of quant manually: we should always use this method to create quants
|
||||||
return self.sudo().create(vals)
|
return self.sudo().create(vals)
|
||||||
# compatibility method
|
|
||||||
_quant_create = _quant_create_from_move
|
@api.model
|
||||||
|
def _quant_create(self, qty, move, lot_id=False, owner_id=False,
|
||||||
|
src_package_id=False, dest_package_id=False,
|
||||||
|
force_location_from=False, force_location_to=False):
|
||||||
|
# FIXME - remove me in master/saas-14
|
||||||
|
_logger.warning("'_quant_create' has been renamed into '_quant_create_from_move'... Overrides are ignored")
|
||||||
|
return self._quant_create_from_move(
|
||||||
|
qty, move, lot_id=lot_id, owner_id=owner_id,
|
||||||
|
src_package_id=src_package_id, dest_package_id=dest_package_id,
|
||||||
|
force_location_from=force_location_from, force_location_to=force_location_to)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _quant_update_from_move(self, move, location_dest_id, dest_package_id, lot_id=False, entire_pack=False):
|
def _quant_update_from_move(self, move, location_dest_id, dest_package_id, lot_id=False, entire_pack=False):
|
||||||
@@ -275,8 +288,12 @@ class Quant(models.Model):
|
|||||||
if not entire_pack:
|
if not entire_pack:
|
||||||
vals.update({'package_id': dest_package_id})
|
vals.update({'package_id': dest_package_id})
|
||||||
self.write(vals)
|
self.write(vals)
|
||||||
# compatibility method
|
|
||||||
move_quants_write = _quant_update_from_move
|
@api.multi
|
||||||
|
def move_quants_write(self, move, location_dest_id, dest_package_id, lot_id=False, entire_pack=False):
|
||||||
|
# FIXME - remove me in master/saas-14
|
||||||
|
_logger.warning("'move_quants_write' has been renamed into '_quant_update_from_move'... Overrides are ignored")
|
||||||
|
return self._quant_update_from_move(move, location_dest_id, dest_package_id, lot_id=lot_id, entire_pack=entire_pack)
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
def _quant_reconcile_negative(self, move):
|
def _quant_reconcile_negative(self, move):
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ from odoo.addons import decimal_precision as dp
|
|||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Warehouse(models.Model):
|
class Warehouse(models.Model):
|
||||||
_name = "stock.warehouse"
|
_name = "stock.warehouse"
|
||||||
@@ -452,7 +456,11 @@ class Warehouse(models.Model):
|
|||||||
'product_categ_selectable': True,
|
'product_categ_selectable': True,
|
||||||
'supplied_wh_id': self.id,
|
'supplied_wh_id': self.id,
|
||||||
'supplier_wh_id': supplier_warehouse.id}
|
'supplier_wh_id': supplier_warehouse.id}
|
||||||
_get_inter_wh_route = _get_inter_warehouse_route_values # compatibility
|
|
||||||
|
def _get_inter_wh_route(self, supplier_warehouse):
|
||||||
|
# FIXME - remove me in master/saas-14
|
||||||
|
_logger.warning("'_get_inter_wh_route' has been renamed into '_get_inter_warehouse_route_values'... Overrides are ignored")
|
||||||
|
return self._get_inter_warehouse_route_values(supplier_warehouse)
|
||||||
|
|
||||||
def _get_crossdock_route_values(self):
|
def _get_crossdock_route_values(self):
|
||||||
return {
|
return {
|
||||||
@@ -463,8 +471,10 @@ class Warehouse(models.Model):
|
|||||||
'active': self.delivery_steps != 'ship_only' and self.reception_steps != 'one_step',
|
'active': self.delivery_steps != 'ship_only' and self.reception_steps != 'one_step',
|
||||||
'sequence': 20}
|
'sequence': 20}
|
||||||
|
|
||||||
def _get_crossdock_route(self, route_name): # compatibility
|
def _get_crossdock_route(self, route_name):
|
||||||
return self._get_crossdock_route_values()
|
# FIXME - remove me in master/saas-14
|
||||||
|
_logger.warning("'_get_crossdock_route' has been renamed into '_get_crossdock_route_values'... Overrides are ignored")
|
||||||
|
return self._get_crossdock_route_values(route_name)
|
||||||
|
|
||||||
# Pull / Push tools
|
# Pull / Push tools
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
@@ -503,9 +513,15 @@ class Warehouse(models.Model):
|
|||||||
'procure_method': 'make_to_order',
|
'procure_method': 'make_to_order',
|
||||||
'active': True}, name_suffix=_('MTO'))
|
'active': True}, name_suffix=_('MTO'))
|
||||||
return pull_rules_list
|
return pull_rules_list
|
||||||
_get_mto_pull_rule = _get_mto_pull_rules_values # compatibility
|
|
||||||
|
|
||||||
def _get_push_pull_rules(self, active, values, new_route_id): # compatibility
|
def _get_mto_pull_rule(self, route_values):
|
||||||
|
# FIXME - remove me in master/saas-14
|
||||||
|
_logger.warning("'_get_mto_pull_rule' has been renamed into '_get_mto_pull_rules_values'... Overrides are ignored")
|
||||||
|
return self._get_mto_pull_rules_values(route_values)
|
||||||
|
|
||||||
|
def _get_push_pull_rules(self, active, values, new_route_id):
|
||||||
|
# FIXME - remove me in master/saas-14
|
||||||
|
_logger.warning("'_get_push_pull_rules' has been renamed into '_get_push_pull_rules_values'... Overrides are ignored")
|
||||||
return self._get_push_pull_rules_values(values, values={'active': active, 'route_id': new_route_id})
|
return self._get_push_pull_rules_values(values, values={'active': active, 'route_id': new_route_id})
|
||||||
|
|
||||||
def _get_supply_pull_rules_values(self, route_values, values=None):
|
def _get_supply_pull_rules_values(self, route_values, values=None):
|
||||||
@@ -523,7 +539,11 @@ class Warehouse(models.Model):
|
|||||||
if delivery_new and warehouse.delivery_steps != delivery_new and (warehouse.delivery_steps == 'ship_only' or delivery_new == 'ship_only'):
|
if delivery_new and warehouse.delivery_steps != delivery_new and (warehouse.delivery_steps == 'ship_only' or delivery_new == 'ship_only'):
|
||||||
change_to_multiple = warehouse.delivery_steps == 'ship_only'
|
change_to_multiple = warehouse.delivery_steps == 'ship_only'
|
||||||
warehouse._check_delivery_resupply(output_loc, change_to_multiple)
|
warehouse._check_delivery_resupply(output_loc, change_to_multiple)
|
||||||
_check_resupply = _update_reception_delivery_resupply
|
|
||||||
|
def _check_resupply(self, reception_new, delivery_new):
|
||||||
|
# FIXME - remove me in master/saas-14
|
||||||
|
_logger.warning("'_check_resupply' has been renamed into '_update_reception_delivery_resupply'... Overrides are ignored")
|
||||||
|
return self._update_reception_delivery_resupply(reception_new, delivery_new)
|
||||||
|
|
||||||
def _check_delivery_resupply(self, new_location, change_to_multiple):
|
def _check_delivery_resupply(self, new_location, change_to_multiple):
|
||||||
""" Check if the resupply routes from this warehouse follow the changes of number of delivery steps
|
""" Check if the resupply routes from this warehouse follow the changes of number of delivery steps
|
||||||
@@ -567,7 +587,12 @@ class Warehouse(models.Model):
|
|||||||
self._create_or_update_crossdock_route(routes_data)
|
self._create_or_update_crossdock_route(routes_data)
|
||||||
self._create_or_update_mto_pull(routes_data)
|
self._create_or_update_mto_pull(routes_data)
|
||||||
return True
|
return True
|
||||||
change_route = _update_routes
|
|
||||||
|
@api.multi
|
||||||
|
def change_route(self):
|
||||||
|
# FIXME - remove me in master/saas-14
|
||||||
|
_logger.warning("'change_route' has been renamed into '_update_routes'... Overrides are ignored")
|
||||||
|
return self._update_routes()
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
def _update_picking_type(self):
|
def _update_picking_type(self):
|
||||||
@@ -597,7 +622,12 @@ class Warehouse(models.Model):
|
|||||||
warehouse.pack_type_id.sequence_id.write(sequence_data['pack_type_id'])
|
warehouse.pack_type_id.sequence_id.write(sequence_data['pack_type_id'])
|
||||||
warehouse.pick_type_id.sequence_id.write(sequence_data['pick_type_id'])
|
warehouse.pick_type_id.sequence_id.write(sequence_data['pick_type_id'])
|
||||||
warehouse.int_type_id.sequence_id.write(sequence_data['int_type_id'])
|
warehouse.int_type_id.sequence_id.write(sequence_data['int_type_id'])
|
||||||
_handle_renaming = _update_name_and_code
|
|
||||||
|
@api.multi
|
||||||
|
def _handle_renaming(self, new_name=False, new_code=False):
|
||||||
|
# FIXME - remove me in master/saas-14
|
||||||
|
_logger.warning("'_handle_renaming' has been renamed into '_update_name_and_code'... Overrides are ignored")
|
||||||
|
return self._update_name_and_code(new_name=new_name, new_code=new_code)
|
||||||
|
|
||||||
def _update_location_reception(self, new_reception_step):
|
def _update_location_reception(self, new_reception_step):
|
||||||
switch_warehouses = self.filtered(lambda wh: wh.reception_steps != new_reception_step and not wh._location_used(wh.wh_input_stock_loc_id))
|
switch_warehouses = self.filtered(lambda wh: wh.reception_steps != new_reception_step and not wh._location_used(wh.wh_input_stock_loc_id))
|
||||||
|
|||||||
@@ -793,18 +793,19 @@ var BufferedDataSet = DataSetStatic.extend({
|
|||||||
var def = $.Deferred();
|
var def = $.Deferred();
|
||||||
this.mutex.exec(function () {
|
this.mutex.exec(function () {
|
||||||
var dirty = false;
|
var dirty = false;
|
||||||
_.each(data, function (v, k) {
|
// _.each is broken if a field "length" is present
|
||||||
if (!_.isEqual(v, cached.values[k])) {
|
for (var k in data) {
|
||||||
|
if (!_.isEqual(data[k], cached.values[k])) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
if (_.isEqual(v, cached.from_read[k])) { // clean changes
|
if (_.isEqual(data[k], cached.from_read[k])) { // clean changes
|
||||||
delete cached.changes[k];
|
delete cached.changes[k];
|
||||||
} else {
|
} else {
|
||||||
cached.changes[k] = v;
|
cached.changes[k] = data[k];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
delete data[k];
|
delete data[k];
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
self._update_cache(id, options);
|
self._update_cache(id, options);
|
||||||
|
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
|
|||||||
@@ -470,11 +470,12 @@ var ListView = View.extend({
|
|||||||
self.records.remove(record);
|
self.records.remove(record);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_.each(values, function (value, key) {
|
// _.each is broken if a field "length" is present
|
||||||
|
for (var key in values) {
|
||||||
if (fields[key] && fields[key].type === 'many2many')
|
if (fields[key] && fields[key].type === 'many2many')
|
||||||
record.set(key + '__display', false, {silent: true});
|
record.set(key + '__display', false, {silent: true});
|
||||||
record.set(key, value, {silent: true});
|
record.set(key, values[key], {silent: true});
|
||||||
});
|
}
|
||||||
record.trigger('change', record);
|
record.trigger('change', record);
|
||||||
|
|
||||||
/* When a record is reloaded, there is a rendering lag because of the addition/suppression of
|
/* When a record is reloaded, there is a rendering lag because of the addition/suppression of
|
||||||
|
|||||||
@@ -196,6 +196,13 @@ odoo.define_section('list.edition', ['web.data', 'web.ListView', 'web.data_manag
|
|||||||
mock.add('demo:onchange', function () {
|
mock.add('demo:onchange', function () {
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
mock.add('demo:fields_get', function () {
|
||||||
|
return {
|
||||||
|
a: {type: 'char', string: "A"},
|
||||||
|
b: {type: 'char', string: "B"},
|
||||||
|
c: {type: 'char', string: "C"}
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test('newrecord', function (assert, data, ListView, data_manager) {
|
test('newrecord', function (assert, data, ListView, data_manager) {
|
||||||
@@ -263,6 +270,13 @@ odoo.define_section('list.edition.events', ['web.data', 'web.ListView', 'web.dat
|
|||||||
mock.add('demo:read', function () {
|
mock.add('demo:read', function () {
|
||||||
return [{ id: 1, a: 'foo', b: 'bar', c: 'baz' }];
|
return [{ id: 1, a: 'foo', b: 'bar', c: 'baz' }];
|
||||||
});
|
});
|
||||||
|
mock.add('demo:fields_get', function () {
|
||||||
|
return {
|
||||||
|
a: {type: 'char', string: "A"},
|
||||||
|
b: {type: 'char', string: "B"},
|
||||||
|
c: {type: 'char', string: "C"}
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test('edition events',function (assert, data, ListView, data_manager) {
|
test('edition events',function (assert, data, ListView, data_manager) {
|
||||||
@@ -345,6 +359,13 @@ odoo.define_section('list.edition.onwrite', ['web.data', 'web.ListView', 'web.da
|
|||||||
mock.add('demo:default_get', function () { return {}; });
|
mock.add('demo:default_get', function () { return {}; });
|
||||||
mock.add('demo:create', function () { return 1; });
|
mock.add('demo:create', function () { return 1; });
|
||||||
mock.add('demo:on_write', function () { return [42]; });
|
mock.add('demo:on_write', function () { return [42]; });
|
||||||
|
mock.add('demo:fields_get', function () {
|
||||||
|
return {
|
||||||
|
a: {type: 'char', string: "A"},
|
||||||
|
b: {type: 'char', string: "B"},
|
||||||
|
c: {type: 'char', string: "C"}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
var ds = new data.DataSetStatic(null, 'demo', null, []);
|
var ds = new data.DataSetStatic(null, 'demo', null, []);
|
||||||
var fields_view = data_manager._postprocess_fvg({
|
var fields_view = data_manager._postprocess_fvg({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ odoo.define_section('list.buttons', ['web.ListView', 'web.data', 'web.data_manag
|
|||||||
|
|
||||||
test('record-deletion', function (assert, ListView, data, data_manager) {
|
test('record-deletion', function (assert, ListView, data, data_manager) {
|
||||||
assert.expect(2);
|
assert.expect(2);
|
||||||
|
|
||||||
mock.add('demo:read', function (args, kwargs) {
|
mock.add('demo:read', function (args, kwargs) {
|
||||||
if (_.isEqual(args[0], [1, 2, 3])) {
|
if (_.isEqual(args[0], [1, 2, 3])) {
|
||||||
return [
|
return [
|
||||||
@@ -18,7 +18,9 @@ odoo.define_section('list.buttons', ['web.ListView', 'web.data', 'web.data_manag
|
|||||||
throw new Error(JSON.stringify(_.toArray(arguments)));
|
throw new Error(JSON.stringify(_.toArray(arguments)));
|
||||||
});
|
});
|
||||||
mock.add('/web/dataset/call_button', function () { return false; });
|
mock.add('/web/dataset/call_button', function () { return false; });
|
||||||
|
mock.add('demo:fields_get', function() {
|
||||||
|
return {a: {type: 'char', string: "A"}};
|
||||||
|
});
|
||||||
|
|
||||||
var ds = new data.DataSetStatic(null, 'demo', null, [1, 2, 3]);
|
var ds = new data.DataSetStatic(null, 'demo', null, [1, 2, 3]);
|
||||||
var fields_view = data_manager._postprocess_fvg({
|
var fields_view = data_manager._postprocess_fvg({
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import unittest
|
||||||
import odoo.tests
|
import odoo.tests
|
||||||
|
|
||||||
|
|
||||||
class WebSuite(odoo.tests.HttpCase):
|
class WebSuite(odoo.tests.HttpCase):
|
||||||
|
@unittest.skip('Memory leak in this test lead to phantomjs crash, making it unreliable')
|
||||||
def test_01_js(self):
|
def test_01_js(self):
|
||||||
self.phantom_js('/web/tests?mod=web',"","", login='admin')
|
self.phantom_js('/web/tests?mod=web',"","", login='admin')
|
||||||
|
|||||||
@@ -246,16 +246,13 @@ var DiagramView = View.extend({
|
|||||||
self.dataset.read_index(_.keys(self.fields_view.fields)).then(self.on_diagram_loaded);
|
self.dataset.read_index(_.keys(self.fields_view.fields)).then(self.on_diagram_loaded);
|
||||||
});
|
});
|
||||||
|
|
||||||
var form_fields = [self.parent_field];
|
pop.opened().then(function(){
|
||||||
var form_controller = pop.view_form;
|
var form_controller = pop.view_form;
|
||||||
|
var field = form_controller.fields[self.parent_field];
|
||||||
form_controller.on("load_record", self, function(){
|
if (field) {
|
||||||
_.each(form_fields, function(fld) {
|
|
||||||
if (!(fld in form_controller.fields)) { return; }
|
|
||||||
var field = form_controller.fields[fld];
|
|
||||||
field.$input.prop('disabled', true);
|
field.$input.prop('disabled', true);
|
||||||
field.$dropdown.unbind();
|
field.$dropdown.unbind();
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -274,16 +271,13 @@ var DiagramView = View.extend({
|
|||||||
}
|
}
|
||||||
}).open();
|
}).open();
|
||||||
|
|
||||||
var form_controller = pop.view_form;
|
pop.opened().then(function(){
|
||||||
var form_fields = [this.parent_field];
|
var form_controller = pop.view_form;
|
||||||
|
var field = form_controller.fields[self.parent_field];
|
||||||
form_controller.on("load_record", self, function(){
|
if (field) {
|
||||||
_.each(form_fields, function(fld) {
|
|
||||||
if (!(fld in form_controller.fields)) { return; }
|
|
||||||
var field = form_controller.fields[fld];
|
|
||||||
field.set_value(self.id);
|
field.set_value(self.id);
|
||||||
field.dirty = true;
|
field.dirty = true;
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -326,12 +320,14 @@ var DiagramView = View.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var form_controller = pop.view_form;
|
pop.opened().then(function(){
|
||||||
form_controller.on("load_record", self, function(){
|
var form_controller = pop.view_form;
|
||||||
form_controller.fields[self.connectors.attrs.source].set_value(node_source_id);
|
var source_field = form_controller.fields[self.connectors.attrs.source];
|
||||||
form_controller.fields[self.connectors.attrs.source].dirty = true;
|
var destination_field = form_controller.fields[self.connectors.attrs.destination];
|
||||||
form_controller.fields[self.connectors.attrs.destination].set_value(node_dest_id);
|
source_field.set_value(node_source_id);
|
||||||
form_controller.fields[self.connectors.attrs.destination].dirty = true;
|
source_field.dirty = true;
|
||||||
|
destination_field.set_value(node_dest_id);
|
||||||
|
destination_field.dirty = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ class Planner(models.Model):
|
|||||||
module = self.env['ir.module.module'].sudo().search([('name', '=', module_name)], limit=1)
|
module = self.env['ir.module.module'].sudo().search([('name', '=', module_name)], limit=1)
|
||||||
if module:
|
if module:
|
||||||
params['id'] = module.id
|
params['id'] = module.id
|
||||||
|
else:
|
||||||
|
return "#show_enterprise"
|
||||||
return "/web#%s" % (urlencode(params),)
|
return "/web#%s" % (urlencode(params),)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ var PlannerDialog = Widget.extend({
|
|||||||
category_selector: 'div[menu-category-id]',
|
category_selector: 'div[menu-category-id]',
|
||||||
events: {
|
events: {
|
||||||
'click li a[href^="#"]:not([data-toggle="collapse"])': 'change_page',
|
'click li a[href^="#"]:not([data-toggle="collapse"])': 'change_page',
|
||||||
|
'click a[href^="#show_enterprise"]': 'show_enterprise',
|
||||||
'click button.mark_as_done': 'click_on_done',
|
'click button.mark_as_done': 'click_on_done',
|
||||||
'click a.btn-next': 'change_to_next_page',
|
'click a.btn-next': 'change_to_next_page',
|
||||||
'click .o_planner_close_block span': 'close_modal',
|
'click .o_planner_close_block span': 'close_modal',
|
||||||
@@ -420,7 +421,40 @@ var PlannerDialog = Widget.extend({
|
|||||||
destroy: function() {
|
destroy: function() {
|
||||||
this.$el.modal('hide');
|
this.$el.modal('hide');
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
}
|
},
|
||||||
|
show_enterprise: function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
var message = $(QWeb.render('EnterpriseUpgrade'));
|
||||||
|
var buttons = [
|
||||||
|
{
|
||||||
|
text: _t("Upgrade now"),
|
||||||
|
classes: 'btn-primary',
|
||||||
|
close: true,
|
||||||
|
click: this.confirm_upgrade,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: _t("Cancel"),
|
||||||
|
close: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
var dialog = new Dialog(this, {
|
||||||
|
size: 'medium',
|
||||||
|
buttons: buttons,
|
||||||
|
$content: $('<div>', {
|
||||||
|
html: message,
|
||||||
|
}),
|
||||||
|
title: _t("Odoo Enterprise"),
|
||||||
|
}).open();
|
||||||
|
|
||||||
|
// force dialog to be hover the planner
|
||||||
|
dialog.$el.parents('.modal').css('z-index', 1052);
|
||||||
|
return dialog;
|
||||||
|
},
|
||||||
|
confirm_upgrade: function() {
|
||||||
|
new Model("res.users").call("search_count", [[["share", "=", false]]]).then(function(data) {
|
||||||
|
window.location = "https://www.odoo.com/odoo-enterprise/upgrade?utm_medium=community_upgrade&num_users=" + data;
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ var ViewEditor = AceCommon.ViewEditor.extend({
|
|||||||
var args = {
|
var args = {
|
||||||
key: $(document.documentElement).data('view-xmlid'),
|
key: $(document.documentElement).data('view-xmlid'),
|
||||||
full: true,
|
full: true,
|
||||||
bundles: !!$('script[src*=".assets_common"]').length
|
bundles: this.$('.js_include_bundles')[0].checked
|
||||||
};
|
};
|
||||||
return ajax
|
return ajax
|
||||||
.jsonRpc('/website/customize_template_get', 'call', args)
|
.jsonRpc('/website/customize_template_get', 'call', args)
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ var Dashboard = Widget.extend(ControlPanelMixin, {
|
|||||||
// Check if the user is authenticated and has the right to make API calls
|
// Check if the user is authenticated and has the right to make API calls
|
||||||
if (!gapi.analytics.auth.getAuthResponse()) {
|
if (!gapi.analytics.auth.getAuthResponse()) {
|
||||||
this.display_unauthorized_message($analytics_components, 'not_connected');
|
this.display_unauthorized_message($analytics_components, 'not_connected');
|
||||||
} else if (gapi.analytics.auth.getAuthResponse() && gapi.analytics.auth.getAuthResponse().scope.indexOf('https://www.googleapis.com/auth/analytics ') === -1) {
|
} else if (gapi.analytics.auth.getAuthResponse() && gapi.analytics.auth.getAuthResponse().scope.indexOf('https://www.googleapis.com/auth/analytics') === -1) {
|
||||||
this.display_unauthorized_message($analytics_components, 'no_right');
|
this.display_unauthorized_message($analytics_components, 'no_right');
|
||||||
} else {
|
} else {
|
||||||
this.make_analytics_calls($analytics_components);
|
this.make_analytics_calls($analytics_components);
|
||||||
|
|||||||
@@ -135,9 +135,9 @@ odoo.define('website.website', function (require) {
|
|||||||
|
|
||||||
var error = function (title, message, url) {
|
var error = function (title, message, url) {
|
||||||
return new Dialog(null, {
|
return new Dialog(null, {
|
||||||
title: data.data ? data.data.arguments[0] : "",
|
title: title || "",
|
||||||
$content: $(qweb.render('website.error_dialog', {
|
$content: $(qweb.render('website.error_dialog', {
|
||||||
message: data.data ? data.data.arguments[1] : data.statusText,
|
message: message || "",
|
||||||
backend_url: url,
|
backend_url: url,
|
||||||
})),
|
})),
|
||||||
}).open();
|
}).open();
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ class MailGroup(http.Controller):
|
|||||||
def _get_archives(self, group_id):
|
def _get_archives(self, group_id):
|
||||||
MailMessage = request.env['mail.message']
|
MailMessage = request.env['mail.message']
|
||||||
groups = MailMessage._read_group_raw(
|
groups = MailMessage._read_group_raw(
|
||||||
[('model', '=', 'mail.channel'), ('res_id', '=', group_id)], ['subject', 'date'],
|
[('model', '=', 'mail.channel'), ('res_id', '=', group_id), ('message_type', '!=', 'notification')],
|
||||||
|
['subject', 'date'],
|
||||||
groupby=["date"], orderby="date desc")
|
groupby=["date"], orderby="date desc")
|
||||||
for group in groups:
|
for group in groups:
|
||||||
(r, label) = group['date']
|
(r, label) = group['date']
|
||||||
@@ -41,7 +42,7 @@ class MailGroup(http.Controller):
|
|||||||
# compute statistics
|
# compute statistics
|
||||||
month_date = datetime.today() - relativedelta.relativedelta(months=1)
|
month_date = datetime.today() - relativedelta.relativedelta(months=1)
|
||||||
messages = request.env['mail.message'].read_group(
|
messages = request.env['mail.message'].read_group(
|
||||||
[('model', '=', 'mail.channel'), ('date', '>=', fields.Datetime.to_string(month_date))],
|
[('model', '=', 'mail.channel'), ('date', '>=', fields.Datetime.to_string(month_date)), ('message_type', '!=', 'notification')],
|
||||||
[], ['res_id'])
|
[], ['res_id'])
|
||||||
message_data = dict((message['res_id'], message['res_id_count']) for message in messages)
|
message_data = dict((message['res_id'], message['res_id_count']) for message in messages)
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -17,6 +17,7 @@ Depends:
|
|||||||
node-less,
|
node-less,
|
||||||
postgresql-client,
|
postgresql-client,
|
||||||
python,
|
python,
|
||||||
|
python-babel,
|
||||||
python-dateutil,
|
python-dateutil,
|
||||||
python-decorator,
|
python-decorator,
|
||||||
python-docutils,
|
python-docutils,
|
||||||
@@ -32,7 +33,6 @@ Depends:
|
|||||||
python-passlib,
|
python-passlib,
|
||||||
python-psutil,
|
python-psutil,
|
||||||
python-psycopg2,
|
python-psycopg2,
|
||||||
python-pybabel,
|
|
||||||
python-pychart,
|
python-pychart,
|
||||||
python-pydot,
|
python-pydot,
|
||||||
python-pyparsing,
|
python-pyparsing,
|
||||||
|
|||||||
@@ -131,6 +131,10 @@ class IrModel(models.Model):
|
|||||||
if model.state != 'manual':
|
if model.state != 'manual':
|
||||||
raise UserError(_("Model '%s' contains module data and cannot be removed!") % model.name)
|
raise UserError(_("Model '%s' contains module data and cannot be removed!") % model.name)
|
||||||
|
|
||||||
|
# prevent screwing up fields that depend on these models' fields
|
||||||
|
for model in self:
|
||||||
|
model.field_id._prepare_update()
|
||||||
|
|
||||||
self._drop_table()
|
self._drop_table()
|
||||||
res = super(IrModel, self).unlink()
|
res = super(IrModel, self).unlink()
|
||||||
|
|
||||||
@@ -421,6 +425,19 @@ class IrModelFields(models.Model):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _prepare_update(self):
|
||||||
|
""" Check whether the fields in ``self`` may be modified or removed.
|
||||||
|
This method prevents the modification/deletion of many2one fields
|
||||||
|
that have an inverse one2many, for instance.
|
||||||
|
"""
|
||||||
|
for record in self:
|
||||||
|
model = self.env[record.model]
|
||||||
|
field = model._fields[record.name]
|
||||||
|
if field.type == 'many2one' and model._field_inverses.get(field):
|
||||||
|
msg = _("The field '%s' cannot be removed because the field '%s' depends on it.")
|
||||||
|
raise UserError(msg % (field, model._field_inverses[field][0]))
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def unlink(self):
|
def unlink(self):
|
||||||
if not self:
|
if not self:
|
||||||
@@ -431,6 +448,9 @@ class IrModelFields(models.Model):
|
|||||||
any(field.state != 'manual' for field in self):
|
any(field.state != 'manual' for field in self):
|
||||||
raise UserError(_("This column contains module data and cannot be removed!"))
|
raise UserError(_("This column contains module data and cannot be removed!"))
|
||||||
|
|
||||||
|
# prevent screwing up fields that depend on these fields
|
||||||
|
self._prepare_update()
|
||||||
|
|
||||||
model_names = self.mapped('model')
|
model_names = self.mapped('model')
|
||||||
self._drop_column()
|
self._drop_column()
|
||||||
res = super(IrModelFields, self).unlink()
|
res = super(IrModelFields, self).unlink()
|
||||||
@@ -522,6 +542,7 @@ class IrModelFields(models.Model):
|
|||||||
|
|
||||||
if vals.get('name', item.name) != item.name:
|
if vals.get('name', item.name) != item.name:
|
||||||
# We need to rename the column
|
# We need to rename the column
|
||||||
|
item._prepare_update()
|
||||||
if column_rename:
|
if column_rename:
|
||||||
raise UserError(_('Can only rename one field at a time!'))
|
raise UserError(_('Can only rename one field at a time!'))
|
||||||
if vals['name'] in obj._fields:
|
if vals['name'] in obj._fields:
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ class HttpCase(TransactionCase):
|
|||||||
_logger.info("phantomjs: %s", line)
|
_logger.info("phantomjs: %s", line)
|
||||||
|
|
||||||
if line == "ok":
|
if line == "ok":
|
||||||
break
|
return True
|
||||||
|
|
||||||
def phantom_run(self, cmd, timeout):
|
def phantom_run(self, cmd, timeout):
|
||||||
_logger.info('phantom_run executing %s', ' '.join(cmd))
|
_logger.info('phantom_run executing %s', ' '.join(cmd))
|
||||||
@@ -369,8 +369,9 @@ class HttpCase(TransactionCase):
|
|||||||
phantom = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=None)
|
phantom = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=None)
|
||||||
except OSError:
|
except OSError:
|
||||||
raise unittest.SkipTest("PhantomJS not found")
|
raise unittest.SkipTest("PhantomJS not found")
|
||||||
|
result = False
|
||||||
try:
|
try:
|
||||||
self.phantom_poll(phantom, timeout)
|
result = self.phantom_poll(phantom, timeout)
|
||||||
finally:
|
finally:
|
||||||
# kill phantomjs if phantom.exit() wasn't called in the test
|
# kill phantomjs if phantom.exit() wasn't called in the test
|
||||||
if phantom.poll() is None:
|
if phantom.poll() is None:
|
||||||
@@ -379,6 +380,10 @@ class HttpCase(TransactionCase):
|
|||||||
self._wait_remaining_requests()
|
self._wait_remaining_requests()
|
||||||
# we ignore phantomjs return code as we kill it as soon as we have ok
|
# we ignore phantomjs return code as we kill it as soon as we have ok
|
||||||
_logger.info("phantom_run execution finished")
|
_logger.info("phantom_run execution finished")
|
||||||
|
self.assertTrue(
|
||||||
|
result,
|
||||||
|
"PhantomJS test completed without reporting success; "
|
||||||
|
"the log may contain errors or hints.")
|
||||||
|
|
||||||
def _wait_remaining_requests(self):
|
def _wait_remaining_requests(self):
|
||||||
t0 = int(time.time())
|
t0 = int(time.time())
|
||||||
|
|||||||
+13
-5
@@ -111,7 +111,7 @@ class configmanager(object):
|
|||||||
group.add_option("--addons-path", dest="addons_path",
|
group.add_option("--addons-path", dest="addons_path",
|
||||||
help="specify additional addons paths (separated by commas).",
|
help="specify additional addons paths (separated by commas).",
|
||||||
action="callback", callback=self._check_addons_path, nargs=1, type="string")
|
action="callback", callback=self._check_addons_path, nargs=1, type="string")
|
||||||
group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules default=web")
|
group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules. Default is 'web,web_kanban'")
|
||||||
|
|
||||||
group.add_option("-D", "--data-dir", dest="data_dir", my_default=_get_default_datadir(),
|
group.add_option("-D", "--data-dir", dest="data_dir", my_default=_get_default_datadir(),
|
||||||
help="Directory where to store Odoo data")
|
help="Directory where to store Odoo data")
|
||||||
@@ -406,6 +406,7 @@ class configmanager(object):
|
|||||||
'test_file', 'test_enable', 'test_commit', 'test_report_directory',
|
'test_file', 'test_enable', 'test_commit', 'test_report_directory',
|
||||||
'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
|
'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
|
||||||
'data_dir',
|
'data_dir',
|
||||||
|
'server_wide_modules',
|
||||||
]
|
]
|
||||||
|
|
||||||
posix_keys = [
|
posix_keys = [
|
||||||
@@ -459,14 +460,21 @@ class configmanager(object):
|
|||||||
if len(self.options['language']) > 5:
|
if len(self.options['language']) > 5:
|
||||||
raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE')
|
raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE')
|
||||||
|
|
||||||
|
# server_wide_modules defaults to web,web_kanban if empty or unset
|
||||||
|
server_wide_modules = self.options['server_wide_modules'] = (
|
||||||
|
self.options['server_wide_modules']
|
||||||
|
if self.options['server_wide_modules']
|
||||||
|
else 'web,web_kanban'
|
||||||
|
)
|
||||||
|
|
||||||
if opt.save:
|
if opt.save:
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
odoo.conf.addons_paths = self.options['addons_path'].split(',')
|
odoo.conf.addons_paths = self.options['addons_path'].split(',')
|
||||||
if opt.server_wide_modules:
|
|
||||||
odoo.conf.server_wide_modules = map(lambda m: m.strip(), opt.server_wide_modules.split(','))
|
odoo.conf.server_wide_modules = [
|
||||||
else:
|
m.strip() for m in server_wide_modules.split(',')
|
||||||
odoo.conf.server_wide_modules = ['web','web_kanban']
|
]
|
||||||
|
|
||||||
def _is_addons_path(self, path):
|
def _is_addons_path(self, path):
|
||||||
from odoo.modules.module import MANIFEST_NAMES
|
from odoo.modules.module import MANIFEST_NAMES
|
||||||
|
|||||||
@@ -36,9 +36,11 @@ def py2exe_datafiles():
|
|||||||
|
|
||||||
import docutils
|
import docutils
|
||||||
import passlib
|
import passlib
|
||||||
|
import reportlab
|
||||||
import requests
|
import requests
|
||||||
data_mapping = ((docutils, 'docutils'),
|
data_mapping = ((docutils, 'docutils'),
|
||||||
(passlib, 'passlib'),
|
(passlib, 'passlib'),
|
||||||
|
(reportlab, 'reportlab'),
|
||||||
(requests, 'requests'))
|
(requests, 'requests'))
|
||||||
|
|
||||||
for mod, datadir in data_mapping:
|
for mod, datadir in data_mapping:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ RUN apt-get update -qq && \
|
|||||||
postgresql \
|
postgresql \
|
||||||
postgresql-client \
|
postgresql-client \
|
||||||
python \
|
python \
|
||||||
|
python-babel \
|
||||||
python-dateutil \
|
python-dateutil \
|
||||||
python-decorator \
|
python-decorator \
|
||||||
python-docutils \
|
python-docutils \
|
||||||
@@ -36,7 +37,6 @@ RUN apt-get update -qq && \
|
|||||||
python-passlib \
|
python-passlib \
|
||||||
python-psutil \
|
python-psutil \
|
||||||
python-psycopg2 \
|
python-psycopg2 \
|
||||||
python-pybabel \
|
|
||||||
python-pychart \
|
python-pychart \
|
||||||
python-pydot \
|
python-pydot \
|
||||||
python-pyparsing \
|
python-pyparsing \
|
||||||
|
|||||||
Reference in New Issue
Block a user