[MERGE] forward port branch saas-12.2 up to 18c735361a
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from collections import OrderedDict
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
@@ -539,10 +540,7 @@ class AccountInvoice(models.Model):
|
||||
if not vals.get('journal_id') and vals.get('type'):
|
||||
vals['journal_id'] = self.with_context(type=vals.get('type'))._default_journal().id
|
||||
|
||||
onchanges = {
|
||||
'_onchange_partner_id': ['account_id', 'payment_term_id', 'fiscal_position_id', 'partner_bank_id'],
|
||||
'_onchange_journal_id': ['currency_id'],
|
||||
}
|
||||
onchanges = self._get_onchange_create()
|
||||
for onchange_method, changed_fields in onchanges.items():
|
||||
if any(f not in vals for f in changed_fields):
|
||||
invoice = self.new(vals)
|
||||
@@ -696,7 +694,7 @@ class AccountInvoice(models.Model):
|
||||
"""
|
||||
# Split `From` and `CC` email address from received email to look for related partners to subscribe on the invoice
|
||||
subscribed_emails = email_split((msg_dict.get('from') or '') + ',' + (msg_dict.get('cc') or ''))
|
||||
subscribed_partner_ids = [pid for pid in self._find_partner_from_emails(subscribed_emails) if pid]
|
||||
seen_partner_ids = [pid for pid in self._find_partner_from_emails(subscribed_emails) if pid]
|
||||
|
||||
# Detection of the partner_id of the invoice:
|
||||
# 1) check if the email_from correspond to a supplier
|
||||
@@ -720,7 +718,7 @@ class AccountInvoice(models.Model):
|
||||
|
||||
# If the partner_id can be found, subscribe it to the bill, otherwise it's left empty to be manually filled
|
||||
if partner_id:
|
||||
subscribed_partner_ids.append(partner_id)
|
||||
seen_partner_ids.append(partner_id)
|
||||
|
||||
# Find the right purchase journal based on the "TO" email address
|
||||
destination_emails = email_split((msg_dict.get('to') or '') + ',' + (msg_dict.get('cc') or ''))
|
||||
@@ -736,9 +734,13 @@ class AccountInvoice(models.Model):
|
||||
# Passing `type` in context so that _default_journal(...) can correctly set journal for new vendor bill
|
||||
invoice = super(AccountInvoice, self.with_context(type=values.get('type'))).message_new(msg_dict, values)
|
||||
|
||||
# Subscribe people on the newly created bill
|
||||
if subscribed_partner_ids:
|
||||
invoice.message_subscribe(subscribed_partner_ids)
|
||||
# Subscribe internal users on the newly created bill
|
||||
partners = self.env['res.partner'].browse(seen_partner_ids)
|
||||
is_internal = lambda p: (p.user_ids and
|
||||
all(p.user_ids.mapped(lambda u: u.user_has_groups('base.group_user'))))
|
||||
partners_to_subscribe = partners.filtered(is_internal)
|
||||
if partners_to_subscribe:
|
||||
invoice.message_subscribe([p.id for p in partners_to_subscribe])
|
||||
return invoice
|
||||
|
||||
@api.model
|
||||
@@ -1657,6 +1659,13 @@ class AccountInvoice(models.Model):
|
||||
def _get_intrastat_country_id(self):
|
||||
return self.partner_id.country_id.id
|
||||
|
||||
def _get_onchange_create(self):
|
||||
return OrderedDict([
|
||||
('_onchange_partner_id', ['account_id', 'payment_term_id', 'fiscal_position_id', 'partner_bank_id']),
|
||||
('_onchange_journal_id', ['currency_id']),
|
||||
])
|
||||
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
_name = "account.invoice.line"
|
||||
_description = "Invoice Line"
|
||||
|
||||
@@ -391,10 +391,10 @@ class account_journal(models.Model):
|
||||
action_name = 'action_view_bank_statement_tree'
|
||||
elif self.type == 'sale':
|
||||
action_name = 'action_invoice_tree1'
|
||||
self = self.with_context(use_domain=[('type', 'in', ['out_invoice', 'out_refund'])])
|
||||
self = self.with_context(use_domain=[('journal_id', '=', self.id)])
|
||||
elif self.type == 'purchase':
|
||||
action_name = 'action_vendor_bill_template'
|
||||
self = self.with_context(use_domain=[('type', 'in', ['in_invoice', 'in_refund'])])
|
||||
self = self.with_context(use_domain=[('journal_id', '=', self.id)])
|
||||
else:
|
||||
action_name = 'action_move_journal_line'
|
||||
|
||||
|
||||
@@ -1086,7 +1086,7 @@ class AccountMoveLine(models.Model):
|
||||
writeoff_moves += writeoff_move
|
||||
# writeoff_move.post()
|
||||
|
||||
line_to_reconcile += writeoff_move.line_ids.filtered(lambda r: r.account_id == self[0].account_id)
|
||||
line_to_reconcile += writeoff_move.line_ids.filtered(lambda r: r.account_id == self[0].account_id).sorted(key='id')[-1:]
|
||||
if writeoff_moves:
|
||||
writeoff_moves.post()
|
||||
# Return the writeoff move.line which is to be reconciled
|
||||
|
||||
@@ -665,9 +665,7 @@ class account_payment(models.Model):
|
||||
if any(len(record.invoice_ids) != 1 for record in self):
|
||||
# For multiple invoices, there is account.register.payments wizard
|
||||
raise UserError(_("This method should only be called to process a single invoice's payment."))
|
||||
res = self.post()
|
||||
self.mapped('payment_transaction_id').filtered(lambda x: x.state == 'done' and not x.is_processed)._post_process_after_done()
|
||||
return res
|
||||
return self.post()
|
||||
|
||||
def _create_payment_entry(self, amount):
|
||||
""" Create a journal entry corresponding to a payment, if the payment references invoice(s) they are reconciled.
|
||||
|
||||
@@ -418,6 +418,9 @@ class AccountReconcileModel(models.Model):
|
||||
aml.date_maturity AS aml_date_maturity,
|
||||
aml.amount_residual AS aml_amount_residual,
|
||||
aml.amount_residual_currency AS aml_amount_residual_currency,
|
||||
aml.balance AS aml_balance,
|
||||
aml.amount_currency AS aml_amount_currency,
|
||||
account.internal_type AS account_internal_type,
|
||||
|
||||
-- Determine a matching or not with the statement line communication using the move.name or move.ref.
|
||||
regexp_split_to_array(TRIM(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g')),'\s+')
|
||||
@@ -565,9 +568,12 @@ class AccountReconcileModel(models.Model):
|
||||
return True
|
||||
|
||||
# Match total residual amount.
|
||||
total_residual = sum(
|
||||
aml['aml_currency_id'] and aml['aml_amount_residual_currency'] or aml['aml_amount_residual'] for aml in
|
||||
candidates)
|
||||
total_residual = 0.0
|
||||
for aml in candidates:
|
||||
if aml['account_internal_type'] == 'liquidity':
|
||||
total_residual += aml['aml_currency_id'] and aml['aml_amount_currency'] or aml['aml_balance']
|
||||
else:
|
||||
total_residual += aml['aml_currency_id'] and aml['aml_amount_residual_currency'] or aml['aml_amount_residual']
|
||||
line_residual = statement_line.currency_id and statement_line.amount_currency or statement_line.amount
|
||||
line_currency = statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.company_id.currency_id
|
||||
|
||||
|
||||
@@ -471,6 +471,67 @@ class TestReconciliation(AccountingTestCase):
|
||||
self.assertEquals(aml.amount_residual, 0, 'The journal item should be totally reconciled')
|
||||
self.assertEquals(aml.amount_residual_currency, 0, 'The journal item should be totally reconciled')
|
||||
|
||||
def test_manual_reconcile_wizard_same_account(self):
|
||||
move_ids = self.env['account.move']
|
||||
debit_line_vals = {
|
||||
'name': '1',
|
||||
'debit': 728.35,
|
||||
'credit': 0.0,
|
||||
'account_id': self.account_rcv.id,
|
||||
'amount_currency': 795.05,
|
||||
'currency_id': self.currency_swiss_id,
|
||||
}
|
||||
credit_line_vals = {
|
||||
'name': '1',
|
||||
'debit': 0.0,
|
||||
'credit': 728.35,
|
||||
'account_id': self.account_rsa.id,
|
||||
'amount_currency': -795.05,
|
||||
'currency_id': self.currency_swiss_id,
|
||||
}
|
||||
vals = {
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'date': time.strftime('%Y') + '-02-15',
|
||||
'line_ids': [(0,0, debit_line_vals), (0, 0, credit_line_vals)]
|
||||
}
|
||||
move_ids += self.env['account.move'].create(vals)
|
||||
debit_line_vals = {
|
||||
'name': '2',
|
||||
'debit': 0.0,
|
||||
'credit': 737.10,
|
||||
'account_id': self.account_rcv.id,
|
||||
'amount_currency': -811.25,
|
||||
'currency_id': self.currency_swiss_id,
|
||||
}
|
||||
credit_line_vals = {
|
||||
'name': '2',
|
||||
'debit': 737.10,
|
||||
'credit': 0.0,
|
||||
'account_id': self.account_rsa.id,
|
||||
'amount_currency': 811.25,
|
||||
'currency_id': self.currency_swiss_id,
|
||||
}
|
||||
vals = {
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'date': time.strftime('%Y') + '-07-15',
|
||||
'line_ids': [(0,0, debit_line_vals), (0, 0, credit_line_vals)]
|
||||
}
|
||||
move_ids += self.env['account.move'].create(vals)
|
||||
|
||||
account_move_line = move_ids.mapped('line_ids').filtered(lambda l: l.account_id == self.account_rcv)
|
||||
writeoff_vals = [{
|
||||
'account_id': self.account_rcv.id,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'date': time.strftime('%Y') + '-04-15',
|
||||
'debit': 8.75,
|
||||
'credit': 0.0
|
||||
}]
|
||||
writeoff_line = account_move_line._create_writeoff(writeoff_vals)
|
||||
(account_move_line + writeoff_line).reconcile()
|
||||
self.assertEquals(len(writeoff_line), 1, "The writeoff_line (balance_line) should have only one moves line")
|
||||
self.assertTrue(all(l.reconciled for l in writeoff_line), 'The balance lines should be totally reconciled')
|
||||
self.assertTrue(all(l.reconciled for l in account_move_line), 'The move lines should be totally reconciled')
|
||||
|
||||
def test_reconcile_bank_statement_with_payment_and_writeoff(self):
|
||||
# Use case:
|
||||
# Company is in EUR, create a bill for 80 USD and register payment of 80 USD.
|
||||
|
||||
@@ -24,8 +24,10 @@ class TestReconciliationMatchingRules(AccountingTestCase):
|
||||
lines = invoice.move_id.line_ids
|
||||
return lines.filtered(lambda l: l.account_id == invoice.account_id)
|
||||
|
||||
def _check_statement_matching(self, rules, expected_values):
|
||||
statement_lines = (self.bank_st + self.cash_st).mapped('line_ids')
|
||||
def _check_statement_matching(self, rules, expected_values, statements=None):
|
||||
if statements is None:
|
||||
statements = self.bank_st + self.cash_st
|
||||
statement_lines = statements.mapped('line_ids')
|
||||
matching_values = rules._apply_rules(statement_lines)
|
||||
for st_line_id, values in matching_values.items():
|
||||
values.pop('reconciled_lines', None)
|
||||
@@ -34,6 +36,10 @@ class TestReconciliationMatchingRules(AccountingTestCase):
|
||||
def setUp(self):
|
||||
super(AccountingTestCase, self).setUp()
|
||||
|
||||
self.account_pay = self.env['account.account'].search([('internal_type', '=', 'payable')], limit=1)
|
||||
self.account_liq = self.env['account.account'].search([('internal_type', '=', 'liquidity')], limit=1)
|
||||
self.account_rcv = self.env['account.account'].search([('internal_type', '=', 'receivable')], limit=1)
|
||||
|
||||
self.partner_1 = self.env['res.partner'].create({'name': 'partner_1'})
|
||||
self.partner_2 = self.env['res.partner'].create({'name': 'partner_2'})
|
||||
|
||||
@@ -45,7 +51,8 @@ class TestReconciliationMatchingRules(AccountingTestCase):
|
||||
current_assets_account = self.env['account.account'].search(
|
||||
[('user_type_id', '=', self.env.ref('account.data_account_type_current_assets').id)], limit=1)
|
||||
|
||||
self.rule_1 = self.env.ref('account.reconciliation_model_default_rule')
|
||||
self.rule_0 = self.env.ref('account.reconciliation_model_default_rule')
|
||||
self.rule_1 = self.rule_0.copy()
|
||||
self.rule_1.account_id = current_assets_account
|
||||
self.rule_1.match_partner = True
|
||||
self.rule_1.match_partner_ids |= self.partner_1 + self.partner_2
|
||||
@@ -59,10 +66,10 @@ class TestReconciliationMatchingRules(AccountingTestCase):
|
||||
|
||||
invoice_number = self.invoice_line_1.move_id.name
|
||||
|
||||
bank_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1)
|
||||
self.bank_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1)
|
||||
|
||||
self.bank_st = self.env['account.bank.statement'].create({
|
||||
'name': 'test bank journal', 'journal_id': bank_journal.id,
|
||||
'name': 'test bank journal', 'journal_id': self.bank_journal.id,
|
||||
})
|
||||
self.bank_line_1 = self.env['account.bank.statement.line'].create({
|
||||
'statement_id': self.bank_st.id,
|
||||
@@ -296,3 +303,47 @@ class TestReconciliationMatchingRules(AccountingTestCase):
|
||||
{'partner_id': self.partner_2.id, 'debit': 0.0, 'credit': 1000.0},
|
||||
{'partner_id': self.partner_2.id, 'debit': 1000.0, 'credit': 0.0},
|
||||
])
|
||||
|
||||
def test_reverted_move_matching(self):
|
||||
AccountMove = self.env['account.move']
|
||||
move = AccountMove.create({
|
||||
'name': 'To Revert',
|
||||
'journal_id': self.bank_journal.id,
|
||||
})
|
||||
|
||||
partner = self.env['res.partner'].create({'name': 'Eugene'})
|
||||
AccountMoveLine = self.env['account.move.line'].with_context(check_move_validity=False)
|
||||
payment_payable_line = AccountMoveLine.create({
|
||||
'account_id': self.account_pay.id,
|
||||
'move_id': move.id,
|
||||
'partner_id': partner.id,
|
||||
'name': 'One of these days',
|
||||
'debit': 10,
|
||||
})
|
||||
payment_bnk_line = AccountMoveLine.create({
|
||||
'account_id': self.account_liq.id,
|
||||
'move_id': move.id,
|
||||
'partner_id': partner.id,
|
||||
'name': 'I\'m gonna cut you into little pieces',
|
||||
'credit': 10,
|
||||
})
|
||||
|
||||
move.post()
|
||||
move_reversed = AccountMove.browse(move.reverse_moves())
|
||||
self.assertTrue(move_reversed.exists())
|
||||
|
||||
bank_st = self.env['account.bank.statement'].create({
|
||||
'name': 'test bank journal', 'journal_id': self.bank_journal.id,
|
||||
})
|
||||
bank_line_1 = self.env['account.bank.statement.line'].create({
|
||||
'statement_id': bank_st.id,
|
||||
'name': '8',
|
||||
'partner_id': partner.id,
|
||||
'amount': -10,
|
||||
'sequence': 1,
|
||||
})
|
||||
|
||||
expected_values = {
|
||||
bank_line_1.id: {'aml_ids': [payment_bnk_line.id], 'model': self.rule_0}
|
||||
}
|
||||
self._check_statement_matching(self.rule_0, expected_values, statements=bank_st)
|
||||
|
||||
@@ -51,29 +51,26 @@
|
||||
<table class="table table-sm o_main_table" name="invoice_line_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<t t-set="colspan" t-value="6"/>
|
||||
<th class="text-left"><span>Description</span></th>
|
||||
<th class="d-none text-left"><span>Source Document</span></th>
|
||||
<th class="text-right"><span>Quantity</span></th>
|
||||
<th t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}"><span>Unit Price</span></th>
|
||||
<th t-if="display_discount" t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
|
||||
<span>Disc.(%)</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}"><span>Taxes</span></th>
|
||||
<th class="text-right">
|
||||
<span groups="account.group_show_line_subtotals_tax_excluded">Amount</span>
|
||||
<span groups="account.group_show_line_subtotals_tax_included">Total Price</span>
|
||||
</th>
|
||||
<th class="text-right" groups="account.group_show_line_subtotals_tax_excluded">Amount</th>
|
||||
<th class="text-right" groups="account.group_show_line_subtotals_tax_included">Total Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="invoice_tbody">
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_excluded" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_included" t-value="0"/>
|
||||
|
||||
<t t-foreach="o.invoice_line_ids" t-as="line">
|
||||
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
<t t-set="current_subtotal_tax_excluded" t-value="current_subtotal_tax_excluded + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<t t-set="current_subtotal_tax_included" t-value="current_subtotal_tax_included + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
|
||||
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''">
|
||||
<t t-if="not line.display_type" name="account_invoice_line_accountable">
|
||||
@@ -92,20 +89,23 @@
|
||||
<td t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.invoice_line_tax_ids))"/>
|
||||
</td>
|
||||
<td class="text-right o_price_total">
|
||||
<span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
<td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_excluded">
|
||||
<span t-field="line.price_subtotal"/>
|
||||
</td>
|
||||
<td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_included">
|
||||
<span t-field="line.price_total"/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_section'">
|
||||
<td t-att-colspan="colspan">
|
||||
<td colspan="99">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
<t t-set="current_section" t-value="line"/>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_excluded" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_included" t-value="0"/>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_note'">
|
||||
<td t-att-colspan="colspan">
|
||||
<td colspan="99">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
</t>
|
||||
@@ -113,12 +113,17 @@
|
||||
|
||||
<t t-if="current_section and (line_last or o.invoice_line_ids[line_index+1].display_type == 'line_section')">
|
||||
<tr class="is-subtotal text-right">
|
||||
<td t-att-colspan="colspan">
|
||||
<td colspan="99">
|
||||
<strong class="mr16">Subtotal</strong>
|
||||
<span
|
||||
t-esc="current_subtotal"
|
||||
groups="account.group_show_line_subtotals_tax_excluded"
|
||||
t-esc="current_subtotal_tax_excluded"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'
|
||||
/>
|
||||
<span groups="account.group_show_line_subtotals_tax_included"> | <span
|
||||
t-esc="current_subtotal_tax_included"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'
|
||||
/></span>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
@@ -28,7 +28,7 @@ class AccountRegisterPayments(models.TransientModel):
|
||||
def _onchange_amount(self):
|
||||
if hasattr(super(AccountRegisterPayments, self), '_onchange_amount'):
|
||||
super(AccountRegisterPayments, self)._onchange_amount()
|
||||
self.check_amount_in_words = self.currency_id.amount_to_text(self.amount)
|
||||
self.check_amount_in_words = self.currency_id.amount_to_text(self.amount) if self.currency_id else False
|
||||
|
||||
def _prepare_payment_vals(self, invoices):
|
||||
res = super(AccountRegisterPayments, self)._prepare_payment_vals(invoices)
|
||||
|
||||
@@ -131,7 +131,7 @@ class ImDispatch(object):
|
||||
|
||||
event = self.Event()
|
||||
for channel in channels:
|
||||
self.channels.setdefault(hashable(channel), []).append(event)
|
||||
self.channels.setdefault(hashable(channel), set()).add(event)
|
||||
try:
|
||||
event.wait(timeout=timeout)
|
||||
with registry.cursor() as cr:
|
||||
@@ -140,6 +140,12 @@ class ImDispatch(object):
|
||||
except Exception:
|
||||
# timeout
|
||||
pass
|
||||
finally:
|
||||
# gc pointers to event
|
||||
for channel in channels:
|
||||
channel_events = self.channels.get(hashable(channel))
|
||||
if channel_events and event in channel_events:
|
||||
channel_events.remove(event)
|
||||
return notifications
|
||||
|
||||
def loop(self):
|
||||
@@ -160,7 +166,7 @@ class ImDispatch(object):
|
||||
# dispatch to local threads/greenlets
|
||||
events = set()
|
||||
for channel in channels:
|
||||
events.update(self.channels.pop(hashable(channel), []))
|
||||
events.update(self.channels.pop(hashable(channel), set()))
|
||||
for event in events:
|
||||
event.set()
|
||||
|
||||
|
||||
@@ -93,6 +93,9 @@ class Employee(models.Model):
|
||||
show_leaves = fields.Boolean('Able to see Remaining Time Off', compute='_compute_show_leaves')
|
||||
is_absent = fields.Boolean('Absent Today', compute='_compute_leave_status', search='_search_absent_employee')
|
||||
|
||||
def _get_date_start_work(self):
|
||||
return self.create_date
|
||||
|
||||
def _get_remaining_leaves(self):
|
||||
""" Helper to compute the remaining leaves for the current employees
|
||||
:returns dict where the key is the employee id, and the value is the remain leaves
|
||||
|
||||
@@ -167,16 +167,15 @@ class HolidaysAllocation(models.Model):
|
||||
|
||||
# We have to check when the employee has been created
|
||||
# in order to not allocate him/her too much leaves
|
||||
creation_date = fields.Datetime.from_string(holiday.employee_id.create_date)
|
||||
|
||||
start_date = holiday.employee_id._get_date_start_work()
|
||||
# If employee is created after the period, we cancel the computation
|
||||
if period_end <= creation_date:
|
||||
if period_end <= start_date:
|
||||
holiday.write(values)
|
||||
continue
|
||||
|
||||
# If employee created during the period, taking the date at which he has been created
|
||||
if period_start <= creation_date:
|
||||
period_start = creation_date
|
||||
if period_start <= start_date:
|
||||
period_start = start_date
|
||||
|
||||
worked = holiday.employee_id._get_work_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days']
|
||||
left = holiday.employee_id._get_leave_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days']
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
""" ESC/POS Commands (Constants) """
|
||||
|
||||
# Control characters
|
||||
ESC = '\x1b'
|
||||
|
||||
# Feed control sequences
|
||||
CTL_LF = '\x0a' # Print and line feed
|
||||
CTL_FF = '\x0c' # Form feed
|
||||
@@ -19,9 +22,10 @@ DLE_EOT_PAPER = '\x10\x04\x04'
|
||||
HW_INIT = '\x1b\x40' # Clear data in buffer and reset modes
|
||||
HW_SELECT = '\x1b\x3d\x01' # Printer select
|
||||
HW_RESET = '\x1b\x3f\x0a\x00' # Reset printer hardware
|
||||
# Cash Drawer
|
||||
CD_KICK_2 = '\x1b\x70\x00' # Sends a pulse to pin 2 []
|
||||
CD_KICK_5 = '\x1b\x70\x01' # Sends a pulse to pin 5 []
|
||||
# Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>)
|
||||
_CASH_DRAWER = lambda m, t1='', t2='': ESC + 'p' + m + chr(t1) + chr(t2)
|
||||
CD_KICK_2 = _CASH_DRAWER('\x00', 50, 50) # Sends a pulse to pin 2 []
|
||||
CD_KICK_5 = _CASH_DRAWER('\x01', 50, 50) # Sends a pulse to pin 5 []
|
||||
# Paper
|
||||
PAPER_FULL_CUT = '\x1d\x56\x00' # Full cut paper
|
||||
PAPER_PART_CUT = '\x1d\x56\x01' # Partial cut paper
|
||||
|
||||
@@ -30,7 +30,6 @@ class MailChannel(models.Model):
|
||||
|
||||
_name = 'mail.channel'
|
||||
_inherit = ['mail.channel', 'rating.mixin']
|
||||
_description = 'Discussion channel'
|
||||
|
||||
anonymous_name = fields.Char('Anonymous Name')
|
||||
channel_type = fields.Selection(selection_add=[('livechat', 'Livechat Conversation')])
|
||||
|
||||
@@ -1024,7 +1024,7 @@
|
||||
<field name="account_id" ref="pcg_445203"/>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="type_tax_use">purchase</field>
|
||||
<field name="tag_ids" eval="[(6,0,[ref('l10n_fr.tag_fr_73'),ref('l10n_fr.tag_fr_82')])]"/>
|
||||
<field name="tag_ids" eval="[(6,0,[ref('l10n_fr.tag_fr_73'),ref('l10n_fr.tag_fr_64')])]"/>
|
||||
<field name="tax_group_id" ref="tax_group_intra_21"/>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -324,6 +324,9 @@
|
||||
"8043","Omzet buiten EU werk derden","8043","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
|
||||
"8044","Omzet Intercompany werk derden","8044","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
|
||||
"8090","Diverse overige opbrengsten","8090","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
|
||||
"8110","Installatie/afstandsverkopen binnen EU handelsgoederen 1","80011","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
|
||||
"8120","Installatie/afstandsverkopen binnen EU handelsgoederen 2","80012","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
|
||||
"8130","Installatie/afstandsverkopen binnen EU handelsgoederen 3","80013","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
|
||||
"8920","Koersverschillen","8920","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_31","False"
|
||||
"9011","Resultaat deelneming 1","9011","account.data_account_type_other_income","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_12","False"
|
||||
"9012","Resultaat deelneming 2","9012","account.data_account_type_other_income","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_12","False"
|
||||
|
||||
|
@@ -92,5 +92,30 @@
|
||||
<field name="account_dest_id" ref="8022" />
|
||||
</record>
|
||||
|
||||
<!-- Installatie / afstandsverkopen -->
|
||||
<record
|
||||
id="fiscal_position_account_installatie_afstand_1"
|
||||
model="account.fiscal.position.account.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report" />
|
||||
<field name="account_src_id" ref="8001" />
|
||||
<field name="account_dest_id" ref="8110" />
|
||||
</record>
|
||||
|
||||
<record
|
||||
id="fiscal_position_account_installatie_afstand_2"
|
||||
model="account.fiscal.position.account.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report" />
|
||||
<field name="account_src_id" ref="8011" />
|
||||
<field name="account_dest_id" ref="8120" />
|
||||
</record>
|
||||
|
||||
<record
|
||||
id="fiscal_position_account_installatie_afstand_3"
|
||||
model="account.fiscal.position.account.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report" />
|
||||
<field name="account_src_id" ref="8021" />
|
||||
<field name="account_dest_id" ref="8130" />
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</openerp>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<record id="position_tax_extracom_d_0" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_non_eu"/>
|
||||
<field name="tax_src_id" ref="btw_0_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X3"/>
|
||||
<field name="tax_dest_id" ref="btw_X1"/>
|
||||
</record>
|
||||
<record id="position_tax_extracom_d_6" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_non_eu"/>
|
||||
@@ -49,12 +49,12 @@
|
||||
<record id="position_tax_extracom_d_21" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_non_eu"/>
|
||||
<field name="tax_src_id" ref="btw_21_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X3"/>
|
||||
<field name="tax_dest_id" ref="btw_X1"/>
|
||||
</record>
|
||||
<record id="position_tax_extracom_d_overig" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_non_eu"/>
|
||||
<field name="tax_src_id" ref="btw_overig_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X3"/>
|
||||
<field name="tax_dest_id" ref="btw_X1"/>
|
||||
</record>
|
||||
<!-- VAT on buying from outside the EU -->
|
||||
<record id="position_tax_extracom_6" model="account.fiscal.position.tax.template">
|
||||
@@ -65,7 +65,7 @@
|
||||
<record id="position_tax_extracom_9" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_non_eu"/>
|
||||
<field name="tax_src_id" ref="btw_9_buy"/>
|
||||
<field name="tax_dest_id" ref="btw_E1"/>
|
||||
<field name="tax_dest_id" ref="btw_E1_9"/>
|
||||
</record>
|
||||
<record id="position_tax_extracom_7" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_non_eu"/>
|
||||
@@ -85,7 +85,7 @@
|
||||
<record id="position_tax_extracom_d_9" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_non_eu"/>
|
||||
<field name="tax_src_id" ref="btw_9_buy_d"/>
|
||||
<field name="tax_dest_id" ref="btw_E1"/>
|
||||
<field name="tax_dest_id" ref="btw_E1_9"/>
|
||||
</record>
|
||||
<record id="position_tax_extracom_d_7" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_non_eu"/>
|
||||
@@ -95,59 +95,59 @@
|
||||
<record id="position_tax_extracom_d_8" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_non_eu"/>
|
||||
<field name="tax_src_id" ref="btw_overig_buy_d"/>
|
||||
<field name="tax_dest_id" ref="btw_E_overig"/>
|
||||
<field name="tax_dest_id" ref="btw_E_overig_d"/>
|
||||
</record>
|
||||
|
||||
<!-- EU Countries -->
|
||||
<record id="position_tax_intracom_1" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_0"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_diensten"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_producten"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_2" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_6"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_diensten"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_producten"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_2_9" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_9"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_diensten"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_producten"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_3" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_21"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_diensten"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_producten"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_4" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_overig"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_diensten"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_producten"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_d_1" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_0_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_diensten"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_d_2" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_6_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_diensten"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_d_2_9" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_9_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_diensten"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_d_3" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_21_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_diensten"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_d_4" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_overig_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
<field name="tax_dest_id" ref="btw_X0_diensten"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_6" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
@@ -172,22 +172,22 @@
|
||||
<record id="position_tax_intracom_d_6" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_6_buy_d"/>
|
||||
<field name="tax_dest_id" ref="btw_I_6"/>
|
||||
<field name="tax_dest_id" ref="btw_I_6_d"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_d_9" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_9_buy_d"/>
|
||||
<field name="tax_dest_id" ref="btw_I_9"/>
|
||||
<field name="tax_dest_id" ref="btw_I_9_d"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_d_7" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_21_buy_d"/>
|
||||
<field name="tax_dest_id" ref="btw_I_21"/>
|
||||
<field name="tax_dest_id" ref="btw_I_21_d"/>
|
||||
</record>
|
||||
<record id="position_tax_intracom_d_8" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu"/>
|
||||
<field name="tax_src_id" ref="btw_overig_buy_d"/>
|
||||
<field name="tax_dest_id" ref="btw_I_overig"/>
|
||||
<field name="tax_dest_id" ref="btw_I_overig_d"/>
|
||||
</record>
|
||||
|
||||
<!-- BTW verlegd -->
|
||||
@@ -196,6 +196,5 @@
|
||||
<field name="tax_src_id" ref="btw_21_buy"/>
|
||||
<field name="tax_dest_id" ref="btw_ink_0"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -28,83 +28,57 @@
|
||||
<record id="position_tax_eu_no_taxes_report_1" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_0"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_2" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_6"/>
|
||||
<field name="tax_src_id" ref="btw_verk_0"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_3" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_21"/>
|
||||
<field name="tax_src_id" ref="btw_6"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_4" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_overig"/>
|
||||
<field name="tax_src_id" ref="btw_9"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_5" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_0_d"/>
|
||||
<field name="tax_src_id" ref="btw_21"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_6" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_6_d"/>
|
||||
<field name="tax_src_id" ref="btw_overig"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_7" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_21_d"/>
|
||||
<field name="tax_src_id" ref="btw_0_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_8" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_overig_d"/>
|
||||
<field name="tax_src_id" ref="btw_6_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_9" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_6_buy"/>
|
||||
<field name="tax_src_id" ref="btw_9_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_10" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_6_buy_incl"/>
|
||||
<field name="tax_src_id" ref="btw_21_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_11" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_21_buy"/>
|
||||
<field name="tax_src_id" ref="btw_overig_d"/>
|
||||
<field name="tax_dest_id" ref="btw_X2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_12" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_21_buy_incl"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_13" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_overig_buy"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_14" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_6_buy_d"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_15" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_21_buy_d"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_16" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_overig_buy_d"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_17" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_verk_0"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_18" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_ink_0_1"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_19" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_ink_0_2"/>
|
||||
</record>
|
||||
<record id="position_tax_eu_no_taxes_report_20" model="account.fiscal.position.tax.template">
|
||||
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
|
||||
<field name="tax_src_id" ref="btw_ink_0"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -200,7 +200,7 @@ var DocumentViewer = Widget.extend({
|
||||
*/
|
||||
_onDownload: function (e) {
|
||||
e.preventDefault();
|
||||
window.location = '/web/content/' + this.activeAttachment.id + '?download=true';
|
||||
window.location = '/web/content/' + this.modelName + '/' + this.activeAttachment.id + '/' + 'datas' + '?download=true';
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
|
||||
@@ -318,9 +318,9 @@ class MassMailingCampaign(models.Model):
|
||||
campaign_id = fields.Many2one('utm.campaign', 'campaign_id',
|
||||
required=True, ondelete='cascade', help="This name helps you tracking your different campaign efforts, e.g. Fall_Drive, Christmas_Special")
|
||||
source_id = fields.Many2one('utm.source', string='Source',
|
||||
help="This is the link source, e.g. Search Engine, another domain,or name of email list", default=lambda self: self.env.ref('utm.utm_source_newsletter'))
|
||||
help="This is the link source, e.g. Search Engine, another domain,or name of email list", default=lambda self: self.env.ref('utm.utm_source_newsletter', False))
|
||||
medium_id = fields.Many2one('utm.medium', string='Medium',
|
||||
help="This is the delivery method, e.g. Postcard, Email, or Banner Ad", default=lambda self: self.env.ref('utm.utm_medium_email'))
|
||||
help="This is the delivery method, e.g. Postcard, Email, or Banner Ad", default=lambda self: self.env.ref('utm.utm_medium_email', False))
|
||||
tag_ids = fields.Many2many(
|
||||
'mail.mass_mailing.tag', 'mail_mass_mailing_tag_rel',
|
||||
'tag_id', 'campaign_id', string='Tags')
|
||||
|
||||
@@ -205,10 +205,10 @@ class ReportBomStructure(models.AbstractModel):
|
||||
|
||||
def _get_pdf_line(self, bom_id, product_id=False, qty=1, child_bom_ids=[], unfolded=False):
|
||||
|
||||
data = self._get_bom(bom_id=bom_id, product_id=product_id, line_qty=qty)
|
||||
data = self._get_bom(bom_id=bom_id, product_id=product_id.id, line_qty=qty)
|
||||
|
||||
def get_sub_lines(bom, product_id, line_qty, line_id, level):
|
||||
data = self._get_bom(bom_id=bom.id, product_id=product_id, line_qty=line_qty, line_id=line_id, level=level)
|
||||
data = self._get_bom(bom_id=bom.id, product_id=product_id.id, line_qty=line_qty, line_id=line_id, level=level)
|
||||
bom_lines = data['components']
|
||||
lines = []
|
||||
for bom_line in bom_lines:
|
||||
|
||||
@@ -71,6 +71,11 @@ class AccountPayment(models.Model):
|
||||
|
||||
return transactions
|
||||
|
||||
def action_validate_invoice_payment(self):
|
||||
res = super(AccountPayment, self).action_validate_invoice_payment()
|
||||
self.mapped('payment_transaction_id').filtered(lambda x: x.state == 'done' and not x.is_processed)._post_process_after_done()
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def post(self):
|
||||
# Post the payments "normally" if no transactions are needed.
|
||||
|
||||
@@ -32,12 +32,12 @@ class BuckarooForm(BuckarooCommon):
|
||||
# ----------------------------------------
|
||||
|
||||
form_values = {
|
||||
'add_returndata': '',
|
||||
'add_returndata': '/payment/process',
|
||||
'Brq_websitekey': self.buckaroo.brq_websitekey,
|
||||
'Brq_amount': '2240.0',
|
||||
'Brq_currency': 'EUR',
|
||||
'Brq_invoicenumber': 'SO004',
|
||||
'Brq_signature': 'aa6fe072afdd9d1b463d55f43e6df4b272d3529a', # update me
|
||||
'Brq_signature': 'f09bb79451a7ddef3178462ea52b07e55d34e99e', # update me
|
||||
'brq_test': 'True',
|
||||
'Brq_return': urls.url_join(base_url, BuckarooController._return_url),
|
||||
'Brq_returncancel': urls.url_join(base_url, BuckarooController._cancel_url),
|
||||
|
||||
@@ -67,6 +67,7 @@ class PaypalForm(PaypalCommon):
|
||||
'return': urls.url_join(base_url, PaypalController._return_url),
|
||||
'notify_url': urls.url_join(base_url, PaypalController._notify_url),
|
||||
'cancel_return': urls.url_join(base_url, PaypalController._cancel_url),
|
||||
'custom': '{"return_url": "/payment/process"}',
|
||||
}
|
||||
|
||||
# check form result
|
||||
@@ -140,7 +141,7 @@ class PaypalForm(PaypalCommon):
|
||||
'item_name': u'test_ref_2',
|
||||
'address_country': u'France',
|
||||
'charset': u'windows-1252',
|
||||
'custom': u'',
|
||||
'custom': u'{"return_url": "/payment/process"}',
|
||||
'notify_version': u'3.7',
|
||||
'address_name': u'Norbert Poilu',
|
||||
'pending_reason': u'multi_currency',
|
||||
|
||||
@@ -331,6 +331,12 @@ class ProductTemplate(models.Model):
|
||||
raise ValidationError(_('The default Unit of Measure and the purchase Unit of Measure must be in the same category.'))
|
||||
return True
|
||||
|
||||
@api.constrains('attribute_line_ids')
|
||||
def _check_attribute_line(self):
|
||||
if any(len(template.attribute_line_ids) != len(template.attribute_line_ids.mapped('attribute_id')) for template in self):
|
||||
raise ValidationError(_('You cannot define two attribute lines for the same attribute.'))
|
||||
return True
|
||||
|
||||
@api.onchange('uom_id')
|
||||
def _onchange_uom_id(self):
|
||||
if self.uom_id:
|
||||
|
||||
@@ -2,16 +2,6 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- sale_timesheet and project define the same field without depending on each, which causes the field to be deleted when the module that created it
|
||||
is deleted. To avoid this, we create xmlids manually for this field in both modules to prevent accidental deletion. To fix in saas-7 by moving the field definition-->
|
||||
<record id="duplicate_field_xmlid" model="ir.model.data">
|
||||
<field name="res_id" search="[('model','=','res.company'),('name','=','project_time_mode_id')]" model="ir.model.fields"/>
|
||||
<field name="model">ir.model.fields</field>
|
||||
<field name="module">project</field>
|
||||
<field name="name">project_time_mode_id_duplicate_xmlid</field>
|
||||
<field name="noupdate">True</field>
|
||||
</record>
|
||||
|
||||
<record id="ir_cron_rating_project" model="ir.cron">
|
||||
<field name="name">Project: Send rating</field>
|
||||
<field name="model_id" ref="project.model_project_project"/>
|
||||
|
||||
@@ -131,7 +131,7 @@ class AccountInvoice(models.Model):
|
||||
res = super(AccountInvoice, self)._onchange_partner_id()
|
||||
if payment_term_id:
|
||||
self.payment_term_id = payment_term_id
|
||||
if not self.env.context.get('default_journal_id') and self.partner_id and self.currency_id and\
|
||||
if not self.env.context.get('default_journal_id') and self.partner_id and\
|
||||
self.type in ['in_invoice', 'in_refund'] and\
|
||||
self.currency_id != self.partner_id.property_purchase_currency_id and\
|
||||
self.partner_id.property_purchase_currency_id.id:
|
||||
@@ -172,6 +172,11 @@ class AccountInvoice(models.Model):
|
||||
invoice.message_post(body=message)
|
||||
return result
|
||||
|
||||
def _get_onchange_create(self):
|
||||
res = super()._get_onchange_create()
|
||||
res['_onchange_partner_id'].append('currency_id')
|
||||
return res
|
||||
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
""" Override AccountInvoice_line to add the link to the purchase order line it is related to"""
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="report_purchaseorder_document" inherit_id="purchase.report_purchaseorder_document">
|
||||
<xpath expr="//div[@name='purchase_shipping_address']" position="after">
|
||||
<div t-if="not o.dest_address_id and o.picking_type_id and o.picking_type_id.warehouse_id">
|
||||
<span t-field="o.picking_type_id.warehouse_id.name"/>
|
||||
<div t-field="o.picking_type_id.warehouse_id.partner_id" t-options='{"widget": "contact", "fields": ["address", "phone"], "no_marker": True, "phone_icons": True}'/>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="report_purchaseorder_document" inherit_id="purchase.report_purchaseorder_document">
|
||||
<xpath expr="//t[@t-if='o.dest_address_id']" position="after">
|
||||
<t t-else="">
|
||||
<t t-set="information_block">
|
||||
<strong>Shipping address:</strong>
|
||||
<div t-if="o.picking_type_id and o.picking_type_id.warehouse_id">
|
||||
<span t-field="o.picking_type_id.warehouse_id.name"/>
|
||||
<div t-field="o.picking_type_id.warehouse_id.partner_id" t-options='{"widget": "contact", "fields": ["address", "phone"], "no_marker": True, "phone_icons": True}'/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="report_purchasequotation_document" inherit_id="purchase.report_purchasequotation_document">
|
||||
<xpath expr="//div[@name='purchase_shipping_address']" position="after">
|
||||
<div t-if="not o.dest_address_id and o.picking_type_id.warehouse_id">
|
||||
<span t-field="o.picking_type_id.warehouse_id.name"/>
|
||||
<div t-field="o.picking_type_id.warehouse_id.partner_id" t-options='{"widget": "contact", "fields": ["address", "phone"], "no_marker": True, "phone_icons": True}'/>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="report_purchasequotation_document" inherit_id="purchase.report_purchasequotation_document">
|
||||
<xpath expr="//t[@t-if='o.dest_address_id']" position="after">
|
||||
<t t-else="">
|
||||
<t t-set="information_block">
|
||||
<strong>Shipping address:</strong>
|
||||
<div t-if="o.picking_type_id and o.picking_type_id.warehouse_id">
|
||||
<span t-field="o.picking_type_id.warehouse_id.name"/>
|
||||
<div t-field="o.picking_type_id.warehouse_id.partner_id" t-options='{"widget": "contact", "fields": ["address", "phone"], "no_marker": True, "phone_icons": True}'/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -599,17 +599,24 @@ class RepairLine(models.Model):
|
||||
'title': _('No pricelist found.'),
|
||||
'message':
|
||||
_('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.')}
|
||||
else:
|
||||
price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner)
|
||||
if price is False:
|
||||
warning = {
|
||||
'title': _('No valid pricelist line found.'),
|
||||
'message':
|
||||
_("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")}
|
||||
else:
|
||||
self.price_unit = price
|
||||
if warning:
|
||||
return {'warning': warning}
|
||||
else:
|
||||
self._onchange_product_uom()
|
||||
|
||||
@api.onchange('product_uom')
|
||||
def _onchange_product_uom(self):
|
||||
partner = self.repair_id.partner_id
|
||||
pricelist = self.repair_id.pricelist_id
|
||||
if pricelist and self.product_id and self.type != 'remove':
|
||||
price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner, uom_id=self.product_uom.id)
|
||||
if price is False:
|
||||
warning = {
|
||||
'title': _('No valid pricelist line found.'),
|
||||
'message':
|
||||
_("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")}
|
||||
return {'warning': warning}
|
||||
else:
|
||||
self.price_unit = price
|
||||
|
||||
|
||||
class RepairFee(models.Model):
|
||||
@@ -659,14 +666,21 @@ class RepairFee(models.Model):
|
||||
'title': _('No pricelist found.'),
|
||||
'message':
|
||||
_('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.')}
|
||||
return {'warning': warning}
|
||||
else:
|
||||
price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner)
|
||||
self._onchange_product_uom()
|
||||
|
||||
@api.onchange('product_uom')
|
||||
def _onchange_product_uom(self):
|
||||
partner = self.repair_id.partner_id
|
||||
pricelist = self.repair_id.pricelist_id
|
||||
if pricelist and self.product_id:
|
||||
price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner, uom_id=self.product_uom.id)
|
||||
if price is False:
|
||||
warning = {
|
||||
'title': _('No valid pricelist line found.'),
|
||||
'message':
|
||||
_("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")}
|
||||
return {'warning': warning}
|
||||
else:
|
||||
self.price_unit = price
|
||||
if warning:
|
||||
return {'warning': warning}
|
||||
|
||||
@@ -70,29 +70,26 @@
|
||||
<table class="table table-sm o_main_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<t t-set="colspan" t-value="5"/>
|
||||
<th class="text-left">Description</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th class="text-right">Unit Price</th>
|
||||
<th t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">
|
||||
<span>Disc.(%)</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th class="text-right">Taxes</th>
|
||||
<th class="text-right">
|
||||
<t groups="account.group_show_line_subtotals_tax_excluded">Amount</t>
|
||||
<t groups="account.group_show_line_subtotals_tax_included">Total Price</t>
|
||||
</th>
|
||||
<th class="text-right" groups="account.group_show_line_subtotals_tax_excluded">Amount</th>
|
||||
<th class="text-right" groups="account.group_show_line_subtotals_tax_included">Total Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="sale_tbody">
|
||||
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_excluded" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_included" t-value="0"/>
|
||||
|
||||
<t t-foreach="doc.order_line" t-as="line">
|
||||
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
<t t-set="current_subtotal_tax_excluded" t-value="current_subtotal_tax_excluded + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<t t-set="current_subtotal_tax_included" t-value="current_subtotal_tax_included + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
|
||||
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''">
|
||||
<t t-if="not line.display_type">
|
||||
@@ -110,20 +107,23 @@
|
||||
<td class="text-right">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_id))"/>
|
||||
</td>
|
||||
<td class="text-right o_price_total">
|
||||
<span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
<td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_excluded">
|
||||
<span t-field="line.price_subtotal"/>
|
||||
</td>
|
||||
<td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_included">
|
||||
<span t-field="line.price_total"/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_section'">
|
||||
<td t-att-colspan="99">
|
||||
<td colspan="99">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
<t t-set="current_section" t-value="line"/>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_excluded" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_included" t-value="0"/>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_note'">
|
||||
<td t-att-colspan="99">
|
||||
<td colspan="99">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
</t>
|
||||
@@ -131,12 +131,17 @@
|
||||
|
||||
<t t-if="current_section and (line_last or doc.order_line[line_index+1].display_type == 'line_section')">
|
||||
<tr class="is-subtotal text-right">
|
||||
<td t-att-colspan="99">
|
||||
<td colspan="99" groups="account.group_show_line_subtotals_tax_excluded,account.group_show_line_subtotals_tax_included">
|
||||
<strong class="mr16">Subtotal</strong>
|
||||
<span
|
||||
t-esc="current_subtotal"
|
||||
groups="account.group_show_line_subtotals_tax_excluded"
|
||||
t-esc="current_subtotal_tax_excluded"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
|
||||
/>
|
||||
<span groups="account.group_show_line_subtotals_tax_included"> | <span
|
||||
t-esc="current_subtotal_tax_included"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
|
||||
/></span>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
@@ -424,29 +424,26 @@
|
||||
<table class="table table-sm" id="sales_order_table">
|
||||
<thead class="bg-100">
|
||||
<tr>
|
||||
<t t-set="colspan" t-value="6"/>
|
||||
<th class="text-left">Products</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">Unit Price</th>
|
||||
<th t-if="display_discount" t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">
|
||||
<span>Discount</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">Taxes</th>
|
||||
<th class="text-right" >
|
||||
<span groups="account.group_show_line_subtotals_tax_excluded">Amount</span>
|
||||
<span groups="account.group_show_line_subtotals_tax_included">Total Price</span>
|
||||
</th>
|
||||
<th class="text-right" groups="account.group_show_line_subtotals_tax_excluded">Amount</th>
|
||||
<th class="text-right" groups="account.group_show_line_subtotals_tax_included">Total Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="sale_tbody">
|
||||
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_excluded" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_included" t-value="0"/>
|
||||
|
||||
<t t-foreach="sale_order.order_line" t-as="line">
|
||||
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
<t t-set="current_subtotal_tax_excluded" t-value="current_subtotal_tax_excluded + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<t t-set="current_subtotal_tax_included" t-value="current_subtotal_tax_included + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
|
||||
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''">
|
||||
<t t-if="not line.display_type">
|
||||
@@ -477,20 +474,23 @@
|
||||
<td t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_id))"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
<td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_excluded">
|
||||
<span t-field="line.price_subtotal"/>
|
||||
</td>
|
||||
<td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_included">
|
||||
<span t-field="line.price_total"/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_section'">
|
||||
<td t-att-colspan="colspan">
|
||||
<td colspan="99">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
<t t-set="current_section" t-value="line"/>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_excluded" t-value="0"/>
|
||||
<t t-set="current_subtotal_tax_included" t-value="0"/>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_note'">
|
||||
<td t-att-colspan="colspan">
|
||||
<td colspan="99">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
</t>
|
||||
@@ -498,12 +498,17 @@
|
||||
|
||||
<t t-if="current_section and (line_last or sale_order.order_line[line_index+1].display_type == 'line_section')">
|
||||
<tr class="is-subtotal text-right">
|
||||
<td t-att-colspan="colspan">
|
||||
<td colspan="99">
|
||||
<strong class="mr16">Subtotal</strong>
|
||||
<span
|
||||
t-esc="current_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
|
||||
groups="account.group_show_line_subtotals_tax_excluded"
|
||||
t-esc="current_subtotal_tax_excluded"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
|
||||
/>
|
||||
<span groups="account.group_show_line_subtotals_tax_included"> | <span
|
||||
t-esc="current_subtotal_tax_included"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
|
||||
/></span>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
@@ -34,3 +34,7 @@ class StockWarnInsufficientQtyScrap(models.TransientModel):
|
||||
|
||||
def action_done(self):
|
||||
return self.scrap_id.do_scrap()
|
||||
|
||||
def action_cancel(self):
|
||||
# FIXME in master: we should not have created the scrap in a first place
|
||||
return self.scrap_id.sudo().unlink()
|
||||
|
||||
@@ -44,6 +44,9 @@
|
||||
<xpath expr="//field[@name='product_id']" position="after">
|
||||
<field name="scrap_id" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='cancel_button']" position="replace">
|
||||
<button string="Discard" name="action_cancel" type="object" class="btn-primary"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -91,20 +91,20 @@ var AbstractAction = Widget.extend(ActionMixin, {
|
||||
*/
|
||||
willStart: function () {
|
||||
var self = this;
|
||||
var defs = [this._super.apply(this, arguments)];
|
||||
if (this.hasControlPanel) {
|
||||
var params = this.controlPanelParams;
|
||||
var def;
|
||||
if (this.loadControlPanel) {
|
||||
def = this
|
||||
defs.push(this
|
||||
.loadFieldView(params.modelName, params.context || {}, params.viewId, 'search')
|
||||
.then(function (fieldsView) {
|
||||
params.viewInfo = {
|
||||
arch: fieldsView.arch,
|
||||
fields: fieldsView.fields,
|
||||
};
|
||||
});
|
||||
}));
|
||||
}
|
||||
return Promise.resolve(def).then(function () {
|
||||
return Promise.all(defs).then(function () {
|
||||
var controlPanelView = new self.config.ControlPanelView(params);
|
||||
return controlPanelView.getController(self).then(function (controlPanel) {
|
||||
self._controlPanel = controlPanel;
|
||||
@@ -112,7 +112,7 @@ var AbstractAction = Widget.extend(ActionMixin, {
|
||||
});
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
return Promise.all(defs);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
|
||||
@@ -231,13 +231,13 @@ var Dialog = Widget.extend({
|
||||
this.$modal.remove();
|
||||
}
|
||||
|
||||
if (!isFocusSet) {
|
||||
var modals = $('body > .modal').filter(':visible');
|
||||
if (modals.length) {
|
||||
var modals = $('body > .modal').filter(':visible');
|
||||
if (modals.length) {
|
||||
if (!isFocusSet) {
|
||||
modals.last().focus();
|
||||
// Keep class modal-open (deleted by bootstrap hide fnct) on body to allow scrolling inside the modal
|
||||
$('body').addClass('modal-open');
|
||||
}
|
||||
// Keep class modal-open (deleted by bootstrap hide fnct) on body to allow scrolling inside the modal
|
||||
$('body').addClass('modal-open');
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -75,9 +75,11 @@ return AbstractModel.extend({
|
||||
.utc();
|
||||
} else {
|
||||
// default hours in the user's timezone
|
||||
start.hours(7).add(-this.getSession().getTZOffset(start), 'minutes');
|
||||
end.hours(19).add(-this.getSession().getTZOffset(end), 'minutes');
|
||||
start.hours(7);
|
||||
end.hours(19);
|
||||
}
|
||||
start.add(-this.getSession().getTZOffset(start), 'minutes');
|
||||
end.add(-this.getSession().getTZOffset(end), 'minutes');
|
||||
}
|
||||
} else {
|
||||
start.add(-this.getSession().getTZOffset(start), 'minutes');
|
||||
|
||||
@@ -685,6 +685,11 @@
|
||||
|
||||
.o_field_widget {
|
||||
margin-bottom: $o-form-spacing-unit * 2;
|
||||
|
||||
> .o_field_widget {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.o_field_boolean {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@@ -285,6 +285,68 @@ QUnit.module('relational_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('focus when closing many2one modal in many2one modal', async function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<field name="trululu"/>' +
|
||||
'</form>',
|
||||
res_id: 2,
|
||||
archs: {
|
||||
'partner,false,form': '<form><field name="trululu"/></form>'
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'get_formview_id') {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
|
||||
// Open many2one modal
|
||||
await testUtils.form.clickEdit(form);
|
||||
await testUtils.dom.click(form.$('.o_external_button'));
|
||||
|
||||
var $originalModal = $('.modal-dialog');
|
||||
var $focusedModal = $(document.activeElement).closest('.modal-dialog');
|
||||
|
||||
assert.equal($originalModal.length, 1, 'There should be one modal');
|
||||
assert.equal($originalModal[0], $focusedModal[0], 'Modal is focused');
|
||||
assert.ok($('body').hasClass('modal-open'), 'Modal is said opened');
|
||||
|
||||
// Open many2one modal of field in many2one modal
|
||||
await testUtils.dom.click($originalModal.find('.o_external_button'));
|
||||
var $modals = $('.modal-dialog');
|
||||
$focusedModal = $(document.activeElement).closest('.modal-dialog');
|
||||
|
||||
assert.equal($modals.length, 2, 'There should be two modals');
|
||||
assert.equal($modals[1], $focusedModal[0], 'Last modal is focused');
|
||||
assert.ok($('body').hasClass('modal-open'), 'Modal is said opened');
|
||||
|
||||
// Close second modal
|
||||
await testUtils.dom.click($modals.last().find('button[class="close"]'));
|
||||
var $modal = $('.modal-dialog');
|
||||
$focusedModal = $(document.activeElement).closest('.modal-dialog');
|
||||
|
||||
assert.equal($modal.length, 1, 'There should be one modal');
|
||||
assert.equal($modal[0], $originalModal[0], 'First modal is still opened');
|
||||
assert.equal($modal[0], $focusedModal[0], 'Modal is focused');
|
||||
assert.ok($('body').hasClass('modal-open'), 'Modal is said opened');
|
||||
|
||||
// Close first modal
|
||||
await testUtils.dom.click($modal.find('button[class="close"]'));
|
||||
$modal = $('.modal-dialog.modal-lg');
|
||||
|
||||
assert.equal($modal.length, 0, 'There should be no modal');
|
||||
assert.notOk($('body').hasClass('modal-open'), 'Modal is not said opened');
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
|
||||
QUnit.test('one2many from a model that has been sorted', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
@@ -2267,6 +2267,61 @@ QUnit.module('Views', {
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('timezone does not affect drag and drop', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
var calendar = await createCalendarView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar date_start="start" mode="month">'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="start"/>'+
|
||||
'</calendar>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === "write") {
|
||||
assert.deepEqual(args.args[0], [6], "event 6 is moved")
|
||||
assert.deepEqual(args.args[1].start, "2016-11-29 08:00:00",
|
||||
"event moved to 27th nov 16h00 +40 hours timezone")
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
session: {
|
||||
getTZOffset: function () {
|
||||
return -2400; // 40 hours timezone
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
var events = calendar.$('.fc-event').map(function () {
|
||||
return $(this).text().trim().replace(/\s+/g, '|');
|
||||
});
|
||||
|
||||
assert.strictEqual(events[0], "event|1|12/09/2016|08:00:00");
|
||||
assert.strictEqual(events[5], "event|6|12/16/2016|16:00:00");
|
||||
|
||||
// Move event 6 as on first day of month view (27th november 2016)
|
||||
await testUtils.dragAndDrop(
|
||||
calendar.$('.fc-event').eq(5),
|
||||
calendar.$('.fc-day-top').first()
|
||||
);
|
||||
await testUtils.nextTick();
|
||||
|
||||
events = calendar.$('.fc-event').map(function () {
|
||||
return $(this).text().trim().replace(/\s+/g, '|');
|
||||
});
|
||||
|
||||
assert.strictEqual(events[0], "event|6|11/27/2016|16:00:00");
|
||||
assert.strictEqual(events[1], "event|1|12/09/2016|08:00:00");
|
||||
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('form_view_id attribute works (for creating events)', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
|
||||
@@ -224,7 +224,6 @@
|
||||
.ActiveUsers {
|
||||
position: relative;
|
||||
float: right;
|
||||
top: -60px;
|
||||
right: 10px;
|
||||
border: 1px solid gray('200');
|
||||
font-weight: 300;
|
||||
|
||||
@@ -447,10 +447,10 @@
|
||||
<div class="card-body">
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<t t-foreach="nav_list" t-as="year">
|
||||
<li data-toggle="collapse" t-att-data-target="'#' + year" aria-expanded="false" class="nav-item blog_post_year_collapse mt8">
|
||||
<li data-toggle="collapse" t-att-data-target="'#b_' + year" aria-expanded="false" class="nav-item blog_post_year_collapse mt8">
|
||||
<i t-attf-class="fa #{'fa-chevron-down' if not date and year_index == 0 else 'fa-chevron-right'} mr4" role="img" aria-label="Next year" title="Next year"/><span t-esc="year"/>
|
||||
</li>
|
||||
<ul t-attf-class="blog_post_year collapse nav nav-pills flex-column #{not date and year_index == 0 and 'in'}" t-att-id="year" role="menu">
|
||||
<ul t-attf-class="blog_post_year collapse nav nav-pills flex-column #{not date and year_index == 0 and 'in'}" t-att-id="'b_' + year" role="menu">
|
||||
<t t-foreach="nav_list[year]" t-as="months">
|
||||
<li t-if="months['date_begin'] == date" class="nav-item">
|
||||
<a role="menuitem" t-ignore="True" t-attf-href="#{blog_url(date_begin=False, date_end=False)}" class="nav-link active"><t t-esc="months['month']"/><span class="float-right badge badge-pill" t-esc="months['post_date_count']"/></a>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</record>
|
||||
|
||||
<record model="product.pricelist" id="product.list0">
|
||||
<field name="selectable" eval="False" />
|
||||
<field name="selectable" eval="True" />
|
||||
<field name="website_id" eval="False"/>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
@@ -91,7 +91,7 @@ class ProductPricelist(models.Model):
|
||||
Change in this method should be reflected in `_get_website_pricelists_domain`.
|
||||
"""
|
||||
self.ensure_one()
|
||||
return self.website_id.id == website_id or (not self.website_id and (self.selectable or self.code))
|
||||
return self.website_id.id == website_id or (not self.website_id and (self.selectable or self.sudo().code))
|
||||
|
||||
def _get_website_pricelists_domain(self, website_id):
|
||||
''' Check above `_is_available_on_website` for explanation.
|
||||
|
||||
@@ -287,7 +287,8 @@ class SaleOrder(models.Model):
|
||||
|
||||
@api.multi
|
||||
def action_recovery_email_send(self):
|
||||
self._portal_ensure_token()
|
||||
for order in self:
|
||||
order._portal_ensure_token()
|
||||
composer_form_view_id = self.env.ref('mail.email_compose_message_wizard_form').id
|
||||
try:
|
||||
default_template = self.env.ref('website_sale.mail_template_sale_cart_recovery', raise_if_not_found=False)
|
||||
|
||||
@@ -720,12 +720,16 @@ class IrActionsReport(models.Model):
|
||||
data = self._get_rendering_context(docids, data)
|
||||
return self.render_template(self.report_name, data), 'html'
|
||||
|
||||
@api.model
|
||||
def _get_rendering_context_model(self):
|
||||
report_model_name = 'report.%s' % self.report_name
|
||||
return self.env.get(report_model_name)
|
||||
|
||||
@api.model
|
||||
def _get_rendering_context(self, docids, data):
|
||||
# If the report is using a custom model to render its html, we must use it.
|
||||
# Otherwise, fallback on the generic html rendering.
|
||||
report_model_name = 'report.%s' % self.report_name
|
||||
report_model = self.env.get(report_model_name)
|
||||
report_model = self._get_rendering_context_model()
|
||||
|
||||
data = data and dict(data) or {}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import base64
|
||||
import io
|
||||
|
||||
import odoo
|
||||
from odoo.tests import common
|
||||
from odoo.tests import common, tagged
|
||||
from odoo.tools.misc import file_open, mute_logger
|
||||
from odoo.tools.translate import _
|
||||
|
||||
@@ -23,6 +23,18 @@ class TestTermCount(common.TransactionCase):
|
||||
])
|
||||
self.assertEqual(len(ids), 2)
|
||||
|
||||
def test_count_term_module(self):
|
||||
"""
|
||||
Just make sure we have as many translation entries as we wanted and module deducted from file content
|
||||
"""
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
|
||||
translations = self.env['ir.translation'].search([
|
||||
('lang', '=', 'fr_FR'),
|
||||
('src', '=', 'Ijkl'),
|
||||
('module', '=', 'test_translation_import'),
|
||||
])
|
||||
self.assertEqual(len(translations), 1)
|
||||
|
||||
def test_noupdate(self):
|
||||
"""
|
||||
Make sure no update do not overwrite translations
|
||||
@@ -167,3 +179,49 @@ class TestTermCount(common.TransactionCase):
|
||||
|
||||
self.env.context = dict(self.env.context, lang="dot")
|
||||
self.assertEqual(_("Accounting"), "samva", "The code translation was not applied")
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestTranslationFlow(common.TransactionCase):
|
||||
|
||||
def test_export_import(self):
|
||||
""" Ensure export+import gives the same result as loading a language """
|
||||
# load language and generate missing terms to create missing empty terms
|
||||
with mute_logger('odoo.addons.base.models.ir_translation'):
|
||||
self.env["base.language.install"].create({'lang': 'fr_FR'}).lang_install()
|
||||
self.env["base.update.translations"].create({'lang': 'fr_FR'}).act_update()
|
||||
|
||||
translations = self.env["ir.translation"].search([
|
||||
('lang', '=', 'fr_FR'),
|
||||
('module', '=', 'test_translation_import')
|
||||
])
|
||||
|
||||
# minus 3 as the original fr.po contains 3 fake code translations (cf
|
||||
# test_no_duplicate test) which are not found by babel_extract_terms
|
||||
init_translation_count = len(translations) - 3
|
||||
|
||||
module = self.env.ref('base.module_test_translation_import')
|
||||
export = self.env["base.language.export"].create({
|
||||
'lang': 'fr_FR',
|
||||
'format': 'po',
|
||||
'modules': [(6, 0, [module.id])]
|
||||
})
|
||||
export.act_getfile()
|
||||
po_file = export.data
|
||||
self.assertIsNotNone(po_file)
|
||||
|
||||
translations.unlink()
|
||||
|
||||
import_fr = self.env["base.language.import"].create({
|
||||
'name': 'French',
|
||||
'code': 'fr_FR',
|
||||
'data': export.data,
|
||||
'filename': export.name,
|
||||
})
|
||||
with mute_logger('odoo.addons.base.models.res_lang'):
|
||||
import_fr.with_context(create_empty_translation=True).import_lang()
|
||||
|
||||
import_translation = self.env["ir.translation"].search([
|
||||
('lang', '=', 'fr_FR'),
|
||||
('module', '=', 'test_translation_import')
|
||||
])
|
||||
self.assertEqual(init_translation_count, len(import_translation))
|
||||
|
||||
@@ -509,7 +509,7 @@ class PoFile(object):
|
||||
return self.lines_count - len(self.lines)
|
||||
|
||||
def next(self):
|
||||
trans_type = name = res_id = source = trad = None
|
||||
trans_type = name = res_id = source = trad = module = None
|
||||
if self.extra_lines:
|
||||
trans_type, name, res_id, source, trad, comments = self.extra_lines.pop(0)
|
||||
if not res_id:
|
||||
@@ -530,6 +530,8 @@ class PoFile(object):
|
||||
line = line[2:].strip()
|
||||
if not line.startswith('module:'):
|
||||
comments.append(line)
|
||||
else:
|
||||
module = line[7:].strip()
|
||||
elif line.startswith('#:'):
|
||||
# Process the `reference` comments. Each line can specify
|
||||
# multiple targets (e.g. model, view, code, selection,
|
||||
@@ -603,7 +605,7 @@ class PoFile(object):
|
||||
_logger.warning('Missing "#:" formatted comment at line %d for the following source:\n\t%s',
|
||||
self.cur_line(), source[:30])
|
||||
return next(self)
|
||||
return trans_type, name, res_id, source, trad, '\n'.join(comments)
|
||||
return trans_type, name, res_id, source, trad, '\n'.join(comments), module
|
||||
__next__ = next
|
||||
|
||||
def write_infos(self, modules):
|
||||
@@ -1045,7 +1047,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
|
||||
|
||||
elif fileformat == 'po':
|
||||
reader = PoFile(fileobj)
|
||||
fields = ['type', 'name', 'res_id', 'src', 'value', 'comments']
|
||||
fields = ['type', 'name', 'res_id', 'src', 'value', 'comments', 'module']
|
||||
|
||||
# Make a reader for the POT file and be somewhat defensive for the
|
||||
# stable branch.
|
||||
@@ -1079,7 +1081,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
|
||||
self.comments = None
|
||||
|
||||
pot_targets = defaultdict(Target)
|
||||
for type, name, res_id, src, _ignored, comments in pot_reader:
|
||||
for type, name, res_id, src, _ignored, comments, module in pot_reader:
|
||||
if type is not None:
|
||||
target = pot_targets[src]
|
||||
target.targets.add((type, name, type != 'code' and res_id or 0))
|
||||
|
||||
Reference in New Issue
Block a user