[MERGE] forward port branch saas-15 up to dde62073ba
This commit is contained in:
@@ -307,7 +307,10 @@ class AccountBankStatement(models.Model):
|
|||||||
#try to assign partner to bank_statement_line
|
#try to assign partner to bank_statement_line
|
||||||
stl_to_assign_partner = [stl.id for stl in st_lines_left if not stl.partner_id]
|
stl_to_assign_partner = [stl.id for stl in st_lines_left if not stl.partner_id]
|
||||||
refs = list(set([st.name for st in st_lines_left if not stl.partner_id]))
|
refs = list(set([st.name for st in st_lines_left if not stl.partner_id]))
|
||||||
if st_lines_left and stl_to_assign_partner and refs:
|
if st_lines_left and stl_to_assign_partner and refs\
|
||||||
|
and st_lines_left[0].journal_id.default_credit_account_id\
|
||||||
|
and st_lines_left[0].journal_id.default_debit_account_id:
|
||||||
|
|
||||||
sql_query = """SELECT aml.partner_id, aml.ref, stl.id
|
sql_query = """SELECT aml.partner_id, aml.ref, stl.id
|
||||||
FROM account_move_line aml
|
FROM account_move_line aml
|
||||||
JOIN account_account acc ON acc.id = aml.account_id
|
JOIN account_account acc ON acc.id = aml.account_id
|
||||||
@@ -605,13 +608,14 @@ class AccountBankStatementLine(models.Model):
|
|||||||
acc_type = "acc.internal_type IN ('payable', 'receivable')" if (self.partner_id or overlook_partner) else "acc.reconcile = true"
|
acc_type = "acc.internal_type IN ('payable', 'receivable')" if (self.partner_id or overlook_partner) else "acc.reconcile = true"
|
||||||
select_clause = "SELECT aml.id "
|
select_clause = "SELECT aml.id "
|
||||||
from_clause = "FROM account_move_line aml JOIN account_account acc ON acc.id = aml.account_id "
|
from_clause = "FROM account_move_line aml JOIN account_account acc ON acc.id = aml.account_id "
|
||||||
|
account_clause = ''
|
||||||
|
if self.journal_id.default_credit_account_id and self.journal_id.default_debit_account_id:
|
||||||
|
account_clause = "(aml.statement_id IS NULL AND aml.account_id IN %(account_payable_receivable)s AND aml.payment_id IS NOT NULL) OR"
|
||||||
where_clause = """WHERE aml.company_id = %(company_id)s
|
where_clause = """WHERE aml.company_id = %(company_id)s
|
||||||
AND (
|
AND (
|
||||||
(aml.statement_id IS NULL AND aml.account_id IN %(account_payable_receivable)s
|
""" + account_clause + """
|
||||||
AND aml.payment_id IS NOT NULL)
|
("""+acc_type+""" AND aml.reconciled = false)
|
||||||
OR
|
)"""
|
||||||
("""+acc_type+""" AND aml.reconciled = false)
|
|
||||||
)"""
|
|
||||||
where_clause = where_clause + ' AND aml.partner_id = %(partner_id)s' if self.partner_id else where_clause
|
where_clause = where_clause + ' AND aml.partner_id = %(partner_id)s' if self.partner_id else where_clause
|
||||||
where_clause = where_clause + ' AND aml.id NOT IN %(excluded_ids)s' if excluded_ids else where_clause
|
where_clause = where_clause + ' AND aml.id NOT IN %(excluded_ids)s' if excluded_ids else where_clause
|
||||||
if split:
|
if split:
|
||||||
|
|||||||
@@ -778,7 +778,6 @@ class AccountInvoice(models.Model):
|
|||||||
if tax.amount_type == "group":
|
if tax.amount_type == "group":
|
||||||
for child_tax in tax.children_tax_ids:
|
for child_tax in tax.children_tax_ids:
|
||||||
done_taxes.append(child_tax.id)
|
done_taxes.append(child_tax.id)
|
||||||
done_taxes.append(tax.id)
|
|
||||||
res.append({
|
res.append({
|
||||||
'invoice_tax_line_id': tax_line.id,
|
'invoice_tax_line_id': tax_line.id,
|
||||||
'tax_line_id': tax_line.tax_id.id,
|
'tax_line_id': tax_line.tax_id.id,
|
||||||
@@ -790,8 +789,9 @@ class AccountInvoice(models.Model):
|
|||||||
'account_id': tax_line.account_id.id,
|
'account_id': tax_line.account_id.id,
|
||||||
'account_analytic_id': tax_line.account_analytic_id.id,
|
'account_analytic_id': tax_line.account_analytic_id.id,
|
||||||
'invoice_id': self.id,
|
'invoice_id': self.id,
|
||||||
'tax_ids': [(6, 0, done_taxes)] if tax_line.tax_id.include_base_amount else []
|
'tax_ids': [(6, 0, list(done_taxes))] if tax_line.tax_id.include_base_amount else []
|
||||||
})
|
})
|
||||||
|
done_taxes.append(tax.id)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def inv_line_characteristic_hashcode(self, invoice_line):
|
def inv_line_characteristic_hashcode(self, invoice_line):
|
||||||
|
|||||||
@@ -530,6 +530,7 @@ class AccountMoveLine(models.Model):
|
|||||||
{2}
|
{2}
|
||||||
WHERE
|
WHERE
|
||||||
a.reconcile IS TRUE
|
a.reconcile IS TRUE
|
||||||
|
AND l.full_reconcile_id is NULL
|
||||||
{3}
|
{3}
|
||||||
{4}
|
{4}
|
||||||
{5}
|
{5}
|
||||||
@@ -1545,6 +1546,50 @@ class AccountPartialReconcile(models.Model):
|
|||||||
move.post()
|
move.post()
|
||||||
return line_to_reconcile, partial_rec
|
return line_to_reconcile, partial_rec
|
||||||
|
|
||||||
|
# Do not forwardport in master as of 2017-07-20
|
||||||
|
def _fix_multiple_exchange_rates_diff(self, amls_to_fix, amount_diff, diff_in_currency, currency, move):
|
||||||
|
self.ensure_one()
|
||||||
|
move_lines = self.env['account.move.line'].with_context(check_move_validity=False)
|
||||||
|
partial_reconciles = self.with_context(skip_full_reconcile_check=True)
|
||||||
|
amount_diff = self.company_id.currency_id.round(amount_diff)
|
||||||
|
diff_in_currency = currency.round(diff_in_currency)
|
||||||
|
|
||||||
|
for aml in amls_to_fix:
|
||||||
|
account_payable_line = move_lines.create({
|
||||||
|
'name': _('Currency exchange rate difference'),
|
||||||
|
'debit': amount_diff < 0 and -aml.amount_residual or 0.0,
|
||||||
|
'credit': amount_diff > 0 and aml.amount_residual or 0.0,
|
||||||
|
'account_id': self.debit_move_id.account_id.id,
|
||||||
|
'move_id': move.id,
|
||||||
|
'currency_id': currency.id,
|
||||||
|
'amount_currency': -aml.amount_residual_currency,
|
||||||
|
'partner_id': self.debit_move_id.partner_id.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
move_lines.create({
|
||||||
|
'name': _('Currency exchange rate difference'),
|
||||||
|
'debit': amount_diff > 0 and aml.amount_residual or 0.0,
|
||||||
|
'credit': amount_diff < 0 and -aml.amount_residual or 0.0,
|
||||||
|
'account_id': amount_diff > 0 and self.company_id.currency_exchange_journal_id.default_debit_account_id.id or self.company_id.currency_exchange_journal_id.default_credit_account_id.id,
|
||||||
|
'move_id': move.id,
|
||||||
|
'currency_id': currency.id,
|
||||||
|
'amount_currency': aml.amount_residual_currency,
|
||||||
|
'partner_id': self.debit_move_id.partner_id.id})
|
||||||
|
|
||||||
|
partial_rec = super(AccountPartialReconcile, partial_reconciles).create({
|
||||||
|
'debit_move_id': aml.credit and account_payable_line.id or aml.id,
|
||||||
|
'credit_move_id': aml.debit and account_payable_line.id or aml.id,
|
||||||
|
'amount': abs(aml.amount_residual),
|
||||||
|
'amount_currency': abs(aml.amount_residual_currency),
|
||||||
|
'currency_id': currency.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
move_lines |= account_payable_line
|
||||||
|
partial_reconciles |= partial_rec
|
||||||
|
|
||||||
|
partial_reconciles._compute_partial_lines()
|
||||||
|
return move_lines, partial_reconciles
|
||||||
|
|
||||||
def create_tax_cash_basis_entry(self, percentage_before_rec):
|
def create_tax_cash_basis_entry(self, percentage_before_rec):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
move_date = self.debit_move_id.date
|
move_date = self.debit_move_id.date
|
||||||
@@ -1647,30 +1692,16 @@ class AccountPartialReconcile(models.Model):
|
|||||||
}
|
}
|
||||||
return self.env['account.move'].create(move_vals)
|
return self.env['account.move'].create(move_vals)
|
||||||
|
|
||||||
@api.model
|
# Do not forwardport in master as of 2017-07-20
|
||||||
def create(self, vals):
|
def _compute_partial_lines(self):
|
||||||
aml = []
|
|
||||||
if vals.get('debit_move_id', False):
|
|
||||||
aml.append(vals['debit_move_id'])
|
|
||||||
if vals.get('credit_move_id', False):
|
|
||||||
aml.append(vals['credit_move_id'])
|
|
||||||
# Get value of matched percentage from both move before reconciliating
|
|
||||||
lines = self.env['account.move.line'].browse(aml)
|
|
||||||
if lines[0].account_id.internal_type in ('receivable', 'payable'):
|
|
||||||
percentage_before_rec = lines._get_matched_percentage()
|
|
||||||
# Reconcile
|
|
||||||
res = super(AccountPartialReconcile, self).create(vals)
|
|
||||||
# if the reconciliation is a matching on a receivable or payable account, eventually create a tax cash basis entry
|
|
||||||
if lines[0].account_id.internal_type in ('receivable', 'payable'):
|
|
||||||
res.create_tax_cash_basis_entry(percentage_before_rec)
|
|
||||||
if self._context.get('skip_full_reconcile_check'):
|
if self._context.get('skip_full_reconcile_check'):
|
||||||
#when running the manual reconciliation wizard, don't check the partials separately for full
|
#when running the manual reconciliation wizard, don't check the partials separately for full
|
||||||
#reconciliation or exchange rate because it is handled manually after the whole processing
|
#reconciliation or exchange rate because it is handled manually after the whole processing
|
||||||
return res
|
return self
|
||||||
#check if the reconcilation is full
|
#check if the reconcilation is full
|
||||||
#first, gather all journal items involved in the reconciliation just created
|
#first, gather all journal items involved in the reconciliation just created
|
||||||
partial_rec_set = OrderedDict.fromkeys([x for x in res])
|
partial_rec_set = OrderedDict.fromkeys([x for x in self])
|
||||||
aml_set = self.env['account.move.line']
|
aml_set = aml_to_balance = self.env['account.move.line']
|
||||||
total_debit = 0
|
total_debit = 0
|
||||||
total_credit = 0
|
total_credit = 0
|
||||||
total_amount_currency = 0
|
total_amount_currency = 0
|
||||||
@@ -1678,7 +1709,7 @@ class AccountPartialReconcile(models.Model):
|
|||||||
#possible to compute the exchange difference entry and it has to be done manually.
|
#possible to compute the exchange difference entry and it has to be done manually.
|
||||||
currency = list(partial_rec_set)[0].currency_id
|
currency = list(partial_rec_set)[0].currency_id
|
||||||
maxdate = None
|
maxdate = None
|
||||||
aml_to_balance = None
|
|
||||||
for partial_rec in partial_rec_set:
|
for partial_rec in partial_rec_set:
|
||||||
if partial_rec.currency_id != currency:
|
if partial_rec.currency_id != currency:
|
||||||
#no exchange rate entry will be created
|
#no exchange rate entry will be created
|
||||||
@@ -1686,7 +1717,7 @@ class AccountPartialReconcile(models.Model):
|
|||||||
for aml in [partial_rec.debit_move_id, partial_rec.credit_move_id]:
|
for aml in [partial_rec.debit_move_id, partial_rec.credit_move_id]:
|
||||||
if aml not in aml_set:
|
if aml not in aml_set:
|
||||||
if aml.amount_residual or aml.amount_residual_currency:
|
if aml.amount_residual or aml.amount_residual_currency:
|
||||||
aml_to_balance = aml
|
aml_to_balance |= aml
|
||||||
maxdate = max(aml.date, maxdate)
|
maxdate = max(aml.date, maxdate)
|
||||||
total_debit += aml.debit
|
total_debit += aml.debit
|
||||||
total_credit += aml.credit
|
total_credit += aml.credit
|
||||||
@@ -1704,16 +1735,21 @@ class AccountPartialReconcile(models.Model):
|
|||||||
aml_ids = aml_set.ids
|
aml_ids = aml_set.ids
|
||||||
#then, if the total debit and credit are equal, or the total amount in currency is 0, the reconciliation is full
|
#then, if the total debit and credit are equal, or the total amount in currency is 0, the reconciliation is full
|
||||||
digits_rounding_precision = aml_set[0].company_id.currency_id.rounding
|
digits_rounding_precision = aml_set[0].company_id.currency_id.rounding
|
||||||
|
|
||||||
if (currency and float_is_zero(total_amount_currency, precision_rounding=currency.rounding)) or float_compare(total_debit, total_credit, precision_rounding=digits_rounding_precision) == 0:
|
if (currency and float_is_zero(total_amount_currency, precision_rounding=currency.rounding)) or float_compare(total_debit, total_credit, precision_rounding=digits_rounding_precision) == 0:
|
||||||
exchange_move_id = False
|
exchange_move_id = False
|
||||||
exchange_partial_rec_id = False
|
exchange_partial_rec_id = False
|
||||||
if currency and aml_to_balance:
|
if currency and aml_to_balance:
|
||||||
|
exchange_move = (self.env['account.move']
|
||||||
|
.create(self.env['account.full.reconcile']
|
||||||
|
._prepare_exchange_diff_move(move_date=maxdate, company=aml_to_balance[0].company_id)))
|
||||||
#eventually create a journal entry to book the difference due to foreign currency's exchange rate that fluctuates
|
#eventually create a journal entry to book the difference due to foreign currency's exchange rate that fluctuates
|
||||||
rate_diff_aml, rate_diff_partial_rec = partial_rec.create_exchange_rate_entry(aml_to_balance, total_debit - total_credit, total_amount_currency, currency, maxdate)
|
rate_diff_amls, rate_diff_partial_recs = partial_rec._fix_multiple_exchange_rates_diff(aml_to_balance, total_debit - total_credit, total_amount_currency, currency, exchange_move)
|
||||||
aml_ids.append(rate_diff_aml.id)
|
aml_ids += rate_diff_amls.ids
|
||||||
partial_rec_ids.append(rate_diff_partial_rec.id)
|
partial_rec_ids += rate_diff_partial_recs.ids
|
||||||
exchange_move_id = rate_diff_aml.move_id.id
|
exchange_move.post()
|
||||||
exchange_partial_rec_id = rate_diff_partial_rec.id
|
exchange_move_id = exchange_move.id
|
||||||
|
exchange_partial_rec_id = rate_diff_partial_recs[-1:].id
|
||||||
#mark the reference of the full reconciliation on the partial ones and on the entries
|
#mark the reference of the full reconciliation on the partial ones and on the entries
|
||||||
self.env['account.full.reconcile'].with_context(check_move_validity=False).create({
|
self.env['account.full.reconcile'].with_context(check_move_validity=False).create({
|
||||||
'partial_reconcile_ids': [(4, p_id) for p_id in partial_rec_ids],
|
'partial_reconcile_ids': [(4, p_id) for p_id in partial_rec_ids],
|
||||||
@@ -1721,6 +1757,25 @@ class AccountPartialReconcile(models.Model):
|
|||||||
'exchange_move_id': exchange_move_id,
|
'exchange_move_id': exchange_move_id,
|
||||||
'exchange_partial_rec_id': exchange_partial_rec_id,
|
'exchange_partial_rec_id': exchange_partial_rec_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Do not forwardport in master as of 2017-07-20
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
aml = []
|
||||||
|
if vals.get('debit_move_id', False):
|
||||||
|
aml.append(vals['debit_move_id'])
|
||||||
|
if vals.get('credit_move_id', False):
|
||||||
|
aml.append(vals['credit_move_id'])
|
||||||
|
# Get value of matched percentage from both move before reconciliating
|
||||||
|
lines = self.env['account.move.line'].browse(aml)
|
||||||
|
if lines[0].account_id.internal_type in ('receivable', 'payable'):
|
||||||
|
percentage_before_rec = lines._get_matched_percentage()
|
||||||
|
# Reconcile
|
||||||
|
res = super(AccountPartialReconcile, self).create(vals)
|
||||||
|
# if the reconciliation is a matching on a receivable or payable account, eventually create a tax cash basis entry
|
||||||
|
if lines[0].account_id.internal_type in ('receivable', 'payable'):
|
||||||
|
res.create_tax_cash_basis_entry(percentage_before_rec)
|
||||||
|
res._compute_partial_lines()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@@ -1766,3 +1821,20 @@ class AccountFullReconcile(models.Model):
|
|||||||
rec.exchange_move_id = False
|
rec.exchange_move_id = False
|
||||||
to_reverse.reverse_moves()
|
to_reverse.reverse_moves()
|
||||||
return super(AccountFullReconcile, self).unlink()
|
return super(AccountFullReconcile, self).unlink()
|
||||||
|
|
||||||
|
# Do not forwardport in master as of 2017-07-20
|
||||||
|
@api.model
|
||||||
|
def _prepare_exchange_diff_move(self, move_date, company):
|
||||||
|
if not company.currency_exchange_journal_id:
|
||||||
|
raise UserError(_("You should configure the 'Exchange Rate Journal' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
|
||||||
|
if not company.income_currency_exchange_account_id.id:
|
||||||
|
raise UserError(_("You should configure the 'Gain Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
|
||||||
|
if not company.expense_currency_exchange_account_id.id:
|
||||||
|
raise UserError(_("You should configure the 'Loss Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
|
||||||
|
res = {'journal_id': company.currency_exchange_journal_id.id}
|
||||||
|
# The move date should be the maximum date between payment and invoice
|
||||||
|
# (in case of payment in advance). However, we should make sure the
|
||||||
|
# move date is not recorded after the end of year closing.
|
||||||
|
if move_date > company.fiscalyear_lock_date:
|
||||||
|
res['date'] = move_date
|
||||||
|
return res
|
||||||
|
|||||||
@@ -474,3 +474,147 @@ class TestReconciliation(AccountingTestCase):
|
|||||||
self.assertEquals(round(aml.credit, 2), line['credit'])
|
self.assertEquals(round(aml.credit, 2), line['credit'])
|
||||||
self.assertEquals(round(aml.amount_currency, 2), line['amount_currency'])
|
self.assertEquals(round(aml.amount_currency, 2), line['amount_currency'])
|
||||||
self.assertEquals(aml.currency_id.id, line['currency_id'])
|
self.assertEquals(aml.currency_id.id, line['currency_id'])
|
||||||
|
|
||||||
|
def test_partial_reconcile_currencies(self):
|
||||||
|
# client Account (payable, rsa)
|
||||||
|
# Debit Credit
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# Pay a : 25/0.5 = 50 | Inv a : 50/0.5 = 100
|
||||||
|
# Pay b: 50/0.75 = 66.66 | Inv b : 50/0.75 = 66.66
|
||||||
|
# Pay c: 25/0.8 = 31.25 |
|
||||||
|
#
|
||||||
|
# Debit_currency = 100 | Credit currency = 100
|
||||||
|
# Debit = 147.91 | Credit = 166.66
|
||||||
|
# Balance Debit = 18.75
|
||||||
|
# Counterpart Credit goes in Exchange diff
|
||||||
|
|
||||||
|
dest_journal_id = self.env['account.journal'].search([('type', '=', 'purchase'), ('company_id', '=', self.env.ref('base.main_company').id)], limit=1)
|
||||||
|
account_expenses = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_expenses').id)], limit=1)
|
||||||
|
|
||||||
|
self.bank_journal_euro.write({'default_debit_account_id': self.account_rsa.id,
|
||||||
|
'default_credit_account_id': self.account_rsa.id})
|
||||||
|
dest_journal_id.write({'default_debit_account_id': self.account_rsa.id,
|
||||||
|
'default_credit_account_id': self.account_rsa.id})
|
||||||
|
# Setting up rates for USD (main_company is in EUR)
|
||||||
|
self.env['res.currency.rate'].create({'name': time.strftime('%Y') + '-' + '07' + '-01',
|
||||||
|
'rate': 0.5,
|
||||||
|
'currency_id': self.currency_usd_id,
|
||||||
|
'company_id': self.env.ref('base.main_company').id})
|
||||||
|
|
||||||
|
self.env['res.currency.rate'].create({'name': time.strftime('%Y') + '-' + '08' + '-01',
|
||||||
|
'rate': 0.75,
|
||||||
|
'currency_id': self.currency_usd_id,
|
||||||
|
'company_id': self.env.ref('base.main_company').id})
|
||||||
|
|
||||||
|
self.env['res.currency.rate'].create({'name': time.strftime('%Y') + '-' + '09' + '-01',
|
||||||
|
'rate': 0.80,
|
||||||
|
'currency_id': self.currency_usd_id,
|
||||||
|
'company_id': self.env.ref('base.main_company').id})
|
||||||
|
|
||||||
|
# Preparing Invoices (from vendor)
|
||||||
|
invoice_a = self.account_invoice_model.create({'partner_id': self.partner_agrolait_id,
|
||||||
|
'reference_type': 'none',
|
||||||
|
'currency_id': self.currency_usd_id,
|
||||||
|
'name': 'invoice to vendor',
|
||||||
|
'account_id': self.account_rsa.id,
|
||||||
|
'type': 'in_invoice',
|
||||||
|
'date_invoice': time.strftime('%Y') + '-' + '07' + '-01',
|
||||||
|
})
|
||||||
|
self.account_invoice_line_model.create({'product_id': self.product.id,
|
||||||
|
'quantity': 1,
|
||||||
|
'price_unit': 50,
|
||||||
|
'invoice_id': invoice_a.id,
|
||||||
|
'name': 'product that cost ' + str(50),
|
||||||
|
'account_id': account_expenses.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
invoice_b = self.account_invoice_model.create({'partner_id': self.partner_agrolait_id,
|
||||||
|
'reference_type': 'none',
|
||||||
|
'currency_id': self.currency_usd_id,
|
||||||
|
'name': 'invoice to vendor',
|
||||||
|
'account_id': self.account_rsa.id,
|
||||||
|
'type': 'in_invoice',
|
||||||
|
'date_invoice': time.strftime('%Y') + '-' + '08' + '-01',
|
||||||
|
})
|
||||||
|
self.account_invoice_line_model.create({'product_id': self.product.id,
|
||||||
|
'quantity': 1,
|
||||||
|
'price_unit': 50,
|
||||||
|
'invoice_id': invoice_b.id,
|
||||||
|
'name': 'product that cost ' + str(50),
|
||||||
|
'account_id': account_expenses.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
invoice_a.action_invoice_open()
|
||||||
|
invoice_b.action_invoice_open()
|
||||||
|
|
||||||
|
# Preparing Payments
|
||||||
|
# One partial for invoice_a (fully assigned to it)
|
||||||
|
payment_a = self.env['account.payment'].create({'payment_type': 'outbound',
|
||||||
|
'amount': 25,
|
||||||
|
'currency_id': self.currency_usd_id,
|
||||||
|
'journal_id': self.bank_journal_euro.id,
|
||||||
|
'company_id': self.env.ref('base.main_company').id,
|
||||||
|
'payment_date': time.strftime('%Y') + '-' + '07' + '-01',
|
||||||
|
'partner_id': self.partner_agrolait_id,
|
||||||
|
'payment_method_id': self.env.ref('account.account_payment_method_manual_out').id,
|
||||||
|
'destination_journal_id': dest_journal_id.id,
|
||||||
|
'partner_type': 'supplier'})
|
||||||
|
|
||||||
|
# One that will complete the payment of a, the rest goes to b
|
||||||
|
payment_b = self.env['account.payment'].create({'payment_type': 'outbound',
|
||||||
|
'amount': 50,
|
||||||
|
'currency_id': self.currency_usd_id,
|
||||||
|
'journal_id': self.bank_journal_euro.id,
|
||||||
|
'company_id': self.env.ref('base.main_company').id,
|
||||||
|
'payment_date': time.strftime('%Y') + '-' + '08' + '-01',
|
||||||
|
'partner_id': self.partner_agrolait_id,
|
||||||
|
'payment_method_id': self.env.ref('account.account_payment_method_manual_out').id,
|
||||||
|
'destination_journal_id': dest_journal_id.id,
|
||||||
|
'partner_type': 'supplier'})
|
||||||
|
|
||||||
|
# The last one will complete the payment of b
|
||||||
|
payment_c = self.env['account.payment'].create({'payment_type': 'outbound',
|
||||||
|
'amount': 25,
|
||||||
|
'currency_id': self.currency_usd_id,
|
||||||
|
'journal_id': self.bank_journal_euro.id,
|
||||||
|
'company_id': self.env.ref('base.main_company').id,
|
||||||
|
'payment_date': time.strftime('%Y') + '-' + '09' + '-01',
|
||||||
|
'partner_id': self.partner_agrolait_id,
|
||||||
|
'payment_method_id': self.env.ref('account.account_payment_method_manual_out').id,
|
||||||
|
'destination_journal_id': dest_journal_id.id,
|
||||||
|
'partner_type': 'supplier'})
|
||||||
|
|
||||||
|
payment_a.post()
|
||||||
|
payment_b.post()
|
||||||
|
payment_c.post()
|
||||||
|
|
||||||
|
# Assigning payments to invoices
|
||||||
|
debit_line_a = payment_a.move_line_ids.filtered(lambda l: l.debit and l.account_id == dest_journal_id.default_debit_account_id)
|
||||||
|
debit_line_b = payment_b.move_line_ids.filtered(lambda l: l.debit and l.account_id == dest_journal_id.default_debit_account_id)
|
||||||
|
debit_line_c = payment_c.move_line_ids.filtered(lambda l: l.debit and l.account_id == dest_journal_id.default_debit_account_id)
|
||||||
|
|
||||||
|
invoice_a.assign_outstanding_credit(debit_line_a.id)
|
||||||
|
invoice_a.assign_outstanding_credit(debit_line_b.id)
|
||||||
|
invoice_b.assign_outstanding_credit(debit_line_b.id)
|
||||||
|
invoice_b.assign_outstanding_credit(debit_line_c.id)
|
||||||
|
|
||||||
|
# Asserting correctness (only in the payable account)
|
||||||
|
full_reconcile = False
|
||||||
|
for inv in (invoice_a + invoice_b):
|
||||||
|
self.assertTrue(inv.reconciled)
|
||||||
|
for aml in (inv.payment_move_line_ids + inv.move_id.line_ids).filtered(lambda l: l.account_id == self.account_rsa):
|
||||||
|
self.assertEqual(aml.amount_residual, 0.0)
|
||||||
|
self.assertEqual(aml.amount_residual_currency, 0.0)
|
||||||
|
self.assertTrue(aml.reconciled)
|
||||||
|
if not full_reconcile:
|
||||||
|
full_reconcile = aml.full_reconcile_id
|
||||||
|
else:
|
||||||
|
self.assertTrue(aml.full_reconcile_id == full_reconcile)
|
||||||
|
|
||||||
|
full_rec_move = full_reconcile.exchange_move_id
|
||||||
|
# Globally check whether the amount is correct
|
||||||
|
self.assertEqual(full_rec_move.amount, 18.75)
|
||||||
|
|
||||||
|
# Checking if the direction of the move is correct
|
||||||
|
full_rec_payable = full_rec_move.line_ids.filtered(lambda l: l.account_id == self.account_rsa)
|
||||||
|
self.assertEqual(full_rec_payable.balance, 18.75)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<form string="Configure Accounting" class="oe_form_configuration">
|
<form string="Configure Accounting" class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<field name="company_id" invisible="1"/>
|
<field name="company_id" invisible="1"/>
|
||||||
|
|||||||
@@ -108,3 +108,6 @@ class AccountInvoiceLine(models.Model):
|
|||||||
elif invoice.type == 'in_invoice':
|
elif invoice.type == 'in_invoice':
|
||||||
self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id.id
|
self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id.id
|
||||||
super(AccountInvoiceLine, self)._set_additional_fields(invoice)
|
super(AccountInvoiceLine, self)._set_additional_fields(invoice)
|
||||||
|
|
||||||
|
def get_invoice_line_account(self, type, product, fpos, company):
|
||||||
|
return product.asset_category_id.account_asset_id or super(AccountInvoiceLine, self).get_invoice_line_account(type, product, fpos, company)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<form string="General Settings" class="oe_form_configuration">
|
<form string="General Settings" class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<div id="business_documents"/>
|
<div id="business_documents"/>
|
||||||
|
|||||||
@@ -323,6 +323,9 @@ class EventEvent(models.Model):
|
|||||||
for attendee in self.registration_ids.filtered(filter_func):
|
for attendee in self.registration_ids.filtered(filter_func):
|
||||||
self.env['mail.template'].browse(template_id).send_mail(attendee.id, force_send=force_send)
|
self.env['mail.template'].browse(template_id).send_mail(attendee.id, force_send=force_send)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _is_event_registrable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
class EventRegistration(models.Model):
|
class EventRegistration(models.Model):
|
||||||
_name = 'event.registration'
|
_name = 'event.registration'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<form String="Event settings" class="oe_form_configuration">
|
<form String="Event settings" class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<h2>Advertising</h2>
|
<h2>Advertising</h2>
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ class Event(models.Model):
|
|||||||
})
|
})
|
||||||
for ticket in self.event_type_id.event_ticket_ids]
|
for ticket in self.event_type_id.event_ticket_ids]
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _is_event_registrable(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if not self.event_ticket_ids:
|
||||||
|
return True
|
||||||
|
return all(self.event_ticket_ids.with_context(active_test=False).mapped(lambda t: t.product_id.active))
|
||||||
|
|
||||||
class EventTicket(models.Model):
|
class EventTicket(models.Model):
|
||||||
_name = 'event.event.ticket'
|
_name = 'event.event.ticket'
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<form string="Configuration" class= "oe_form_configuration">
|
<form string="Configuration" class= "oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<h2>Check-In/Out</h2>
|
<h2>Check-In/Out</h2>
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ class HrExpenseSheet(models.Model):
|
|||||||
_order = "accounting_date desc, id desc"
|
_order = "accounting_date desc, id desc"
|
||||||
|
|
||||||
name = fields.Char(string='Expense Report Summary', required=True)
|
name = fields.Char(string='Expense Report Summary', required=True)
|
||||||
expense_line_ids = fields.One2many('hr.expense', 'sheet_id', string='Expense Lines', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, copy=False)
|
expense_line_ids = fields.One2many('hr.expense', 'sheet_id', string='Expense Lines', states={'approve': [('readonly', True)], 'done': [('readonly', True)], 'post': [('readonly', True)]}, copy=False)
|
||||||
state = fields.Selection([('submit', 'Submitted'),
|
state = fields.Selection([('submit', 'Submitted'),
|
||||||
('approve', 'Approved'),
|
('approve', 'Approved'),
|
||||||
('post', 'Posted'),
|
('post', 'Posted'),
|
||||||
|
|||||||
@@ -392,7 +392,7 @@
|
|||||||
<field name="accounting_date"/>
|
<field name="accounting_date"/>
|
||||||
<field name="employee_id"/>
|
<field name="employee_id"/>
|
||||||
<field name="currency_id" invisible="1"/>
|
<field name="currency_id" invisible="1"/>
|
||||||
<field name="total_amount" sum="Total Amount" widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
<field name="total_amount" sum="Total Amount"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
<field name="message_unread" invisible="1"/>
|
<field name="message_unread" invisible="1"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<form class="oe_form_configuration">
|
<form class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<h2>Recording</h2>
|
<h2>Recording</h2>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<form string="Configure Payroll" class="oe_form_configuration">
|
<form string="Configure Payroll" class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Apply" type="object" name="execute" class="oe_highlight"/>
|
<button string="Apply" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<field name="module_l10n_fr_hr_payroll" invisible="1"/>
|
<field name="module_l10n_fr_hr_payroll" invisible="1"/>
|
||||||
<field name="module_l10n_be_hr_payroll" invisible="1"/>
|
<field name="module_l10n_be_hr_payroll" invisible="1"/>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<form string="Configure HR Recruitment" class="oe_form_configuration">
|
<form string="Configure HR Recruitment" class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<h2>Job Posting</h2>
|
<h2>Job Posting</h2>
|
||||||
|
|||||||
@@ -576,6 +576,7 @@ class MailTemplate(models.Model):
|
|||||||
'name': attachment[0],
|
'name': attachment[0],
|
||||||
'datas_fname': attachment[0],
|
'datas_fname': attachment[0],
|
||||||
'datas': attachment[1],
|
'datas': attachment[1],
|
||||||
|
'type': 'binary',
|
||||||
'res_model': 'mail.message',
|
'res_model': 'mail.message',
|
||||||
'res_id': mail.mail_message_id.id,
|
'res_id': mail.mail_message_id.id,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1725,6 +1725,7 @@ class MailThread(models.AbstractModel):
|
|||||||
data_attach = {
|
data_attach = {
|
||||||
'name': name,
|
'name': name,
|
||||||
'datas': base64.b64encode(str(content)),
|
'datas': base64.b64encode(str(content)),
|
||||||
|
'type': 'binary',
|
||||||
'datas_fname': name,
|
'datas_fname': name,
|
||||||
'description': name,
|
'description': name,
|
||||||
'res_model': message_data['model'],
|
'res_model': message_data['model'],
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<form string="Configure Mass Mailing" class="oe_form_configuration">
|
<form string="Configure Mass Mailing" class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<h2>Design</h2>
|
<h2>Design</h2>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<form string="Configure Manufacturing" class= "oe_form_configuration">
|
<form string="Configure Manufacturing" class= "oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<h2>Operations</h2>
|
<h2>Operations</h2>
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ _logger = logging.getLogger(__name__)
|
|||||||
# changelog https://stripe.com/docs/upgrades#api-changelog
|
# changelog https://stripe.com/docs/upgrades#api-changelog
|
||||||
STRIPE_HEADERS = {'Stripe-Version': '2016-03-07'}
|
STRIPE_HEADERS = {'Stripe-Version': '2016-03-07'}
|
||||||
|
|
||||||
|
# The following currencies are integer only, see https://stripe.com/docs/currencies#zero-decimal
|
||||||
|
INT_CURRENCIES = [
|
||||||
|
'BIF', 'XAF', 'XPF', 'CLP', 'KMF', 'DJF', 'GNF', 'JPY', 'MGA', 'PYGí', 'RWF', 'KRW', 'VUV',
|
||||||
|
'VND', 'XOF'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
class PaymentAcquirerStripe(models.Model):
|
class PaymentAcquirerStripe(models.Model):
|
||||||
_inherit = 'payment.acquirer'
|
_inherit = 'payment.acquirer'
|
||||||
@@ -84,7 +90,7 @@ class PaymentTransactionStripe(models.Model):
|
|||||||
def _create_stripe_charge(self, acquirer_ref=None, tokenid=None, email=None):
|
def _create_stripe_charge(self, acquirer_ref=None, tokenid=None, email=None):
|
||||||
api_url_charge = 'https://%s/charges' % (self.acquirer_id._get_stripe_api_url())
|
api_url_charge = 'https://%s/charges' % (self.acquirer_id._get_stripe_api_url())
|
||||||
charge_params = {
|
charge_params = {
|
||||||
'amount': int(self.amount*100), # Stripe takes amount in cents (https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support)
|
'amount': int(self.amount if self.currency_id.name in INT_CURRENCIES else self.amount*100),
|
||||||
'currency': self.currency_id.name,
|
'currency': self.currency_id.name,
|
||||||
'metadata[reference]': self.reference
|
'metadata[reference]': self.reference
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
odoo.define('payment_stripe.stripe', function(require) {
|
odoo.define('payment_stripe.stripe', function(require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var ajax = require('web.ajax');
|
var ajax = require('web.ajax');
|
||||||
|
// The following currencies are integer only, see
|
||||||
|
// https://stripe.com/docs/currencies#zero-decimal
|
||||||
|
var int_currencies = [
|
||||||
|
'BIF', 'XAF', 'XPF', 'CLP', 'KMF', 'DJF', 'GNF', 'JPY', 'MGA', 'PYGí',
|
||||||
|
'RWF', 'KRW', 'VUV', 'VND', 'XOF'
|
||||||
|
];
|
||||||
|
|
||||||
var handler = StripeCheckout.configure({
|
var handler = StripeCheckout.configure({
|
||||||
key: $("input[name='stripe_key']").val(),
|
key: $("input[name='stripe_key']").val(),
|
||||||
image: $("input[name='stripe_image']").val(),
|
image: $("input[name='stripe_image']").val(),
|
||||||
@@ -50,19 +58,31 @@ odoo.define('payment_stripe.stripe', function(require) {
|
|||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if ($('.o_website_payment').length !== 0) {
|
if ($('.o_website_payment').length !== 0) {
|
||||||
|
var currency = $("input[name='currency']").val();
|
||||||
|
var amount = parseFloat($("input[name='amount']").val() || '0.0');
|
||||||
|
if (!_.contains(int_currencies, currency)) {
|
||||||
|
amount = amount*100;
|
||||||
|
}
|
||||||
|
|
||||||
ajax.jsonRpc('/website_payment/transaction', 'call', {
|
ajax.jsonRpc('/website_payment/transaction', 'call', {
|
||||||
reference: $("input[name='invoice_num']").val(),
|
reference: $("input[name='invoice_num']").val(),
|
||||||
amount: $("input[name='amount']").val(),
|
amount: amount,
|
||||||
currency_id: $("input[name='currency_id']").val(),
|
currency_id: currency,
|
||||||
acquirer_id: acquirer_id
|
acquirer_id: acquirer_id
|
||||||
})
|
})
|
||||||
handler.open({
|
handler.open({
|
||||||
name: $("input[name='merchant']").val(),
|
name: $("input[name='merchant']").val(),
|
||||||
description: $("input[name='invoice_num']").val(),
|
description: $("input[name='invoice_num']").val(),
|
||||||
currency: $("input[name='currency']").val(),
|
currency: currency,
|
||||||
amount: $("input[name='amount']").val()*100
|
amount: amount,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
var currency = $("input[name='currency']").val();
|
||||||
|
var amount = parseFloat($("input[name='amount']").val() || '0.0');
|
||||||
|
if (!_.contains(int_currencies, currency)) {
|
||||||
|
amount = amount*100;
|
||||||
|
}
|
||||||
|
|
||||||
ajax.jsonRpc('/shop/payment/transaction/' + acquirer_id, 'call', {
|
ajax.jsonRpc('/shop/payment/transaction/' + acquirer_id, 'call', {
|
||||||
so_id: so_id,
|
so_id: so_id,
|
||||||
so_token: so_token
|
so_token: so_token
|
||||||
@@ -71,8 +91,8 @@ odoo.define('payment_stripe.stripe', function(require) {
|
|||||||
handler.open({
|
handler.open({
|
||||||
name: $("input[name='merchant']").val(),
|
name: $("input[name='merchant']").val(),
|
||||||
description: $("input[name='invoice_num']").val(),
|
description: $("input[name='invoice_num']").val(),
|
||||||
currency: $("input[name='currency']").val(),
|
currency: currency,
|
||||||
amount: $("input[name='amount']").val()*100
|
amount: amount,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -836,7 +836,7 @@ class PosOrderLine(models.Model):
|
|||||||
def create(self, values):
|
def create(self, values):
|
||||||
if values.get('order_id') and not values.get('name'):
|
if values.get('order_id') and not values.get('name'):
|
||||||
# set name based on the sequence specified on the config
|
# set name based on the sequence specified on the config
|
||||||
config_id = self.env['pos.order'].browse(values['order_id']).session_id.config_id.id
|
config_id = self.order_id.browse(values['order_id']).session_id.config_id.id
|
||||||
# HACK: sequence created in the same transaction as the config
|
# HACK: sequence created in the same transaction as the config
|
||||||
# cf TODO master is pos.config create
|
# cf TODO master is pos.config create
|
||||||
# remove me saas-15
|
# remove me saas-15
|
||||||
|
|||||||
@@ -396,12 +396,13 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
|||||||
self.assertFalse(self.pos_order_pos1.invoice_id, 'Invoice should not be attached to order.')
|
self.assertFalse(self.pos_order_pos1.invoice_id, 'Invoice should not be attached to order.')
|
||||||
|
|
||||||
# I generate an invoice from the order
|
# I generate an invoice from the order
|
||||||
self.invoice = self.pos_order_pos1.action_pos_order_invoice()
|
res = self.pos_order_pos1.action_pos_order_invoice()
|
||||||
|
self.assertIn('res_id', res, "No invoice created")
|
||||||
|
|
||||||
# I test that the total of the attached invoice is correct
|
# I test that the total of the attached invoice is correct
|
||||||
self.amount_total = self.pos_order_pos1.amount_total
|
invoice = self.env['account.invoice'].browse(res['res_id'])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
float_compare(self.amount_total, 1752.75, precision_digits=2), 0, "Invoice not correct")
|
float_compare(invoice.amount_total, 1752.75, precision_digits=2), 0, "Invoice not correct")
|
||||||
|
|
||||||
"""In order to test the reports on Bank Statement defined in point_of_sale module, I create a bank statement line, confirm it and print the reports"""
|
"""In order to test the reports on Bank Statement defined in point_of_sale module, I create a bank statement line, confirm it and print the reports"""
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<form class="oe_form_configuration">
|
<form class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<h2>Point of Sale</h2>
|
<h2>Point of Sale</h2>
|
||||||
|
|||||||
@@ -295,7 +295,9 @@ class ProductProduct(models.Model):
|
|||||||
@api.model
|
@api.model
|
||||||
def create(self, vals):
|
def create(self, vals):
|
||||||
product = super(ProductProduct, self.with_context(create_product_product=True)).create(vals)
|
product = super(ProductProduct, self.with_context(create_product_product=True)).create(vals)
|
||||||
product._set_standard_price(vals.get('standard_price', 0.0))
|
# When a unique variant is created from tmpl then the standard price is set by _set_standard_price
|
||||||
|
if not (self.env.context.get('create_from_tmpl') and len(product.product_tmpl_id.product_variant_ids) == 1):
|
||||||
|
product._set_standard_price(vals.get('standard_price') or 0.0)
|
||||||
return product
|
return product
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
|||||||
@@ -111,8 +111,7 @@ class Pricelist(models.Model):
|
|||||||
uom_id = self._context['uom']
|
uom_id = self._context['uom']
|
||||||
if uom_id:
|
if uom_id:
|
||||||
# rebrowse with uom if given
|
# rebrowse with uom if given
|
||||||
product_ids = [item[0].id for item in products_qty_partner]
|
products = [item[0].with_context(uom=uom_id) for item in products_qty_partner]
|
||||||
products = self.env['product.product'].with_context(uom=uom_id).browse(product_ids)
|
|
||||||
products_qty_partner = [(products[index], data_struct[1], data_struct[2]) for index, data_struct in enumerate(products_qty_partner)]
|
products_qty_partner = [(products[index], data_struct[1], data_struct[2]) for index, data_struct in enumerate(products_qty_partner)]
|
||||||
else:
|
else:
|
||||||
products = [item[0] for item in products_qty_partner]
|
products = [item[0] for item in products_qty_partner]
|
||||||
|
|||||||
@@ -173,7 +173,9 @@ class ProductTemplate(models.Model):
|
|||||||
|
|
||||||
# Support context pricelists specified as display_name or ID for compatibility
|
# Support context pricelists specified as display_name or ID for compatibility
|
||||||
if isinstance(pricelist_id_or_name, basestring):
|
if isinstance(pricelist_id_or_name, basestring):
|
||||||
pricelist = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1)
|
pricelist_data = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1)
|
||||||
|
if pricelist_data:
|
||||||
|
pricelist = self.env['product.pricelist'].browse(pricelist_data[0][0])
|
||||||
elif isinstance(pricelist_id_or_name, pycompat.integer_types):
|
elif isinstance(pricelist_id_or_name, pycompat.integer_types):
|
||||||
pricelist = self.env['product.pricelist'].browse(pricelist_id_or_name)
|
pricelist = self.env['product.pricelist'].browse(pricelist_id_or_name)
|
||||||
|
|
||||||
@@ -289,7 +291,7 @@ class ProductTemplate(models.Model):
|
|||||||
tools.image_resize_images(vals)
|
tools.image_resize_images(vals)
|
||||||
template = super(ProductTemplate, self).create(vals)
|
template = super(ProductTemplate, self).create(vals)
|
||||||
if "create_product_product" not in self._context:
|
if "create_product_product" not in self._context:
|
||||||
template.create_variant_ids()
|
template.with_context(create_from_tmpl=True).create_variant_ids()
|
||||||
|
|
||||||
# This is needed to set given values to first variant after creation
|
# This is needed to set given values to first variant after creation
|
||||||
related_vals = {}
|
related_vals = {}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<form class="oe_form_configuration">
|
<form class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<h2>Team Collaboration</h2>
|
<h2>Team Collaboration</h2>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<form string="Configure Purchases" class="oe_form_configuration">
|
<form string="Configure Purchases" class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<field name="po_double_validation" invisible="1"/>
|
<field name="po_double_validation" invisible="1"/>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<form string="Configure Sales" class="oe_form_configuration" name="sale_config_form">
|
<form string="Configure Sales" class="oe_form_configuration" name="sale_config_form">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<h2 id="integration_h2" invisible="1">Integrations</h2>
|
<h2 id="integration_h2" invisible="1">Integrations</h2>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class Location(models.Model):
|
|||||||
_sql_constraints = [('barcode_company_uniq', 'unique (barcode,company_id)', 'The barcode for a location must be unique per company !')]
|
_sql_constraints = [('barcode_company_uniq', 'unique (barcode,company_id)', 'The barcode for a location must be unique per company !')]
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
@api.depends('name', 'location_id')
|
@api.depends('name', 'location_id.name')
|
||||||
def _compute_complete_name(self):
|
def _compute_complete_name(self):
|
||||||
""" Forms complete name of location from parent location to child location. """
|
""" Forms complete name of location from parent location to child location. """
|
||||||
name = self.name
|
name = self.name
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<form string="Configure Warehouse" class="oe_form_configuration">
|
<form string="Configure Warehouse" class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<field name="company_id" invisible="1"/>
|
<field name="company_id" invisible="1"/>
|
||||||
|
|||||||
@@ -680,7 +680,7 @@ class Database(http.Controller):
|
|||||||
request.session.authenticate(name, post['login'], password)
|
request.session.authenticate(name, post['login'], password)
|
||||||
return http.local_redirect('/web/')
|
return http.local_redirect('/web/')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = "Database creation error: %s" % str(e) or repr(e)
|
error = "Database creation error: %s" % (str(e) or repr(e))
|
||||||
return self._render_template(error=error)
|
return self._render_template(error=error)
|
||||||
|
|
||||||
@http.route('/web/database/duplicate', type='http', auth="none", methods=['POST'], csrf=False)
|
@http.route('/web/database/duplicate', type='http', auth="none", methods=['POST'], csrf=False)
|
||||||
@@ -691,7 +691,7 @@ class Database(http.Controller):
|
|||||||
dispatch_rpc('db', 'duplicate_database', [master_pwd, name, new_name])
|
dispatch_rpc('db', 'duplicate_database', [master_pwd, name, new_name])
|
||||||
return http.local_redirect('/web/database/manager')
|
return http.local_redirect('/web/database/manager')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = "Database duplication error: %s" % str(e) or repr(e)
|
error = "Database duplication error: %s" % (str(e) or repr(e))
|
||||||
return self._render_template(error=error)
|
return self._render_template(error=error)
|
||||||
|
|
||||||
@http.route('/web/database/drop', type='http', auth="none", methods=['POST'], csrf=False)
|
@http.route('/web/database/drop', type='http', auth="none", methods=['POST'], csrf=False)
|
||||||
@@ -701,7 +701,7 @@ class Database(http.Controller):
|
|||||||
request._cr = None # dropping a database leads to an unusable cursor
|
request._cr = None # dropping a database leads to an unusable cursor
|
||||||
return http.local_redirect('/web/database/manager')
|
return http.local_redirect('/web/database/manager')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = "Database deletion error: %s" % str(e) or repr(e)
|
error = "Database deletion error: %s" % (str(e) or repr(e))
|
||||||
return self._render_template(error=error)
|
return self._render_template(error=error)
|
||||||
|
|
||||||
@http.route('/web/database/backup', type='http', auth="none", methods=['POST'], csrf=False)
|
@http.route('/web/database/backup', type='http', auth="none", methods=['POST'], csrf=False)
|
||||||
@@ -719,7 +719,7 @@ class Database(http.Controller):
|
|||||||
return response
|
return response
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.exception('Database.backup')
|
_logger.exception('Database.backup')
|
||||||
error = "Database backup error: %s" % str(e) or repr(e)
|
error = "Database backup error: %s" % (str(e) or repr(e))
|
||||||
return self._render_template(error=error)
|
return self._render_template(error=error)
|
||||||
|
|
||||||
@http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False)
|
@http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False)
|
||||||
@@ -729,7 +729,7 @@ class Database(http.Controller):
|
|||||||
dispatch_rpc('db', 'restore', [master_pwd, name, data, str2bool(copy)])
|
dispatch_rpc('db', 'restore', [master_pwd, name, data, str2bool(copy)])
|
||||||
return http.local_redirect('/web/database/manager')
|
return http.local_redirect('/web/database/manager')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = "Database restore error: %s" % str(e) or repr(e)
|
error = "Database restore error: %s" % (str(e) or repr(e))
|
||||||
return self._render_template(error=error)
|
return self._render_template(error=error)
|
||||||
|
|
||||||
@http.route('/web/database/change_password', type='http', auth="none", methods=['POST'], csrf=False)
|
@http.route('/web/database/change_password', type='http', auth="none", methods=['POST'], csrf=False)
|
||||||
@@ -738,7 +738,7 @@ class Database(http.Controller):
|
|||||||
dispatch_rpc('db', 'change_admin_password', [master_pwd, master_pwd_new])
|
dispatch_rpc('db', 'change_admin_password', [master_pwd, master_pwd_new])
|
||||||
return http.local_redirect('/web/database/manager')
|
return http.local_redirect('/web/database/manager')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = "Master password update error: %s" % str(e) or repr(e)
|
error = "Master password update error: %s" % (str(e) or repr(e))
|
||||||
return self._render_template(error=error)
|
return self._render_template(error=error)
|
||||||
|
|
||||||
@http.route('/web/database/list', type='json', auth='none')
|
@http.route('/web/database/list', type='json', auth='none')
|
||||||
|
|||||||
@@ -416,8 +416,9 @@ animation.registry.media_video = animation.Class.extend({
|
|||||||
if (!this.$target.has('> iframe').length) {
|
if (!this.$target.has('> iframe').length) {
|
||||||
var editor = '<div class="css_editable_mode_display"> </div>';
|
var editor = '<div class="css_editable_mode_display"> </div>';
|
||||||
var size = '<div class="media_iframe_video_size"> </div>';
|
var size = '<div class="media_iframe_video_size"> </div>';
|
||||||
this.$target.html(editor+size+'<iframe src="'+_.escape(this.$target.data("src"))+'" frameborder="0" allowfullscreen="allowfullscreen"></iframe>');
|
this.$target.html(editor+size);
|
||||||
}
|
}
|
||||||
|
this.$target.html(this.$target.html()+'<iframe src="'+_.escape(this.$target.data("src"))+'" frameborder="0" allowfullscreen="allowfullscreen"></iframe>');
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<form string="Configure Website" class="oe_form_configuration">
|
<form string="Configure Website" class="oe_form_configuration">
|
||||||
<header>
|
<header>
|
||||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link" special="cancel"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ class WebsiteEventController(http.Controller):
|
|||||||
'event': event,
|
'event': event,
|
||||||
'main_object': event,
|
'main_object': event,
|
||||||
'range': range,
|
'range': range,
|
||||||
|
'registrable': event._is_event_registrable()
|
||||||
}
|
}
|
||||||
return request.render("website_event.event_description_full", values)
|
return request.render("website_event.event_description_full", values)
|
||||||
|
|
||||||
@@ -225,7 +226,7 @@ class WebsiteEventController(http.Controller):
|
|||||||
def registration_new(self, event, **post):
|
def registration_new(self, event, **post):
|
||||||
tickets = self._process_tickets_details(post)
|
tickets = self._process_tickets_details(post)
|
||||||
if not tickets:
|
if not tickets:
|
||||||
return request.redirect("/event/%s" % slug(event))
|
return False
|
||||||
return request.env['ir.ui.view'].render_template("website_event.registration_attendee_details", {'tickets': tickets, 'event': event})
|
return request.env['ir.ui.view'].render_template("website_event.registration_attendee_details", {'tickets': tickets, 'event': event})
|
||||||
|
|
||||||
def _process_registration_details(self, details):
|
def _process_registration_details(self, details):
|
||||||
|
|||||||
@@ -311,7 +311,8 @@
|
|||||||
<template id="event_description_full">
|
<template id="event_description_full">
|
||||||
<t t-call="website_event.event_details">
|
<t t-call="website_event.event_details">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<t t-call="website_event.registration_template"/>
|
<div t-if="event.state == 'done' or not registrable" class="alert alert-info">Registration for this event is now closed</div>
|
||||||
|
<t t-if="event.state != 'done' and registrable" t-call="website_event.registration_template"/>
|
||||||
<div class="clearfix"/>
|
<div class="clearfix"/>
|
||||||
<hr/>
|
<hr/>
|
||||||
<div itemprop="description" t-field="event.description"></div>
|
<div itemprop="description" t-field="event.description"></div>
|
||||||
|
|||||||
@@ -11,12 +11,7 @@ class WebsiteEventSaleController(WebsiteEventController):
|
|||||||
@http.route(['/event/<model("event.event"):event>/register'], type='http', auth="public", website=True)
|
@http.route(['/event/<model("event.event"):event>/register'], type='http', auth="public", website=True)
|
||||||
def event_register(self, event, **post):
|
def event_register(self, event, **post):
|
||||||
event = event.with_context(pricelist=request.website.get_current_pricelist().id)
|
event = event.with_context(pricelist=request.website.get_current_pricelist().id)
|
||||||
values = {
|
return super(WebsiteEventSaleController, self).event_register(event, **post)
|
||||||
'event': event,
|
|
||||||
'main_object': event,
|
|
||||||
'range': range,
|
|
||||||
}
|
|
||||||
return request.render("website_event.event_description_full", values)
|
|
||||||
|
|
||||||
def _process_tickets_details(self, data):
|
def _process_tickets_details(self, data):
|
||||||
ticket_post = {}
|
ticket_post = {}
|
||||||
|
|||||||
@@ -364,7 +364,8 @@ class WebsiteSale(http.Controller):
|
|||||||
values['suggested_products'] = _order._cart_accessories()
|
values['suggested_products'] = _order._cart_accessories()
|
||||||
|
|
||||||
if post.get('type') == 'popover':
|
if post.get('type') == 'popover':
|
||||||
return request.render("website_sale.cart_popover", values)
|
# force no-cache so IE11 doesn't cache this XHR
|
||||||
|
return request.render("website_sale.cart_popover", values, headers={'Cache-Control': 'no-cache'})
|
||||||
|
|
||||||
return request.render("website_sale.cart", values)
|
return request.render("website_sale.cart", values)
|
||||||
|
|
||||||
|
|||||||
@@ -1205,11 +1205,11 @@
|
|||||||
<div class="clearfix" />
|
<div class="clearfix" />
|
||||||
<t t-if="mode == ('new', 'billing')">
|
<t t-if="mode == ('new', 'billing')">
|
||||||
<div t-attf-class="form-group #{error.get('company_name') and 'has-error' or ''} col-md-6">
|
<div t-attf-class="form-group #{error.get('company_name') and 'has-error' or ''} col-md-6">
|
||||||
<label class="control-label font-weight-normal" for="company_name">Company Name</label>
|
<label class="control-label font-weight-normal label-optional" for="company_name">Company Name</label>
|
||||||
<input type="text" name="company_name" class="form-control" t-att-value="'company_name' in checkout and checkout['company_name']" />
|
<input type="text" name="company_name" class="form-control" t-att-value="'company_name' in checkout and checkout['company_name']" />
|
||||||
</div>
|
</div>
|
||||||
<div t-attf-class="form-group #{error.get('vat') and 'has-error' or ''} col-md-6 div_vat">
|
<div t-attf-class="form-group #{error.get('vat') and 'has-error' or ''} col-md-6 div_vat">
|
||||||
<label class="control-label font-weight-normal" for="vat">TIN / VAT </label>
|
<label class="control-label font-weight-normal label-optional" for="vat">TIN / VAT </label>
|
||||||
<input type="text" name="vat" class="form-control" t-att-value="'vat' in checkout and checkout['vat']" />
|
<input type="text" name="vat" class="form-control" t-att-value="'vat' in checkout and checkout['vat']" />
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
@@ -1219,14 +1219,14 @@
|
|||||||
<input type="text" name="street" class="form-control" t-att-value="'street' in checkout and checkout['street']" />
|
<input type="text" name="street" class="form-control" t-att-value="'street' in checkout and checkout['street']" />
|
||||||
</div>
|
</div>
|
||||||
<div t-attf-class="form-group #{error.get('street2') and 'has-error' or ''} col-md-12 div_street2">
|
<div t-attf-class="form-group #{error.get('street2') and 'has-error' or ''} col-md-12 div_street2">
|
||||||
<label class="control-label" for="street2">Street 2</label>
|
<label class="control-label label-optional" for="street2">Street 2</label>
|
||||||
<input type="text" name="street2" class="form-control" t-att-value="'street2' in checkout and checkout['street2']" />
|
<input type="text" name="street2" class="form-control" t-att-value="'street2' in checkout and checkout['street2']" />
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix" />
|
<div class="clearfix" />
|
||||||
<t t-set='zip_city' t-value='country and [x for x in country.get_address_fields() if x in ["zip", "city"]] or ["city", "zip"]'/>
|
<t t-set='zip_city' t-value='country and [x for x in country.get_address_fields() if x in ["zip", "city"]] or ["city", "zip"]'/>
|
||||||
<t t-if="'zip' in zip_city and zip_city.index('zip') < zip_city.index('city')">
|
<t t-if="'zip' in zip_city and zip_city.index('zip') < zip_city.index('city')">
|
||||||
<div t-attf-class="form-group #{error.get('zip') and 'has-error' or ''} col-sm-4 div_zip">
|
<div t-attf-class="form-group #{error.get('zip') and 'has-error' or ''} col-sm-4 div_zip">
|
||||||
<label class="control-label" for="zip">Zip Code</label>
|
<label class="control-label label-optional" for="zip">Zip Code</label>
|
||||||
<input type="text" name="zip" class="form-control" t-att-value="'zip' in checkout and checkout['zip']" />
|
<input type="text" name="zip" class="form-control" t-att-value="'zip' in checkout and checkout['zip']" />
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
@@ -1236,7 +1236,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<t t-if="'zip' in zip_city and zip_city.index('zip') > zip_city.index('city')">
|
<t t-if="'zip' in zip_city and zip_city.index('zip') > zip_city.index('city')">
|
||||||
<div t-attf-class="form-group #{error.get('zip') and 'has-error' or ''} col-sm-4 div_zip">
|
<div t-attf-class="form-group #{error.get('zip') and 'has-error' or ''} col-sm-4 div_zip">
|
||||||
<label class="control-label" for="zip">Zip Code</label>
|
<label class="control-label label-optional" for="zip">Zip Code</label>
|
||||||
<input type="text" name="zip" class="form-control" t-att-value="'zip' in checkout and checkout['zip']" />
|
<input type="text" name="zip" class="form-control" t-att-value="'zip' in checkout and checkout['zip']" />
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
Finland, 2017-08-01
|
||||||
|
|
||||||
|
Vizucom Oy agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0.
|
||||||
|
|
||||||
|
We declare that we are authorized and able to make this agreement and sign this declaration.
|
||||||
|
|
||||||
|
Signed,
|
||||||
|
|
||||||
|
Timo Talvitie timo.talvitie@vizucom.com https://github.com/timotalvitie
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
|
||||||
|
Timo Talvitie timo.talvitie@vizucom.com https://github.com/timotalvitie
|
||||||
|
Henri Alasentie henri.alasentie@vizucom.com https://github.com/halasentie
|
||||||
@@ -188,7 +188,7 @@ class CurrencyRate(models.Model):
|
|||||||
rate = fields.Float(digits=(12, 6), help='The rate of the currency to the currency of rate 1')
|
rate = fields.Float(digits=(12, 6), help='The rate of the currency to the currency of rate 1')
|
||||||
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True)
|
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True)
|
||||||
company_id = fields.Many2one('res.company', string='Company',
|
company_id = fields.Many2one('res.company', string='Company',
|
||||||
default=lambda self: self.env.user._get_company())
|
default=lambda self: self.env.user.company_id)
|
||||||
|
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
('unique_name_per_day', 'unique (name,currency_id,company_id)', 'Only one currency rate per day allowed!'),
|
('unique_name_per_day', 'unique (name,currency_id,company_id)', 'Only one currency rate per day allowed!'),
|
||||||
|
|||||||
+2
-1
@@ -558,7 +558,7 @@ class Field(object):
|
|||||||
""" Traverse the fields of the related field `self` except for the last
|
""" Traverse the fields of the related field `self` except for the last
|
||||||
one, and return it as a pair `(last_record, last_field)`. """
|
one, and return it as a pair `(last_record, last_field)`. """
|
||||||
for name in self.related[:-1]:
|
for name in self.related[:-1]:
|
||||||
record = record[name][:1]
|
record = record[name][:1].with_prefetch(record._prefetch)
|
||||||
return record, self.related_field
|
return record, self.related_field
|
||||||
|
|
||||||
def _compute_related(self, records):
|
def _compute_related(self, records):
|
||||||
@@ -1023,6 +1023,7 @@ class Field(object):
|
|||||||
self.compute_value(record)
|
self.compute_value(record)
|
||||||
else:
|
else:
|
||||||
recs = record._in_cache_without(self)
|
recs = record._in_cache_without(self)
|
||||||
|
recs = recs.with_prefetch(record._prefetch)
|
||||||
self.compute_value(recs)
|
self.compute_value(recs)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1435,6 +1435,7 @@ class Root(object):
|
|||||||
httprequest = werkzeug.wrappers.Request(environ)
|
httprequest = werkzeug.wrappers.Request(environ)
|
||||||
httprequest.app = self
|
httprequest.app = self
|
||||||
httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableOrderedMultiDict
|
httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableOrderedMultiDict
|
||||||
|
threading.current_thread().url = httprequest.url
|
||||||
|
|
||||||
explicit_session = self.setup_session(httprequest)
|
explicit_session = self.setup_session(httprequest)
|
||||||
self.setup_db(httprequest)
|
self.setup_db(httprequest)
|
||||||
|
|||||||
+8
-9
@@ -2554,10 +2554,6 @@ class BaseModel(object):
|
|||||||
else:
|
else:
|
||||||
records &= self._in_cache_without(f)
|
records &= self._in_cache_without(f)
|
||||||
|
|
||||||
# prefetch at most PREFETCH_MAX records
|
|
||||||
if len(records) > PREFETCH_MAX:
|
|
||||||
records = records[:PREFETCH_MAX] | self
|
|
||||||
|
|
||||||
# fetch records with read()
|
# fetch records with read()
|
||||||
assert self in records and field in fs
|
assert self in records and field in fs
|
||||||
records = records.with_prefetch(self._prefetch)
|
records = records.with_prefetch(self._prefetch)
|
||||||
@@ -4682,13 +4678,16 @@ class BaseModel(object):
|
|||||||
return RecordCache(self)
|
return RecordCache(self)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _in_cache_without(self, field):
|
def _in_cache_without(self, field, limit=PREFETCH_MAX):
|
||||||
""" Make sure ``self`` is present in cache (for prefetching), and return
|
""" Return records to prefetch that have no value in cache for ``field``
|
||||||
the records of model ``self`` in cache that have no value for ``field``
|
(:class:`Field` instance), including ``self``.
|
||||||
(:class:`Field` instance).
|
Return at most ``limit`` records.
|
||||||
"""
|
"""
|
||||||
ids = filter(None, self._prefetch[self._name] - set(self.env.cache[field]))
|
ids = filter(None, self._prefetch[self._name] - set(self.env.cache[field]))
|
||||||
return self.browse(ids)
|
recs = self.browse(ids)
|
||||||
|
if limit and len(recs) > limit:
|
||||||
|
recs = self + (recs - self)[:(limit - len(self))]
|
||||||
|
return recs
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
|
|||||||
@@ -161,6 +161,8 @@ def application_unproxied(environ, start_response):
|
|||||||
del threading.current_thread().uid
|
del threading.current_thread().uid
|
||||||
if hasattr(threading.current_thread(), 'dbname'):
|
if hasattr(threading.current_thread(), 'dbname'):
|
||||||
del threading.current_thread().dbname
|
del threading.current_thread().dbname
|
||||||
|
if hasattr(threading.current_thread(), 'url'):
|
||||||
|
del threading.current_thread().url
|
||||||
|
|
||||||
with odoo.api.Environment.manage():
|
with odoo.api.Environment.manage():
|
||||||
# Try all handlers until one returns some result (i.e. not None).
|
# Try all handlers until one returns some result (i.e. not None).
|
||||||
|
|||||||
@@ -398,11 +398,17 @@ class HttpCase(TransactionCase):
|
|||||||
t0 = int(time.time())
|
t0 = int(time.time())
|
||||||
for thread in threading.enumerate():
|
for thread in threading.enumerate():
|
||||||
if thread.name.startswith('odoo.service.http.request.'):
|
if thread.name.startswith('odoo.service.http.request.'):
|
||||||
|
thread.join_retry_count = 10
|
||||||
while thread.isAlive():
|
while thread.isAlive():
|
||||||
# Need a busyloop here as thread.join() masks signals
|
# Need a busyloop here as thread.join() masks signals
|
||||||
# and would prevent the forced shutdown.
|
# and would prevent the forced shutdown.
|
||||||
thread.join(0.05)
|
thread.join(0.05)
|
||||||
time.sleep(0.05)
|
thread.join_retry_count -= 1
|
||||||
|
if thread.join_retry_count < 0:
|
||||||
|
_logger.warning("Stop waiting for thread %s handling request for url %s",
|
||||||
|
thread.name, thread.url)
|
||||||
|
break
|
||||||
|
time.sleep(0.5)
|
||||||
t1 = int(time.time())
|
t1 = int(time.time())
|
||||||
if t0 != t1:
|
if t0 != t1:
|
||||||
_logger.info('remaining requests')
|
_logger.info('remaining requests')
|
||||||
|
|||||||
+5
-3
@@ -939,15 +939,17 @@ def dumpstacks(sig=None, frame=None):
|
|||||||
# modified for python 2.5 compatibility
|
# modified for python 2.5 compatibility
|
||||||
threads_info = {th.ident: {'name': th.name,
|
threads_info = {th.ident: {'name': th.name,
|
||||||
'uid': getattr(th, 'uid', 'n/a'),
|
'uid': getattr(th, 'uid', 'n/a'),
|
||||||
'dbname': getattr(th, 'dbname', 'n/a')}
|
'dbname': getattr(th, 'dbname', 'n/a'),
|
||||||
|
'url': getattr(th, 'url', 'n/a')}
|
||||||
for th in threading.enumerate()}
|
for th in threading.enumerate()}
|
||||||
for threadId, stack in sys._current_frames().items():
|
for threadId, stack in sys._current_frames().items():
|
||||||
thread_info = threads_info.get(threadId, {})
|
thread_info = threads_info.get(threadId, {})
|
||||||
code.append("\n# Thread: %s (id:%s) (db:%s) (uid:%s)" %
|
code.append("\n# Thread: %s (id:%s) (db:%s) (uid:%s) (url:%s)" %
|
||||||
(thread_info.get('name', 'n/a'),
|
(thread_info.get('name', 'n/a'),
|
||||||
threadId,
|
threadId,
|
||||||
thread_info.get('dbname', 'n/a'),
|
thread_info.get('dbname', 'n/a'),
|
||||||
thread_info.get('uid', 'n/a')))
|
thread_info.get('uid', 'n/a'),
|
||||||
|
thread_info.get('url', 'n/a')))
|
||||||
for line in extract_stack(stack):
|
for line in extract_stack(stack):
|
||||||
code.append(line)
|
code.append(line)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user