[MERGE] forward port of branch 9.0 up to 4bbddc7

This commit is contained in:
Christophe Simonis
2016-01-27 12:17:12 +01:00
101 changed files with 1357 additions and 236 deletions
+3
View File
@@ -1142,6 +1142,9 @@ class AccountMoveLine(models.Model):
if 'company_ids' in context:
domain += [('company_id', 'in', context['company_ids'])]
if context.get('reconcile_date'):
domain += ['|', ('reconciled', '=', False), '|', ('matched_debit_ids.create_date', '>', context['reconcile_date']), ('matched_credit_ids.create_date', '>', context['reconcile_date'])]
where_clause = ""
where_clause_params = []
tables = ''
@@ -61,8 +61,8 @@ class ReportAgedPartnerBalance(models.AbstractModel):
AND (COALESCE(l.date_maturity,l.date) > %s)\
AND (l.partner_id IN %s)\
AND l.reconciled IS FALSE\
AND (l.date <= %s)\
GROUP BY l.partner_id', (tuple(move_state), tuple(account_type), date_from, tuple(partner_ids), date_from,))
AND l.date <= %s \
GROUP BY l.partner_id', (tuple(move_state), tuple(account_type), date_from, tuple(partner_ids), date_from))
partner_totals = cr.fetchall()
for partner_id, amount in partner_totals:
future_past[partner_id] = amount
@@ -85,50 +85,29 @@ class ReportAgedPartnerBalance(models.AbstractModel):
args_list += (form[str(i)]['stop'],)
args_list += (date_from,)
cr.execute('''SELECT l.partner_id, SUM(l.debit - l.credit), l.id
query = '''SELECT l.id
FROM account_move_line AS l, account_account, account_move am
WHERE (l.account_id = account_account.id) AND (l.move_id = am.id)
AND (am.state IN %s)
AND (account_account.internal_type IN %s)
AND (l.partner_id IN %s)
AND l.reconciled IS FALSE
AND ''' + dates_query + '''
AND (l.date <= %s)
GROUP BY l.partner_id, l.id''', args_list)
partners_partial = cr.fetchall()
partners_amount = dict((partner_id, 0) for partner_id, amount, line_id in partners_partial)
for partner_id, amount, line_id in partners_partial:
partial_reconcile_ids = []
line = self.env['account.move.line'].browse(line_id)
GROUP BY l.partner_id, l.id'''
cr.execute(query, args_list)
partners_amount = {}
aml_ids = cr.fetchall()
aml_ids = aml_ids and aml_ids[0] or []
for line in self.env['account.move.line'].browse(aml_ids):
if line.partner_id.id not in partners_amount:
partners_amount[line.partner_id.id] = 0.0
line_amount = line.amount_residual
for partial_line in (line.matched_debit_ids + line.matched_credit_ids):
if not partial_line.credit_move_id.id in partial_reconcile_ids:
partial_reconcile_ids.append(partial_line.credit_move_id.id)
if not partial_line.debit_move_id.id in partial_reconcile_ids:
partial_reconcile_ids.append(partial_line.debit_move_id.id)
if partial_reconcile_ids:
# in case of partial reconciliation, we want to keep the left amount in the oldest period
cr.execute('''SELECT MIN(COALESCE(date_maturity, date)) from account_move_line where id = %s''', (line_id,))
date = cr.fetchall()
partial = False
if 'BETWEEN' in dates_query:
partial = date and args_list[-3] <= date[0][0] <= args_list[-2]
elif '>=' in dates_query:
partial = date and date[0][0] >= form[str(i)]['start']
else:
partial = date and date[0][0] <= form[str(i)]['stop']
if partial:
# partial reconcilation
limit_date = 'COALESCE(l.date_maturity, l.date) <= %s'
cr.execute('''SELECT SUM(l.debit - l.credit)
FROM account_move_line AS l, account_move AS am
WHERE l.move_id = am.id AND am.state IN %s
AND l.id IN %s
AND ''' + limit_date, (tuple(move_state), tuple(partial_reconcile_ids), date_from))
unreconciled_amount = cr.fetchall()
partners_amount[partner_id] += unreconciled_amount[0][0]
else:
partners_amount[partner_id] += amount
if partial_line.create_date > date_from + ' 23:59:59':
line_amount = line.balance
break
partners_amount[line.partner_id.id] += line_amount
history.append(partners_amount)
for partner in partners:
@@ -148,12 +127,9 @@ class ReportAgedPartnerBalance(models.AbstractModel):
# Adding counter
self.total_account[(i)] = self.total_account[(i)] + (during and during[0] or 0)
values[str(i)] = during and during[0] or 0.0
total = False
if totals.has_key( partner['id'] ):
total = [ totals[partner['id']] ]
values['total'] = total and total[0] or 0.0
values['total'] = sum([values['direction']] + [values[str(i)] for i in range(5)])
## Add for total
self.total_account[(i+1)] = self.total_account[(i+1)] + (total and total[0] or 0.0)
self.total_account[(i + 1)] += values['total']
values['name'] = partner['name']
res.append(values)
+5
View File
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import report
import wizard
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Extra Accounting Reports',
'version': '1.1',
'category': 'Accounting & Finance',
'description': """
Extra Accounting Reports.
====================================
This module adds two new reports:
* Sale/Purchase Journal report
* Partner Ledger
""",
'website': 'https://www.odoo.com/page/accounting',
'depends': ['account_accountant'],
'data': [
'wizard/account_report_print_journal_view.xml',
'views/report_journal.xml',
'wizard/account_report_partner_ledger_view.xml',
'views/report_partnerledger.xml',
'data/account_report.xml'
],
'demo': [],
'installable': True,
'auto_install': False,
}
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- QWeb Reports -->
<report
id="action_report_journal"
model="account_extra_reports.report.journal"
string="Sale/Purchase Journal"
report_type="qweb-pdf"
name="account_extra_reports.report_journal"
file="account_extra_reports.report_journal"
/>
<report
id="action_report_partnerledger"
model="account_extra_reports.report_partnerledger"
string="Partner Ledger"
report_type="qweb-pdf"
name="account_extra_reports.report_partnerledger"
file="account_extra_reports.report_partnerledger"
/>
</data>
</openerp>
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import account_journal
import account_partner_ledger
@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
import time
from openerp import api, models
class ReportJournal(models.AbstractModel):
_name = 'report.account_extra_reports.report_journal'
def lines(self, target_move, journal_ids, sort_selection, data):
if isinstance(journal_ids, int):
journal_ids = [journal_ids]
move_state = ['draft', 'posted']
if target_move == 'posted':
move_state = ['posted']
query_get_clause = self._get_query_get_clause(data)
params = [tuple(move_state), tuple(journal_ids)] + query_get_clause[2]
query = 'SELECT "account_move_line".id FROM ' + query_get_clause[0] + ', account_move am WHERE "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' + query_get_clause[1] + ' ORDER BY '
if sort_selection == 'date':
query += '"account_move_line".date'
else:
query += 'am.name'
query += ', "account_move_line".move_id'
self.env.cr.execute(query, tuple(params))
ids = map(lambda x: x[0], self.env.cr.fetchall())
return self.env['account.move.line'].browse(ids)
def _sum_debit(self, data, journal_id):
move_state = ['draft', 'posted']
if data['form'].get('target_move', 'all') == 'posted':
move_state = ['posted']
query_get_clause = self._get_query_get_clause(data)
params = [tuple(move_state), tuple(journal_id.ids)] + query_get_clause[2]
self.env.cr.execute('SELECT SUM(debit) FROM ' + query_get_clause[0] + ', account_move am '
'WHERE "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' + query_get_clause[1] + ' ',
tuple(params))
return self.env.cr.fetchone()[0] or 0.0
def _sum_credit(self, data, journal_id):
move_state = ['draft', 'posted']
if data['form'].get('target_move', 'all') == 'posted':
move_state = ['posted']
query_get_clause = self._get_query_get_clause(data)
params = [tuple(move_state), tuple(journal_id.ids)] + query_get_clause[2]
self.env.cr.execute('SELECT SUM(credit) FROM ' + query_get_clause[0] + ', account_move am '
'WHERE "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' + query_get_clause[1] + ' ',
tuple(params))
return self.env.cr.fetchone()[0] or 0.0
def _get_taxes(self, data, journal_id):
move_state = ['draft', 'posted']
if data['form'].get('target_move', 'all') == 'posted':
move_state = ['posted']
query_get_clause = self._get_query_get_clause(data)
params = [tuple(move_state), tuple(journal_id.ids)] + query_get_clause[2]
self.env.cr.execute('SELECT DISTINCT tax_line_id FROM ' + query_get_clause[0] + ', account_move am '
'WHERE "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' + query_get_clause[1] + ' AND tax_line_id is not null',
tuple(params))
ids = map(lambda x: x[0], self.env.cr.fetchall())
taxes = self.env['account.tax'].browse(ids)
res = {}
for tax in taxes:
res[tax] = {}
self.env.cr.execute('SELECT sum(debit - credit) FROM ' + query_get_clause[0] + ', account_move am '
'WHERE "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' + query_get_clause[1] + ' AND tax_line_id = %s',
tuple(params + [tax.id]))
res[tax]['tax_amount'] = self.env.cr.fetchone()[0] or 0.0
amls = self.env['account.move.line'].search([('tax_ids', 'in', [tax.id])])
self.env.cr.execute('SELECT sum(debit - credit) FROM ' + query_get_clause[0] + ', account_move am '
'WHERE "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' + query_get_clause[1] + ' AND "account_move_line".id in %s',
tuple(params + [tuple(amls.ids)]))
res[tax]['base_amount'] = self.env.cr.fetchone()[0] or 0.0
if journal_id.type == 'sale':
#sales operation are credits
res[tax]['base_amount'] = res[tax]['base_amount'] * -1
res[tax]['tax_amount'] = res[tax]['tax_amount'] * -1
return res
def _get_query_get_clause(self, data):
return self.env['account.move.line'].with_context(data['form'].get('used_context', {}))._query_get()
@api.multi
def render_html(self, data):
target_move = data['form'].get('target_move', 'all')
sort_selection = data['form'].get('sort_selection', 'date')
res = {}
for journal in data['form']['journal_ids']:
res[journal] = self.with_context(data['form'].get('used_context', {})).lines(target_move, journal, sort_selection, data)
docargs = {
'doc_ids': data['form']['journal_ids'],
'doc_model': self.env['account.journal'],
'data': data,
'docs': self.env['account.journal'].browse(data['form']['journal_ids']),
'time': time,
'lines': res,
'sum_credit': self._sum_credit,
'sum_debit': self._sum_debit,
'get_taxes': self._get_taxes,
}
return self.env['report'].render('account_extra_reports.report_journal', docargs)
@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
import time
from openerp import api, models
class ReportPartnerLedger(models.AbstractModel):
_name = 'report.account_extra_reports.report_partnerledger'
def _lines(self, data, partner):
full_account = []
query_get_data = self.env['account.move.line'].with_context(data['form'].get('used_context', {}))._query_get()
reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".reconciled = false '
params = [partner.id, tuple(data['computed']['move_state']), tuple(data['computed']['account_ids'])] + query_get_data[2]
query = """
SELECT "account_move_line".id, "account_move_line".date, j.code, acc.code as a_code, acc.name as a_name, "account_move_line".ref, m.name as move_name, "account_move_line".name, "account_move_line".debit, "account_move_line".credit, "account_move_line".amount_currency,"account_move_line".currency_id, c.symbol AS currency_code
FROM """ + query_get_data[0] + """
LEFT JOIN account_journal j ON ("account_move_line".journal_id = j.id)
LEFT JOIN account_account acc ON ("account_move_line".account_id = acc.id)
LEFT JOIN res_currency c ON ("account_move_line".currency_id=c.id)
LEFT JOIN account_move m ON (m.id="account_move_line".move_id)
WHERE "account_move_line".partner_id = %s
AND m.state IN %s
AND "account_move_line".account_id IN %s AND """ + query_get_data[1] + reconcile_clause + """
ORDER BY "account_move_line".date"""
self.env.cr.execute(query, tuple(params))
res = self.env.cr.dictfetchall()
sum = 0.0
for r in res:
r['displayed_name'] = '-'.join(
r['move_name'] not in ['', '/'] and [r['move_name']] or [] +
r['ref'] not in ['', '/'] and [r['ref']] or [] +
r['name'] not in ['', '/'] and [r['name']] or []
)
sum += r['debit'] - r['credit']
r['progress'] = sum
full_account.append(r)
return full_account
def _sum_partner(self, data, partner, field):
if field not in ['debit', 'credit', 'debit - credit']:
return
result = 0.0
query_get_data = self.env['account.move.line'].with_context(data['form'].get('used_context', {}))._query_get()
reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".reconciled = false '
params = [partner.id, tuple(data['computed']['move_state']), tuple(data['computed']['account_ids'])] + query_get_data[2]
query = """SELECT sum(""" + field + """)
FROM """ + query_get_data[0] + """, account_move AS m
WHERE "account_move_line".partner_id = %s
AND m.id = "account_move_line".move_id
AND m.state IN %s
AND account_id IN %s
AND """ + query_get_data[1] + reconcile_clause
self.env.cr.execute(query, tuple(params))
contemp = self.env.cr.fetchone()
if contemp is not None:
result = contemp[0] or 0.0
return result
@api.multi
def render_html(self, data):
data['computed'] = {}
obj_partner = self.env['res.partner']
query_get_data = self.env['account.move.line'].with_context(data['form'].get('used_context', {}))._query_get()
data['computed']['move_state'] = ['draft', 'posted']
if data['form'].get('target_move', 'all') == 'posted':
data['computed']['move_state'] = ['posted']
result_selection = data['form'].get('result_selection', 'customer')
if result_selection == 'supplier':
data['computed']['ACCOUNT_TYPE'] = ['payable']
elif result_selection == 'customer':
data['computed']['ACCOUNT_TYPE'] = ['receivable']
else:
data['computed']['ACCOUNT_TYPE'] = ['payable', 'receivable']
self.env.cr.execute("""
SELECT a.id
FROM account_account a
WHERE a.internal_type IN %s
AND NOT a.deprecated""", (tuple(data['computed']['ACCOUNT_TYPE']),))
data['computed']['account_ids'] = [a for (a,) in self.env.cr.fetchall()]
params = [tuple(data['computed']['move_state']), tuple(data['computed']['account_ids'])] + query_get_data[2]
reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".reconciled = false '
query = """
SELECT DISTINCT "account_move_line".partner_id
FROM """ + query_get_data[0] + """, account_account AS account, account_move AS am
WHERE "account_move_line".partner_id IS NOT NULL
AND "account_move_line".account_id = account.id
AND am.id = "account_move_line".move_id
AND am.state IN %s
AND "account_move_line".account_id IN %s
AND NOT account.deprecated
AND """ + query_get_data[1] + reconcile_clause
self.env.cr.execute(query, tuple(params))
partner_ids = [res['partner_id'] for res in self.env.cr.dictfetchall()]
partners = obj_partner.browse(partner_ids)
partners = sorted(partners, key=lambda x: (x.ref, x.name))
docargs = {
'doc_ids': partner_ids,
'doc_model': self.env['res.partner'],
'data': data,
'docs': partners,
'time': time,
'lines': self._lines,
'sum_partner': self._sum_partner,
}
return self.env['report'].render('account_extra_reports.report_partnerledger', docargs)
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_journal">
<t t-call="report.html_container">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<t t-foreach="docs" t-as="o">
<t t-call="report.internal_layout">
<div class="page">
<h2><t t-esc="o.name"/> Journal</h2>
<div class="row mt32">
<div class="col-xs-3">
<strong>Company:</strong>
<p t-esc="res_company.name"/>
</div>
<div class="col-xs-3">
<strong>Journal:</strong>
<p t-esc="o.name"/>
</div>
<div class="col-xs-3">
<strong>Entries Sorted By:</strong>
<p t-if="data['form'].get('sort_selection') != 'l.date'">Journal Entry Number</p>
<p t-if="data['form'].get('sort_selection') == 'l.date'">Date</p>
</div>
<div class="col-xs-3">
<strong>Target Moves:</strong>
<p t-if="data['form']['target_move'] == 'all'">All Entries</p>
<p t-if="data['form']['target_move'] == 'posted'">All Posted Entries</p>
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th>Move</th>
<th>Date</th>
<th>Account</th>
<th>Partner</th>
<th>Label</th>
<th>Debit</th>
<th>Credit</th>
<th t-if="data['form']['amount_currency']">Currency</th>
</tr>
</thead>
<tbody>
<tr t-foreach="lines[o.id]" t-as="aml">
<td><span t-esc="aml.move_id.name &lt;&gt; '/' and aml.move_id.name or ('*'+str(aml.move_id.id))"/></td>
<td><span t-field="aml.date"/></td>
<td><span t-field="aml.account_id.code"/></td>
<td><span t-esc="aml.partner_id and aml.partner_id.name[:23] or ''"/></td>
<td><span t-esc="aml.name[:35]"/></td>
<td><span t-esc="aml.debit" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
<td><span t-esc="aml.credit" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
<td t-if="data['form']['amount_currency'] and aml.amount_currency">
<span t-esc="aml.amount_currency" t-esc-options='{"widget": "monetary", "display_currency": "aml.currency_id"}'/>
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-xs-4 pull-right">
<table class="table table-condensed">
<tr>
<td><strong>Total</strong></td>
<td><span t-esc="sum_debit(data, o)" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
<td><span t-esc="sum_credit(data, o)" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
</tr>
</table>
</div>
</div>
<div class="row">
<div class="col-xs-4">
<table class="table table-condensed">
<thead>
<tr><th colspan="3">Tax Declaration</th></tr>
<tr>
<th>Name</th>
<th>Base Amount</th>
<th>Tax Amount</th>
</tr>
</thead>
<tbody>
<t t-set="taxes" t-value="get_taxes(data, o)"/>
<tr t-foreach="taxes" t-as="tax">
<td><span t-esc="tax.name"/></td>
<td><span t-esc="taxes[tax]['base_amount']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
<td><span t-esc="taxes[tax]['tax_amount']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_partnerledger">
<t t-call="report.html_container">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<t t-foreach="docs" t-as="o">
<t t-call="report.internal_layout">
<div class="page">
<h2>Partner Ledger</h2>
<div class="row">
<div class="col-xs-3">
<strong>Company:</strong>
<p t-esc="res_company.name"/>
</div>
<div class="col-xs-3">
<t t-if="data['form']['date_from']"><strong>Date from :</strong> <span t-esc="data['form']['date_from']"/><br/></t>
<t t-if="data['form']['date_to']"><strong>Date to :</strong> <span t-esc="data['form']['date_to']"/></t>
</div>
<div class="col-xs-3">
<strong>Target Moves:</strong>
<p t-if="data['form']['target_move'] == 'all'">All Entries</p>
<p t-if="data['form']['target_move'] == 'posted'">All Posted Entries</p>
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th>Date</th>
<th>JRNL</th>
<th>Account</th>
<th>Ref</th>
<th>Debit</th>
<th>Credit</th>
<th>Balance</th>
<th t-if="amount_currency">Currency</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="4">
<strong t-esc="o.ref"/>
- <strong t-esc="o.name"/>
</td>
<td class="text-right">
<strong t-esc="sum_partner(data, o, 'debit')" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
</td>
<td class="text-right">
<strong t-esc="sum_partner(data, o, 'credit')" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
</td>
<td class="text-right">
<strong t-esc="sum_partner(data, o, 'debit - credit')" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
</td>
</tr>
<tr t-foreach="lines(data, o)" t-as="line">
<td>
<span t-esc="line['date']"/>
</td>
<td>
<span t-esc="line['code']"/>
</td>
<td>
<span t-esc="line['a_code']"/>
</td>
<td>
<span t-esc="line['displayed_name']"/>
</td>
<td class="text-right">
<span t-esc="line['debit']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
</td>
<td class="text-right">
<span t-esc="line['credit']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
</td>
<td class="text-right">
<span t-esc="line['progress']" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/>
</td>
<td t-if="amount_currency">
<span t-esc="line['amount_currency']" t-esc-options='{"widget": "monetary", "display_currency": "line[\"currency_id\"]"}'/>
</td>
</tr>
</tbody>
</table>
</div>
</t>
</t>
</t>
</template>
</odoo>
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import account_report_common_journal
import account_report_print_journal
import account_report_partner_ledger
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from openerp import api, fields, models
class AccountCommonJournalReport(models.TransientModel):
_name = 'account.common.journal.report'
_description = 'Account Common Journal Report'
_inherit = "account.common.report"
amount_currency = fields.Boolean('With Currency', help="Print Report with the currency column if the currency differs from the company currency.")
@api.multi
def pre_print_report(self, data):
data['form'].update({'amount_currency': self.amount_currency})
return data
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from openerp import fields, models, _
from openerp.exceptions import UserError
class AccountPartnerLedger(models.TransientModel):
_inherit = "account.common.partner.report"
_name = "account.report.partner.ledger"
_description = "Account Partner Ledger"
amount_currency = fields.Boolean("With Currency", help="It adds the currency column on report if the currency differs from the company currency.")
reconciled = fields.Boolean('Reconciled Entries')
def _print_report(self, data):
data = self.pre_print_report(data)
data['form'].update({'reconciled': self.reconciled, 'amount_currency': self.amount_currency})
return self.env['report'].get_action(self, 'account_extra_reports.report_partnerledger', data=data)
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_report_partner_ledger_view" model="ir.ui.view">
<field name="name">Partner Ledger</field>
<field name="model">account.report.partner.ledger</field>
<field name="inherit_id" ref="account.account_common_report_view"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='target_move']" position="after">
<field name="result_selection"/>
<field name="amount_currency" groups="base.group_multi_currency"/>
<newline/>
<field name="reconciled"/>
<newline/>
</xpath>
</data>
</field>
</record>
<record id="action_account_partner_ledger_menu" model="ir.actions.act_window">
<field name="name">Partner Ledger</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.report.partner.ledger</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="account_report_partner_ledger_view"/>
<field name="target">new</field>
</record>
<record id="action_account_partner_ledger_values" model="ir.values">
<field name="model_id" ref="account.model_account_account" />
<field name="name">Partner Ledger</field>
<field name="key2">client_print_multi</field>
<field name="value" eval="'ir.actions.act_window,' +str(ref('action_account_partner_ledger_menu'))" />
<field name="key">action</field>
<field name="model">account.account</field>
</record>
<menuitem
id="menu_partner_ledger"
name="Partner Ledger"
parent="account.account_reports_management_menu"
action="action_account_partner_ledger_menu"
groups="account.group_account_manager,account.group_account_user"
/>
</odoo>
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from openerp import fields, models
class AccountPrintJournal(models.TransientModel):
_inherit = "account.common.journal.report"
_name = "account.print.journal"
_description = "Account Print Journal"
sort_selection = fields.Selection([('date', 'Date'), ('move_name', 'Journal Entry Number'),], 'Entries Sorted by', required=True, default='move_name')
journal_ids = fields.Many2many('account.journal', string='Journals', required=True, default=lambda self: self.env['account.journal'].search([('type', 'in', ['sale', 'purchase'])]))
def _print_report(self, data):
data = self.pre_print_report(data)
data['form'].update({'sort_selection': self.sort_selection})
return self.env['report'].with_context(landscape=True).get_action(self, 'account_extra_reports.report_journal', data=data)
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_report_print_journal_view" model="ir.ui.view">
<field name="name">Sale/Purchase Journal</field>
<field name="model">account.print.journal</field>
<field name="inherit_id" ref="account.account_common_report_view"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='target_move']" position="after">
<field name="amount_currency" groups="base.group_multi_currency"/>
<field name="sort_selection" widget="radio"/>
<newline/>
</xpath>
<xpath expr="//field[@name='journal_ids']" position="attributes">
<attribute name="domain">[('type', 'in', ('sale', 'purchase'))]</attribute>
</xpath>
</data>
</field>
</record>
<record id="action_account_print_journal_menu" model="ir.actions.act_window">
<field name="name">Sale/Purchase Journal</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.print.journal</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="account_report_print_journal_view"/>
<field name="target">new</field>
</record>
<menuitem
id="menu_print_journal"
name="Sale/Purchase Journal"
parent="account.account_reports_management_menu"
action="action_account_print_journal_menu"
groups="account.group_account_manager,account.group_account_user"
/>
</odoo>
+1 -1
View File
@@ -188,7 +188,7 @@ class barcode_rule(models.Model):
@api.model
def _get_type_selection(self):
return [('alias','Alias'),('product','Unit Product')]
return [('alias', _('Alias')), ('product', _('Unit Product'))]
_columns = {
'name': fields.char('Rule Name', size=32, required=True, help='An internal identification for this barcode nomenclature rule'),
+8
View File
@@ -53,7 +53,9 @@ msgid "A barcode nomenclature defines how the point of sale identify and interpr
msgstr ""
#. module: barcodes
#: code:addons/barcodes/barcodes.py:191
#: model:ir.model.fields,field_description:barcodes.field_barcode_rule_alias
#, python-format
msgid "Alias"
msgstr ""
@@ -267,6 +269,12 @@ msgstr ""
msgid "UPC/EAN Conversion"
msgstr ""
#. module: barcodes
#: code:addons/barcodes/barcodes.py:191
#, python-format
msgid "Unit Product"
msgstr ""
#. module: barcodes
#: model:ir.model.fields,help:barcodes.field_barcode_rule_sequence
msgid "Used to order rules such that rules with a smaller sequence match first"
+48 -59
View File
@@ -1534,74 +1534,63 @@ class calendar_event(osv.Model):
return super(calendar_event, self)._name_search(cr, user, name=name, args=args, operator=operator, context=context, limit=limit, name_get_uid=name_get_uid)
def write(self, cr, uid, ids, values, context=None):
def _only_changes_to_apply_on_real_ids(field_names):
''' return True if changes are only to be made on the real ids'''
for field in field_names:
if field in ['start', 'start_date', 'start_datetime', 'stop', 'stop_date', 'stop_datetime', 'active']:
return True
return False
context = context or {}
if not isinstance(ids, (tuple, list)):
ids = [ids]
context = context or {}
self._set_date(cr, uid, values, id=ids[0], context=context)
values0 = values
for one_ids in ids:
if isinstance(one_ids, (basestring, int, long)):
if len(str(one_ids).split('-')) == 1:
ids = [int(one_ids)]
else:
ids = [one_ids]
# process events one by one
for event_id in ids:
# make a copy, since _set_date() modifies values depending on event
values = dict(values0)
self._set_date(cr, uid, values, event_id, context=context)
res = False
new_id = False
# Special write of complex IDS
for event_id in list(ids):
if len(str(event_id).split('-')) == 1:
continue
ids.remove(event_id)
real_event_id = calendar_id2real_id(event_id)
# if we are setting the recurrency flag to False or if we are only changing fields that
# should be only updated on the real ID and not on the virtual (like message_follower_ids):
# then set real ids to be updated.
if not values.get('recurrency', True) or not _only_changes_to_apply_on_real_ids(values.keys()):
ids.append(real_event_id)
continue
# special write of complex IDS
real_ids = []
new_ids = []
if '-' not in str(event_id):
real_ids = [int(event_id)]
else:
data = self.read(cr, uid, event_id, ['start', 'stop', 'rrule', 'duration'])
if data.get('rrule'):
new_id = self._detach_one_event(cr, uid, event_id, values, context=None)
real_event_id = calendar_id2real_id(event_id)
res = super(calendar_event, self).write(cr, uid, [int(event_id) for event_id in ids], values, context=context)
# set end_date for calendar searching
if values.get('recurrency', True) and values.get('end_type', 'count') in ('count', unicode('count')) and \
(values.get('rrule_type') or values.get('count') or values.get('start') or values.get('stop')):
for id in ids:
final_date = self._get_recurrency_end_date(cr, uid, id, context=context)
super(calendar_event, self).write(cr, uid, [id], {'final_date': final_date}, context=context)
attendees_create = False
if values.get('partner_ids', False):
attendees_create = self.create_attendees(cr, uid, ids, context)
if (values.get('start_date') or values.get('start_datetime', False)) and values.get('active', True):
the_id = new_id or (ids and int(ids[0]))
if the_id:
if attendees_create:
attendees_create = attendees_create[the_id]
mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids']))
# if we are setting the recurrency flag to False or if we are only changing fields that
# should be only updated on the real ID and not on the virtual (like message_follower_ids):
# then set real ids to be updated.
blacklisted = any(key in values for key in ('start', 'stop', 'active'))
if not values.get('recurrency', True) or not blacklisted:
real_ids = [real_event_id]
else:
mail_to_ids = [att.id for att in self.browse(cr, uid, the_id, context=context).attendee_ids]
data = self.read(cr, uid, event_id, ['start', 'stop', 'rrule', 'duration'])
if data.get('rrule'):
new_ids = [self._detach_one_event(cr, uid, event_id, values, context=None)]
if mail_to_ids:
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='calendar_template_meeting_changedate', email_from=current_user.email, context=context)
return res or True and False
super(calendar_event, self).write(cr, uid, real_ids, values, context=context)
# set end_date for calendar searching
if values.get('recurrency') and values.get('end_type', 'count') in ('count', unicode('count')) and \
(values.get('rrule_type') or values.get('count') or values.get('start') or values.get('stop')):
for id in real_ids:
final_date = self._get_recurrency_end_date(cr, uid, id, context=context)
super(calendar_event, self).write(cr, uid, [id], {'final_date': final_date}, context=context)
attendees_create = False
if values.get('partner_ids', False):
attendees_create = self.create_attendees(cr, uid, real_ids + new_ids, context)
if (values.get('start_date') or values.get('start_datetime')) and values.get('active', True):
for the_id in real_ids + new_ids:
if attendees_create:
attendees_create = attendees_create[the_id]
mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids']))
else:
mail_to_ids = [att.id for att in self.browse(cr, uid, the_id, context=context).attendee_ids]
if mail_to_ids:
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='calendar_template_meeting_changedate', email_from=current_user.email, context=context)
return True
def create(self, cr, uid, vals, context=None):
if context is None:
@@ -91,5 +91,12 @@
meetings = self.browse(cr, uid, meeting_ids, context)
for meeting in meetings:
assert meeting.description == 'Review code of the module: sync_google_calendar.', 'Description not changed for id: %s' %meeting.id
-
I update the description of two meetings, and check that both have been updated
-
!python {model: calendar.event}: |
DESCRIPTION = "Some description"
ids = [ref('calendar_event_testmeeting0'), ref('calendar_event_sprintreview0')]
self.write(cr, uid, ids, {'description': DESCRIPTION}, context=context)
for i, event in enumerate(self.browse(cr, uid, ids, context)):
assert event.description == DESCRIPTION, "Event %d has not been updated" % i
+1 -1
View File
@@ -122,7 +122,7 @@ class crm_team(osv.Model):
def action_your_pipeline(self, cr, uid, context=None):
IrModelData = self.pool['ir.model.data']
action = IrModelData.xmlid_to_object(cr, uid, 'crm.crm_lead_opportunities_tree_view').read(['name', 'help', 'res_model', 'target', 'domain', 'context', 'type', 'search_view_id'])
action = IrModelData.xmlid_to_object(cr, uid, 'crm.crm_lead_opportunities_tree_view', context=context).read(['name', 'help', 'res_model', 'target', 'domain', 'context', 'type', 'search_view_id'])
if not action:
action = {}
else:
+1 -1
View File
@@ -15,7 +15,7 @@
<field name="name">Partner Review</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="filter_id" ref="filter_partner"/>
<field name="google_drive_template_url">https://docs.google.com/spreadsheet/ccc?key=0Ah2qnrLAoZmUdGRvdVdmS1VoSDctWk1kd18taGZ4ckE#gid=0</field>
<field name="google_drive_template_url">https://docs.google.com/spreadsheet/ccc?key=1eIM-q7PX9wp1vEUUdUvrmAe8V3Do60vXDETulNmI9mA#gid=0</field>
<field name="name_template">Partner Review %(name)s</field>
</record>
+1 -1
View File
@@ -105,7 +105,7 @@ msgstr ""
#. module: hr_equipment
#: model:ir.model,name:hr_equipment.model_hr_equipment_category
#: model:ir.model.fields,field_description:hr_equipment.field_hr_equipment_category_id_5383
#: model:ir.model.fields,field_description:hr_equipment.field_hr_equipment_category_id_6493
msgid "Asset Category"
msgstr ""
@@ -66,7 +66,7 @@ class hr_payslip(osv.osv):
def onchange_contract_id(self, cr, uid, ids, date_from, date_to, employee_id=False, contract_id=False, context=None):
contract_obj = self.pool.get('hr.contract')
res = super(hr_payslip, self).onchange_contract_id(cr, uid, ids, date_from=date_from, date_to=date_to, employee_id=employee_id, contract_id=contract_id, context=context)
journal_id = contract_id and contract_obj.browse(cr, uid, contract_id, context=context).journal_id.id or False
journal_id = contract_id and contract_obj.browse(cr, uid, contract_id, context=context).journal_id.id or (not contract_id and self._get_default_journal(cr, uid, context=None))
res['value'].update({'journal_id': journal_id})
return res
+25 -13
View File
@@ -23,7 +23,7 @@ var LivechatButton = Widget.extend({
init: function (parent, server_url, options) {
this._super(parent);
this.options = _.defaults(options || {}, {
placeholder: _t('Ask something ...'),
input_placeholder: _t('Ask something ...'),
default_username: _t("Visitor"),
button_text: _t("Chat with one of our collaborators"),
default_message: _t("How may I help you?"),
@@ -91,10 +91,14 @@ var LivechatButton = Widget.extend({
return $.when.apply($, defs);
},
open_chat: function () {
open_chat: _.debounce(function () {
if (this.opening_chat) {
return;
}
var self = this;
var cookie = utils.get_cookie('im_livechat_session');
var def;
this.opening_chat = true;
clearTimeout(this.auto_popup_timeout);
if (cookie) {
def = $.when(JSON.parse(cookie));
@@ -120,13 +124,16 @@ var LivechatButton = Widget.extend({
utils.set_cookie('im_livechat_session', JSON.stringify(channel), 60*60);
utils.set_cookie('im_livechat_auto_popup', JSON.stringify(false), 60*60);
}
}).always(function () {
self.opening_chat = false;
});
},
}, 200, true),
open_chat_window: function (channel) {
var self = this;
var options = {
display_stars: false,
placeholder: this.options.input_placeholder || "",
};
var is_folded = (channel.state === 'folded');
this.chat_window = new ChatWindow(this, channel.id, channel.name, is_folded, channel.message_unread_counter, options);
@@ -136,7 +143,10 @@ var LivechatButton = Widget.extend({
});
this.chat_window.on("close_chat_session", this, function () {
var input_disabled = this.chat_window.$(".o_chat_input input").prop('disabled');
if (this.messages.length > 1 && !input_disabled) {
var ask_fb = !input_disabled && _.find(this.messages, function (msg) {
return msg.id !== '_welcome';
});
if (ask_fb) {
this.chat_window.toggle_fold(false);
this.ask_feedback();
} else {
@@ -212,15 +222,17 @@ var LivechatButton = Widget.extend({
},
send_welcome_message: function () {
this.add_message({
id: 1,
attachment_ids: [],
author_id: this.channel.operator_pid,
body: this.options.default_message,
channel_ids: [this.channel.id],
date: time.datetime_to_str(new Date()),
tracking_value_ids: [],
}, {prepend: true});
if (this.options.default_message) {
this.add_message({
id: '_welcome',
attachment_ids: [],
author_id: this.channel.operator_pid,
body: this.options.default_message,
channel_ids: [this.channel.id],
date: time.datetime_to_str(new Date()),
tracking_value_ids: [],
}, {prepend: true});
}
},
ask_feedback: function () {
+45 -2
View File
@@ -258,6 +258,13 @@ msgstr ""
msgid "@ Inbox"
msgstr ""
#. module: mail
#. openerp-web
#: code:addons/mail/static/src/xml/systray.xml:20
#, python-format
msgid "@Inbox"
msgstr ""
#. module: mail
#: model:ir.model.fields,help:mail.field_mail_alias_alias_defaults
#: model:ir.model.fields,help:mail.field_mail_channel_alias_defaults
@@ -419,6 +426,13 @@ msgstr ""
msgid "Aliases"
msgstr ""
#. module: mail
#. openerp-web
#: code:addons/mail/static/src/xml/systray.xml:12
#, python-format
msgid "All"
msgstr ""
#. module: mail
#: selection:res.partner,notify_email:0
msgid "All Messages"
@@ -658,7 +672,10 @@ msgid "Channels/Partner"
msgstr ""
#. module: mail
#. openerp-web
#: code:addons/mail/static/src/xml/systray.xml:13
#: model:ir.actions.client,name:mail.mail_channel_action_client_chat
#, python-format
msgid "Chat"
msgstr ""
@@ -1476,7 +1493,8 @@ msgstr ""
#. module: mail
#. openerp-web
#: code:addons/mail/static/src/js/client_action.js:31
#: code:addons/mail/static/src/js/client_action.js:30
#: code:addons/mail/static/src/xml/client_action.xml:123
#, python-format
msgid "Invite people"
msgstr ""
@@ -1813,6 +1831,8 @@ msgid "Message-Id"
msgstr ""
#. module: mail
#. openerp-web
#: code:addons/mail/static/src/xml/systray.xml:6
#: model:ir.actions.act_window,name:mail.action_view_mail_message
#: model:ir.model.fields,field_description:mail.field_mail_channel_message_ids
#: model:ir.model.fields,field_description:mail.field_mail_thread_message_ids
@@ -1820,6 +1840,7 @@ msgstr ""
#: model:ir.model.fields,field_description:mail.field_res_users_message_ids
#: model:ir.ui.menu,name:mail.menu_mail_message
#: model:ir.ui.view,arch_db:mail.view_message_tree
#, python-format
msgid "Messages"
msgstr ""
@@ -1931,8 +1952,11 @@ msgstr ""
#. module: mail
#. openerp-web
#: code:addons/mail/static/src/js/chat_manager.js:99
#: code:addons/mail/static/src/js/chat_manager.js:54
#: code:addons/mail/static/src/js/window_manager.js:124
#: code:addons/mail/static/src/xml/chatter.xml:67
#: code:addons/mail/static/src/xml/client_action.xml:127
#: code:addons/mail/static/src/xml/systray.xml:16
#, python-format
msgid "New message"
msgstr ""
@@ -2574,6 +2598,13 @@ msgstr ""
msgid "Send Email"
msgstr ""
#. module: mail
#. openerp-web
#: code:addons/mail/static/src/xml/client_action.xml:127
#, python-format
msgid "Send mail"
msgstr ""
#. module: mail
#: code:addons/mail/models/mail_template.py:237
#, python-format
@@ -2887,6 +2918,13 @@ msgstr ""
msgid "To (Partners)"
msgstr ""
#. module: mail
#. openerp-web
#: code:addons/mail/static/src/xml/chat_window.xml:24
#, python-format
msgid "To:"
msgstr ""
#. module: mail
#. openerp-web
#: code:addons/mail/static/src/xml/chatter.xml:30
@@ -3000,6 +3038,11 @@ msgstr ""
msgid "Unread Messages Counter"
msgstr ""
#. module: mail
#: model:ir.ui.view,arch_db:mail.view_message_search
msgid "Unread messages"
msgstr ""
#. module: mail
#. openerp-web
#: code:addons/mail/static/src/xml/client_action.xml:126
+1 -1
View File
@@ -445,7 +445,7 @@ class Channel(models.Model):
if self.channel_message_ids.ids:
last_message_id = self.channel_message_ids.ids[0] # zero is the index of the last message
self.env['mail.channel.partner'].search([('channel_id', 'in', self.ids), ('partner_id', '=', self.env.user.partner_id.id)]).write({'seen_message_id': last_message_id})
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), {'info': 'channel_seen', 'id': self.id})
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), {'info': 'channel_seen', 'id': self.id, 'last_message_id': last_message_id})
return last_message_id
@api.multi
+3 -2
View File
@@ -169,6 +169,7 @@ class MailThread(models.AbstractModel):
@api.multi
def _get_message_unread(self):
res = dict((res_id, 0) for res_id in self.ids)
partner_id = self.env.user.partner_id.id
# search for unread messages, directly in SQL to improve performances
self._cr.execute(""" SELECT msg.res_id FROM mail_message msg
@@ -176,8 +177,8 @@ class MailThread(models.AbstractModel):
ON rel.mail_message_id = msg.id
RIGHT JOIN mail_channel_partner cp
ON (cp.channel_id = rel.mail_channel_id AND cp.partner_id = %s AND (cp.seen_message_id < msg.id))
WHERE msg.model = %s AND msg.res_id in %s""",
(self.env.user.partner_id.id, self._name, tuple(self.ids),))
WHERE msg.model = %s AND msg.res_id in %s AND msg.author_id != %s""",
(partner_id, self._name, tuple(self.ids), partner_id,))
for result in self._cr.fetchall():
res[result[0]] += 1
+3 -2
View File
@@ -109,8 +109,9 @@ class publisher_warranty_contract(AbstractModel):
pass
if result.get('enterprise_info'):
# Update expiration date
self.pool['ir.config_parameter'].set_param(cr, SUPERUSER_ID, 'database.expiration_date', result.get('enterprise_info').get('expiration_date'), ['base.group_user'])
self.pool['ir.config_parameter'].set_param(cr, SUPERUSER_ID, 'database.expiration_reason', result.get('enterprise_info').get('expiration_reason', 'trial'), ['base.group_system'])
self.pool['ir.config_parameter'].set_param(cr, SUPERUSER_ID, 'database.expiration_date', result['enterprise_info'].get('expiration_date'), ['base.group_user'])
self.pool['ir.config_parameter'].set_param(cr, SUPERUSER_ID, 'database.expiration_reason', result['enterprise_info'].get('expiration_reason', 'trial'), ['base.group_system'])
self.pool['ir.config_parameter'].set_param(cr, SUPERUSER_ID, 'database.enterprise_code', result['enterprise_info'].get('enterprise_code'), ['base.group_user'])
except Exception:
if cron_mode:
+34 -18
View File
@@ -118,7 +118,7 @@ function strip_html (node, transform_children) {
function inline (node, transform_children) {
if (node.nodeType === 3) return node.data;
if (node.tagName === "BR") return " ";
if (node.tagName.match(/^(A|P|DIV)$/)) return transform_children();
if (node.tagName.match(/^(A|P|DIV|PRE)$/)) return transform_children();
node.innerHTML = transform_children();
return node.outerHTML;
}
@@ -133,7 +133,7 @@ function add_message (data, options) {
if (!msg) {
msg = make_message(data);
// Keep the array ordered by date when inserting the new message
// Keep the array ordered by id when inserting the new message
messages.splice(_.sortedIndex(messages, msg, 'id'), 0, msg);
_.each(msg.channel_ids, function (channel_id) {
var channel = chat_manager.get_channel(channel_id);
@@ -457,9 +457,7 @@ function update_channel_unread_counter (channel, counter) {
}
var channel_seen = _.throttle(function (channel) {
return ChannelModel.call('channel_seen', [[channel.id]]).then(function (last_seen_message_id) {
channel.last_seen_message_id = last_seen_message_id;
});
return ChannelModel.call('channel_seen', [[channel.id]], {}, {shadow: true});
}, 3000);
// Notification handlers
@@ -573,7 +571,7 @@ function on_mark_as_read_notification (data) {
_.each(data.channel_ids, function (channel_id) {
var channel = chat_manager.get_channel(channel_id);
if (channel) {
channel.needaction_counter -= data.message_ids.length;
channel.needaction_counter = Math.max(channel.needaction_counter - data.message_ids.length, 0);
}
});
} else { // if no channel_ids specified, this is a 'mark all read' in the inbox
@@ -581,7 +579,7 @@ function on_mark_as_read_notification (data) {
channel.needaction_counter = 0;
});
}
needaction_counter -= data.message_ids.length;
needaction_counter = Math.max(needaction_counter - data.message_ids.length, 0);
chat_manager.bus.trigger('update_needaction', needaction_counter);
}
@@ -609,8 +607,11 @@ function on_mark_as_unread_notification (data) {
function on_channel_seen_notification (data) {
var channel = chat_manager.get_channel(data.id);
if (channel && channel.unread_counter) {
update_channel_unread_counter(channel, 0);
if (channel) {
channel.last_seen_message_id = data.last_message_id;
if (channel.unread_counter) {
update_channel_unread_counter(channel, 0);
}
}
}
@@ -781,7 +782,7 @@ var chat_manager = {
}
if (!channel.members_deferred) {
channel.members_deferred = ChannelModel
.call("channel_fetch_listeners", [channel.uuid])
.call("channel_fetch_listeners", [channel.uuid], {}, {shadow: true})
.then(function (members) {
var suggestions = [];
_.each(mention_partner_suggestions, function (partners) {
@@ -790,7 +791,7 @@ var chat_manager = {
}));
});
return [members].concat(suggestions);
return [members];
});
}
return channel.members_deferred;
@@ -807,12 +808,27 @@ var chat_manager = {
return chat_unread_counter;
},
get_last_seen_message: function (channel) {
if (channel.last_seen_message_id) {
var messages = channel.cache['[]'].messages;
var msg = _.findWhere(messages, {id: channel.last_seen_message_id});
if (msg) {
var i = _.sortedIndex(messages, msg, 'id') + 1;
while (i < messages.length && messages[i].author_id && messages[i].author_id[0] === session.partner_id) {
msg = messages[i];
i++;
}
return msg;
}
}
},
get_discuss_ids: function () {
return discuss_ids;
},
detach_channel: function (channel) {
return ChannelModel.call("channel_minimize", [channel.uuid, true]);
return ChannelModel.call("channel_minimize", [channel.uuid, true], {}, {shadow: true});
},
remove_chatter_messages: function (model) {
messages = _.reject(messages, function (message) {
@@ -864,7 +880,7 @@ var chat_manager = {
},
close_chat_session: function (channel_id) {
var channel = this.get_channel(channel_id);
ChannelModel.call("channel_fold", [], {uuid : channel.uuid, state : "closed"});
ChannelModel.call("channel_fold", [], {uuid : channel.uuid, state : "closed"}, {shadow: true});
},
fold_channel: function (channel_id, folded) {
var args = {
@@ -873,7 +889,7 @@ var chat_manager = {
if (_.isBoolean(folded)) {
args.state = folded ? 'folded' : 'open';
}
return ChannelModel.call("channel_fold", [], args);
return ChannelModel.call("channel_fold", [], args, {shadow: true});
},
/**
* Special redirection handling for given model and id
@@ -948,8 +964,8 @@ var chat_manager = {
return parse_and_transform(message_body, inline);
},
search_partner: function (search_val) {
return PartnerModel.call('im_search', [search_val, 20]).then(function(result) {
search_partner: function (search_val, limit) {
return PartnerModel.call('im_search', [search_val, limit || 20], {}, {shadow: true}).then(function(result) {
var values = [];
_.each(result, function(user) {
var escaped_name = _.escape(user.name);
@@ -994,8 +1010,8 @@ function init () {
});
var ir_model = new Model("ir.model.data");
var load_menu_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_menu_root_chat"]);
var load_action_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_action_client_chat"]);
var load_menu_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_menu_root_chat"], {}, {shadow: true});
var load_action_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_action_client_chat"], {}, {shadow: true});
bus.on('notification', null, on_notification);
+9 -1
View File
@@ -24,10 +24,10 @@ return Widget.extend({
this._super(parent);
this.title = title;
this.channel_id = channel_id;
this.placeholder = _t("Say something");
this.folded = is_folded;
this.options = _.defaults(options || {}, {
display_stars: true,
placeholder: _t("Say something"),
});
this.unread_msgs = unread_msgs || 0;
this.is_hidden = false;
@@ -46,6 +46,8 @@ return Widget.extend({
if (this.folded) {
this.$el.css('height', HEIGHT_FOLDED);
} else {
this.focus_input();
}
var def = this.thread.replace(this.$('.o_chat_content'));
return $.when(this._super(), def);
@@ -67,9 +69,15 @@ return Widget.extend({
this.folded = _.isBoolean(fold) ? fold : !this.folded;
if (!this.folded) {
this.thread.scroll_to();
this.focus_input();
}
this.fold();
},
focus_input: function () {
if (!config.device.touch) {
this.$input.focus();
}
},
do_show: function () {
this.is_hidden = false;
this._super.apply(this, arguments);
+20 -1
View File
@@ -451,6 +451,7 @@ var ChatterComposer = composer.BasicComposer.extend({
record_name: false,
is_log: false,
internal_subtypes: [],
default_body: '',
});
if (this.options.is_log) {
this.options.send_text = _('Log');
@@ -467,6 +468,13 @@ var ChatterComposer = composer.BasicComposer.extend({
return $.when(this._super.apply(this, arguments), this.message_get_suggested_recipients());
},
start: function () {
var self = this;
return this._super().then(function () {
self.$input.val(self.options.default_body);
});
},
should_send: function () {
return false;
},
@@ -866,6 +874,8 @@ var Chatter = form_common.AbstractField.extend({
// destroy current composer, if any
if (this.composer) {
this.composer.destroy();
this.composer = undefined;
this.mute_new_message_button(false);
}
// fetch and render messages of current document
@@ -895,6 +905,7 @@ var Chatter = form_common.AbstractField.extend({
internal_subtypes: this.options.internal_subtypes,
is_log: options && options.is_log,
record_name: this.record_name,
default_body: old_composer && old_composer.$input.val(),
});
this.composer.on('input_focused', this, function () {
this.composer.mention_set_prefetched_partners(this.mention_suggestions || []);
@@ -910,11 +921,19 @@ var Chatter = form_common.AbstractField.extend({
self.composer.on('post_message', self, self.on_post_message);
self.composer.on('need_refresh', self, self.refresh_followers);
});
this.mute_new_message_button(true);
},
close_composer: function () {
if (this.composer.is_empty()) {
this.composer.do_hide();
this.mute_new_message_button(false);
}
},
mute_new_message_button: function (mute) {
if (mute) {
this.$('.o_chatter_button_new_message').removeClass('btn-primary').addClass('btn-default');
} else if (!mute) {
this.$('.o_chatter_button_new_message').removeClass('btn-default').addClass('btn-primary');
}
},
+4 -6
View File
@@ -171,8 +171,8 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
this.searchview = new SearchView(this, dataset, view_id, {}, options);
this.searchview.on('search_data', this, this.on_search);
this.basic_composer = new composer.BasicComposer(this);
this.extended_composer = new composer.ExtendedComposer(this);
this.basic_composer = new composer.BasicComposer(this, {mention_partners_restricted: true});
this.extended_composer = new composer.ExtendedComposer(this, {mention_partners_restricted: true});
this.thread = new ChatThread(this, {
display_help: true,
shorten_messages: false,
@@ -423,13 +423,11 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
// Compute position of the 'New messages' separator, only once when joining
// a channel to keep it in the thread when new messages arrive
if (_.isUndefined(this.messages_separator_position)) {
var msg_id = this.last_seen_message_id;
if (!this.unread_counter) {
this.messages_separator_position = false; // no unread message -> don't display separator
} else if ((msg_id === false) || !_.findWhere(messages, {id: msg_id})) {
this.messages_separator_position = 'top'; // all displayed messages are unread
} else {
this.messages_separator_position = msg_id; // last read message is msg_id
var msg = chat_manager.get_last_seen_message(this.channel);
this.messages_separator_position = msg ? msg.id : 'top';
}
}
return {
+2 -1
View File
@@ -331,6 +331,7 @@ var BasicComposer = Widget.extend({
input_max_height: 150,
input_min_height: 28,
mention_fetch_limit: 8,
mention_partners_restricted: false, // set to true to only suggest prefetched partners
send_text: _('Send'),
});
this.context = this.options.context;
@@ -620,7 +621,7 @@ var BasicComposer = Widget.extend({
}
}
});
if (!suggestions.length) {
if (!suggestions.length && !self.options.mention_partners_restricted) {
// no result found among prefetched partners, fetch other suggestions
var kwargs = {
limit: limit,
+4 -2
View File
@@ -343,8 +343,10 @@ core.bus.on('web_client_ready', null, function () {
var chat_session = _.findWhere(chat_sessions, {id: channel.id});
if (!chat_session || chat_session.window.folded) {
chat_manager.detach_channel(channel);
} else if (chat_session && chat_session.window.is_hidden) {
} else if (chat_session.window.is_hidden) {
make_session_visible(chat_session);
} else {
chat_session.window.focus_input();
}
});
@@ -370,7 +372,7 @@ return ChatWindow.extend({
self.$('.o_chat_search_input input')
.autocomplete({
source: function(request, response) {
chat_manager.search_partner(request.term).done(response);
chat_manager.search_partner(request.term, 10).done(response);
},
select: function(event, ui) {
self.trigger('open_dm_session', ui.item.id);
@@ -352,6 +352,7 @@
padding: 5px;
cursor: pointer;
font-size: 12px;
overflow: hidden;
&~.o_mail_channel_preview {
border-top: 1px solid lightgray;
}
+8 -1
View File
@@ -97,6 +97,9 @@
.o_thread_message_core {
.o-flex(1, 1, auto);
min-width: 0;
max-width: 100%;
word-wrap: break-word;
&.o_mail_note {
background-color: @gray-lighter;
@@ -115,12 +118,16 @@
display: none;
}
> p {
p {
margin: 0 0 9px; // Required by the old design to override a general rule on p's
&:last-child {
margin-bottom: 0;
}
}
a {
display: inline-block;
word-break: break-all;
}
.o_mail_body_long {
display: none;
+1 -1
View File
@@ -12,7 +12,7 @@
<div class="o_chat_content">
</div>
<div class="o_chat_input">
<input t-att-placeholder="widget.placeholder"/>
<input t-att-placeholder="widget.options.placeholder"/>
</div>
</div>
</t>
+1 -1
View File
@@ -124,7 +124,7 @@
<button type="button" class="btn btn-primary btn-sm o_mail_chat_button_detach hidden-xs" title="Detach the conversation">Detach</button>
<button type="button" class="btn btn-primary btn-sm o_mail_chat_button_mark_read" title="Mark all as read">Mark all read</button>
<button type="button" class="btn btn-primary btn-sm o_mail_chat_button_unstar_all" title="Unstar all messages">Unstar all</button>
<button type="button" class="btn btn-default btn-sm o_mail_chat_button_new_message" title="Compose a new message">New message</button>
<button type="button" class="btn btn-default btn-sm o_mail_chat_button_new_message" title="Compose a new message">Send mail</button>
<button type="button" class="btn btn-default btn-sm o_mail_chat_button_unsubscribe" title="Unsubscribe from channel">Unsubscribe</button>
<button type="button" class="btn btn-default btn-sm o_mail_chat_button_settings" title="Open channel settings">Settings</button>
</div>
@@ -38,6 +38,8 @@ class MailMailStats(osv.Model):
if not self.pool.get(stat.model):
continue
target = self.pool[stat.model].browse(cr, uid, stat.res_id, context=context)
if not target or not target.exists():
continue
email = ''
for email_field in ('email', 'email_from'):
if email_field in target and target[email_field]:
@@ -856,6 +856,12 @@ msgstr ""
msgid "Cashdrawer"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:1604
#, python-format
msgid "Cashier"
msgstr ""
#. module: point_of_sale
#: model:ir.actions.act_window,help:point_of_sale.product_pos_category_action
msgid "Categories are used to browse your products through the\n"
@@ -1371,6 +1377,12 @@ msgstr ""
msgid "Discount:"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:1602
#, python-format
msgid "Discounted Product"
msgstr ""
#. module: point_of_sale
#. openerp-web
#: code:addons/point_of_sale/static/src/xml/pos.xml:791
@@ -2830,6 +2842,12 @@ msgstr ""
msgid "Price"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:1601
#, python-format
msgid "Priced Product"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,field_description:point_of_sale.field_pos_config_pricelist_id
#: model:ir.model.fields,field_description:point_of_sale.field_pos_order_pricelist_id
@@ -4129,6 +4147,12 @@ msgstr ""
msgid "Warehouse"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:1600
#, python-format
msgid "Weighted Product"
msgstr ""
#. module: point_of_sale
#. openerp-web
#: code:addons/point_of_sale/static/src/xml/pos.xml:1180
+5 -5
View File
@@ -1594,10 +1594,10 @@ class barcode_rule(models.Model):
def _get_type_selection(self):
types = sets.Set(super(barcode_rule,self)._get_type_selection())
types.update([
('weight','Weighted Product'),
('price','Priced Product'),
('discount','Discounted Product'),
('client','Client'),
('cashier','Cashier')
('weight', _('Weighted Product')),
('price', _('Priced Product')),
('discount', _('Discounted Product')),
('client', _('Client')),
('cashier', _('Cashier'))
])
return list(types)
@@ -885,7 +885,20 @@ td {
display: inline-block;
margin: 9px 5px;
}
.pos .category-list-scroller{
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
width:100%;
height:100%;
max-height:60vh;
overflow: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
-webkit-transform: translate3d(0,0,0);
}
.pos .category-list {
text-align: left;
padding: 10px;
+31 -1
View File
@@ -443,6 +443,8 @@ var Chrome = PosBaseWidget.extend({
this.logo_click_time = 0;
this.logo_click_count = 0;
this.previous_touch_y_coordinate = -1;
this.widget = {}; // contains references to subwidgets instances
this.cleanup_dom();
@@ -537,12 +539,40 @@ var Chrome = PosBaseWidget.extend({
}
},
_scrollable: function(element, scrolling_down){
var $element = $(element);
var scrollable = true;
if (! scrolling_down && $element.scrollTop() <= 0) {
scrollable = false;
} else if (scrolling_down && $element.scrollTop() + $element.height() >= element.scrollHeight) {
scrollable = false;
}
return scrollable;
},
disable_rubberbanding: function(){
var self = this;
document.body.addEventListener('touchstart', function(event){
self.previous_touch_y_coordinate = event.touches[0].clientY;
});
// prevent the pos body from being scrollable.
document.body.addEventListener('touchmove',function(event){
var node = event.target;
var current_touch_y_coordinate = event.touches[0].clientY;
var scrolling_down;
if (current_touch_y_coordinate < self.previous_touch_y_coordinate) {
scrolling_down = true;
} else {
scrolling_down = false;
}
while(node){
if(node.classList && node.classList.contains('touch-scrollable')){
if(node.classList && node.classList.contains('touch-scrollable') && self._scrollable(node, scrolling_down)){
return;
}
node = node.parentNode;
+3 -1
View File
@@ -194,7 +194,9 @@
</header>
<t t-if="widget.subcategories.length > 0">
<div class="categories">
<div class="category-list">
<div class="category-list-scroller touch-scrollable">
<div class="category-list">
</div>
</div>
</div>
</t>
+6
View File
@@ -151,6 +151,12 @@ msgstr ""
msgid "Created on"
msgstr ""
#. module: pos_mercury
#: code:addons/pos_mercury/models/pos_mercury.py:17
#, python-format
msgid "Credit Card"
msgstr ""
#. module: pos_mercury
#. openerp-web
#: code:addons/pos_mercury/static/src/js/pos_mercury.js:177
+2 -2
View File
@@ -4,7 +4,7 @@
import logging
import sets
from openerp import models, fields, api
from openerp import models, fields, api, _
_logger = logging.getLogger(__name__)
@@ -14,7 +14,7 @@ class barcode_rule(models.Model):
def _get_type_selection(self):
types = sets.Set(super(barcode_rule, self)._get_type_selection())
types.update([
('credit', 'Credit Card')
('credit', _('Credit Card'))
])
return list(types)
+4
View File
@@ -757,6 +757,9 @@ class ProcurementOrder(models.Model):
taxes = self.product_id.supplier_taxes_id
fpos = po.fiscal_position_id
taxes_id = fpos.map_tax(taxes).ids if fpos else []
if taxes_id:
companies = self.env['res.company'].search([('id', 'child_of', self.company_id.id)])
taxes_id = taxes_id.filtered(lambda x: x.company_id.id in companies).ids
price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, self.product_id.supplier_taxes_id, taxes_id) if seller else 0.0
if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id:
@@ -802,6 +805,7 @@ class ProcurementOrder(models.Model):
'partner_id': partner.id,
'picking_type_id': self.rule_id.picking_type_id.id,
'company_id': self.company_id.id,
'currency_id': partner.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id,
'dest_address_id': self.partner_dest_id.id,
'origin': self.origin,
'payment_term_id': partner.property_supplier_payment_term_id.id,
+12
View File
@@ -2926,6 +2926,7 @@ msgid "Localization"
msgstr ""
#. module: stock
#: code:addons/stock/stock.py:4912
#: model:ir.model.fields,field_description:stock.field_product_product_location_id
#: model:ir.model.fields,field_description:stock.field_product_template_location_id
#: model:ir.model.fields,field_description:stock.field_stock_change_product_qty_location_id
@@ -2939,6 +2940,7 @@ msgstr ""
#: model:ir.ui.view,arch_db:stock.quant_search_view
#: model:ir.ui.view,arch_db:stock.view_move_search
#: model:ir.ui.view,arch_db:stock.warehouse_orderpoint_search
#, python-format
msgid "Location"
msgstr ""
@@ -2997,6 +2999,7 @@ msgid "Logistics"
msgstr ""
#. module: stock
#: code:addons/stock/stock.py:4913
#: model:ir.model.fields,field_description:stock.field_stock_move_restrict_lot_id
#: model:ir.model.fields,field_description:stock.field_stock_move_scrap_restrict_lot_id
#: model:ir.model.fields,field_description:stock.field_stock_quant_lot_id
@@ -3005,6 +3008,7 @@ msgstr ""
#: model:ir.ui.view,arch_db:stock.report_lot_barcode
#: model:ir.ui.view,arch_db:stock.report_package_barcode
#: model:ir.ui.view,arch_db:stock.report_picking
#, python-format
msgid "Lot"
msgstr ""
@@ -3748,11 +3752,13 @@ msgid "Pack Type"
msgstr ""
#. module: stock
#: code:addons/stock/stock.py:4914
#: model:ir.model.fields,field_description:stock.field_stock_quant_package_id
#: model:ir.ui.view,arch_db:stock.quant_package_search_view
#: model:ir.ui.view,arch_db:stock.quant_search_view
#: model:ir.ui.view,arch_db:stock.view_quant_package_form
#: model:ir.ui.view,arch_db:stock.view_quant_package_tree
#, python-format
msgid "Package"
msgstr ""
@@ -5800,6 +5806,12 @@ msgstr ""
msgid "USPS integration"
msgstr ""
#. module: stock
#: code:addons/stock/stock.py:730
#, python-format
msgid "Under no circumstances should you delete or change quants yourselves!"
msgstr ""
#. module: stock
#: model:ir.model.fields,help:stock.field_stock_production_lot_name
msgid "Unique Serial Number"
+39 -13
View File
@@ -1636,7 +1636,7 @@ class stock_picking(models.Model):
"""
if not context:
context = {}
notrack_context = dict(context, mail_notrack=True)
stock_move_obj = self.pool.get('stock.move')
self.create_lots_for_picking(cr, uid, ids, context=context)
for picking in self.browse(cr, uid, ids, context=context):
@@ -1664,7 +1664,7 @@ class stock_picking(models.Model):
todo_move_ids.append(move.id)
elif float_compare(remaining_qty,0, precision_rounding = move.product_id.uom_id.rounding) > 0 and \
float_compare(remaining_qty, move.product_qty, precision_rounding = move.product_id.uom_id.rounding) < 0:
new_move = stock_move_obj.split(cr, uid, move, remaining_qty, context=context)
new_move = stock_move_obj.split(cr, uid, move, remaining_qty, context=notrack_context)
todo_move_ids.append(move.id)
#Assign move as it was assigned before
toassign_move_ids.append(new_move)
@@ -1674,7 +1674,7 @@ class stock_picking(models.Model):
self.rereserve_quants(cr, uid, picking, move_ids=todo_move_ids, context=context)
self.do_recompute_remaining_quantities(cr, uid, [picking.id], context=context)
if todo_move_ids and not context.get('do_only_split'):
self.pool.get('stock.move').action_done(cr, uid, todo_move_ids, context=context)
self.pool.get('stock.move').action_done(cr, uid, todo_move_ids, context=notrack_context)
elif context.get('do_only_split'):
context = dict(context, split=todo_move_ids)
self._create_backorder(cr, uid, picking, context=context)
@@ -2103,14 +2103,29 @@ class stock_move(osv.osv):
self.pool['procurement.order'].run(cr, uid, res, context=context)
return res
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
picking_obj = self.pool['stock.picking']
track = not context.get('mail_notrack') and vals.get('picking_id')
if track:
picking = picking_obj.browse(cr, uid, vals['picking_id'], context=context)
initial_values = {picking.id: {'state': picking.state}}
res = super(stock_move, self).create(cr, uid, vals, context=context)
if track:
picking_obj.message_track(cr, uid, [vals['picking_id']], picking_obj.fields_get(cr, uid, ['state'], context=context), initial_values, context=context)
return res
def write(self, cr, uid, ids, vals, context=None):
if context is None:
context = {}
if isinstance(ids, (int, long)):
ids = [ids]
picking_obj = self.pool['stock.picking']
# Check that we do not modify a stock.move which is done
frozen_fields = set(['product_qty', 'product_uom', 'location_id', 'location_dest_id', 'product_id'])
for move in self.browse(cr, uid, ids, context=context):
moves = self.browse(cr, uid, ids, context=context)
for move in moves:
if move.state == 'done':
if frozen_fields.intersection(vals):
raise UserError(_('Quantities, Units of Measure, Products and Locations cannot be modified on stock moves that have already been processed (except by the Administrator).'))
@@ -2147,7 +2162,18 @@ class stock_move(osv.osv):
#Note that, for pulled moves we intentionally don't propagate on the procurement.
if propagated_changes_dict:
self.write(cr, uid, [move.move_dest_id.id], propagated_changes_dict, context=context)
return super(stock_move, self).write(cr, uid, ids, vals, context=context)
track_pickings = not context.get('mail_notrack') and any(field in vals for field in ['state', 'picking_id', 'partially_available'])
if track_pickings:
to_track_picking_ids = set([move.picking_id.id for move in moves if move.picking_id])
if vals.get('picking_id'):
to_track_picking_ids.add(vals['picking_id'])
to_track_picking_ids = list(to_track_picking_ids)
pickings = picking_obj.browse(cr, uid, to_track_picking_ids, context=context)
initial_values = dict((picking.id, {'state': picking.state}) for picking in pickings)
res = super(stock_move, self).write(cr, uid, ids, vals, context=context)
if track_pickings:
picking_obj.message_track(cr, uid, to_track_picking_ids, picking_obj.fields_get(cr, uid, ['state'], context=context), initial_values, context=context)
return res
def onchange_quantity(self, cr, uid, ids, product_id, product_qty, product_uom):
""" On change of product quantity finds UoM
@@ -2306,7 +2332,7 @@ class stock_move(osv.osv):
for state, write_ids in states.items():
if len(write_ids):
self.write(cr, uid, write_ids, {'state': state})
self.write(cr, uid, write_ids, {'state': state}, context=context)
#assign picking in batch for all confirmed move that share the same details
for key, move_ids in to_assign.items():
self._picking_assign(cr, uid, move_ids, context=context)
@@ -2443,7 +2469,7 @@ class stock_move(osv.osv):
"""
procurement_obj = self.pool.get('procurement.order')
context = context or {}
procs_to_check = []
procs_to_check = set()
for move in self.browse(cr, uid, ids, context=context):
if move.state == 'done':
raise UserError(_('You cannot cancel a stock move that has been set to \'Done\'.'))
@@ -2462,11 +2488,11 @@ class stock_move(osv.osv):
self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}, context=context)
if move.procurement_id:
# Does the same as procurement check, only eliminating a refresh
procs_to_check.append(move.procurement_id.id)
procs_to_check.add(move.procurement_id.id)
res = self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}, context=context)
if procs_to_check:
procurement_obj.check(cr, uid, procs_to_check, context=context)
procurement_obj.check(cr, uid, list(procs_to_check), context=context)
return res
def _check_package_from_moves(self, cr, uid, ids, context=None):
@@ -4908,10 +4934,10 @@ class barcode_rule(models.Model):
def _get_type_selection(self):
types = sets.Set(super(barcode_rule,self)._get_type_selection())
types.update([
('weight','Weighted Product'),
('location','Location'),
('lot','Lot'),
('package','Package')
('weight', _('Weighted Product')),
('location', _('Location')),
('lot', _('Lot')),
('package', _('Package'))
])
return list(types)
@@ -50,7 +50,7 @@ class stock_history(osv.osv):
if 'inventory_value' in fields:
group_lines = {}
for line in res:
domain = line.get('__domain', [])
domain = line.get('__domain', domain)
group_lines.setdefault(str(domain), self.search(cr, uid, domain, context=context))
line_ids = set()
for ids in group_lines.values():
@@ -75,7 +75,7 @@ class stock_history(osv.osv):
histories_dict[(history['product_id'], history['company_id'])] = history['cost']
for line in res:
inv_value = 0.0
lines = group_lines.get(str(line.get('__domain', [])))
lines = group_lines.get(str(line.get('__domain', domain)))
for line_id in lines:
line_rec = lines_dict[line_id]
product = products_dict[line_rec['product_id']]
+1 -1
View File
@@ -357,7 +357,7 @@ class survey_survey(osv.Model):
for cell in product(rows.keys(), answers.keys()):
res[cell] = 0
for input_line in question.user_input_line_ids:
if input_line.answer_type == 'suggestion' and (not(current_filters) or input_line.user_input_id.id in current_filters):
if input_line.answer_type == 'suggestion' and (not(current_filters) or input_line.user_input_id.id in current_filters) and input_line.value_suggested_row:
res[(input_line.value_suggested_row.id, input_line.value_suggested.id)] += 1
if input_line.answer_type == 'text' and (not(current_filters) or input_line.user_input_id.id in current_filters):
comments.append(input_line)
@@ -17,6 +17,10 @@
weekdays : "Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag".split("_"),
weekdaysShort : "Son_Maa_Din_Woe_Don_Vry_Sat".split("_"),
weekdaysMin : "So_Ma_Di_Wo_Do_Vr_Sa".split("_"),
meridiemParse: /vm|nm/i,
isPM : function (input) {
return /^nm$/i.test(input);
},
meridiem : function (hours, minutes, isLower) {
if (hours < 12) {
return isLower ? 'vm' : 'VM';
+5 -1
View File
@@ -48,6 +48,10 @@
LLL : "D MMMM YYYY LT",
LLLL : "dddd D MMMM YYYY LT"
},
meridiemParse: /ص|م/,
isPM : function (input) {
return 'م' === input;
},
meridiem : function (hour, minute, isLower) {
if (hour < 12) {
return "ص";
@@ -79,7 +83,7 @@
yy : "%d سنوات"
},
preparse: function (string) {
return string.replace(/[۰-۹]/g, function (match) {
return string.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) {
return numberMap[match];
}).replace(/،/g, ',');
},
+5 -1
View File
@@ -49,6 +49,10 @@
LLL : "D MMMM YYYY LT",
LLLL : "dddd D MMMM YYYY LT"
},
meridiemParse: /ص|م/,
isPM : function (input) {
return 'م' === input;
},
meridiem : function (hour, minute, isLower) {
if (hour < 12) {
return "ص";
@@ -80,7 +84,7 @@
yy : "%d سنوات"
},
preparse: function (string) {
return string.replace(/[۰-۹]/g, function (match) {
return string.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) {
return numberMap[match];
}).replace(/،/g, ',');
},
@@ -72,6 +72,10 @@
y : "bir il",
yy : "%d il"
},
meridiemParse: /gecə|səhər|gündüz|axşam/,
isPM : function (input) {
return /^(gündüz|axşam)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 4) {
return "gecə";
+4 -2
View File
@@ -113,8 +113,10 @@
y : "год",
yy : relativeTimeWithPlural
},
meridiemParse: /ночы|раніцы|дня|вечара/,
isPM : function (input) {
return /^(дня|вечара)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 4) {
return "ночы";
@@ -82,6 +82,10 @@
return symbolMap[match];
});
},
meridiemParse: /রাত|শকাল|দুপুর|বিকেল|রাত/,
isPM: function (input) {
return /^(দুপুর|বিকেল|রাত)$/.test(input);
},
//Bengali is a vast language its spoken
//in different forms in various parts of the world.
//I have just generalized with most common one used
@@ -82,6 +82,10 @@
return symbolMap[match];
});
},
meridiemParse: /མཚན་མོ|ཞོགས་ཀས|ཉིན་གུང|དགོང་དག|མཚན་མོ/,
isPM: function (input) {
return /^(ཉིན་གུང|དགོང་དག|མཚན་མོ)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 4) {
return "མཚན་མོ";
@@ -26,6 +26,10 @@
LLL : "D[-an de] MMMM, YYYY LT",
LLLL : "dddd, [la] D[-an de] MMMM, YYYY LT"
},
meridiemParse: /[ap]\.t\.m/i,
isPM: function (input) {
return input.charAt(0).toLowerCase() === 'p';
},
meridiem : function (hours, minutes, isLower) {
if (hours > 11) {
return isLower ? 'p.t.m.' : 'P.T.M.';
@@ -48,6 +48,10 @@
LLL : 'D MMMM YYYY LT',
LLLL : 'dddd, D MMMM YYYY LT'
},
meridiemParse: /قبل از ظهر|بعد از ظهر/,
isPM: function (input) {
return /بعد از ظهر/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 12) {
return "قبل از ظهر";
@@ -84,6 +84,11 @@
},
// Hindi notation for meridiems are quite fuzzy in practice. While there exists
// a rigid notion of a 'Pahar' it is not used as rigidly in modern Hindi.
meridiemParse: /रात|सुबह|दोपहर|शाम|रात/,
isPM: function (input) {
// TODO: This is incorrect (look at cutoffs). We need a better isPM interface.
return /^(दोपहर|शाम|रात)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 4) {
return "रात";
@@ -62,6 +62,10 @@
LLL : "YYYY. MMMM D., LT",
LLLL : "YYYY. MMMM D., dddd LT"
},
meridiemParse: /de|du/i,
isPM: function (input) {
return input.charAt(1).toLowerCase() === 'u';
},
meridiem : function (hours, minutes, isLower) {
if (hours < 12) {
return isLower === true ? 'de' : 'DE';
@@ -77,6 +77,10 @@
yy : "%d տարի"
},
meridiemParse: /գիշերվա|առավոտվա|ցերեկվա|երեկոյան/,
isPM: function (input) {
return /^(ցերեկվա|երեկոյան)$/.test(input);
},
meridiem : function (hour) {
if (hour < 4) {
return "գիշերվա";
@@ -25,6 +25,11 @@
LLL : "D MMMM YYYY [pukul] LT",
LLLL : "dddd, D MMMM YYYY [pukul] LT"
},
meridiemParse: /pagi|siang|sore|malam/,
isPM: function (input) {
// TODO: This is incorrect (look at cutoffs).
return /^(siang|sore|malam)$/.test(input);
},
meridiem : function (hours, minutes, isLower) {
if (hours < 11) {
return 'pagi';
@@ -24,6 +24,10 @@
LLL : "YYYY年M月D日LT",
LLLL : "YYYY年M月D日LT dddd"
},
meridiemParse: /午前|午後/i,
isPM : function (input) {
return input === '午後';
},
meridiem : function (hour, minute, isLower) {
if (hour < 12) {
return "午前";
+4 -4
View File
@@ -27,9 +27,6 @@
LLL : "YYYY년 MMMM D일 LT",
LLLL : "YYYY년 MMMM D일 dddd LT"
},
meridiem : function (hour, minute, isUpper) {
return hour < 12 ? '오전' : '오후';
},
calendar : {
sameDay : '오늘 LT',
nextDay : '내일 LT',
@@ -55,9 +52,12 @@
yy : "%d년"
},
ordinal : '%d일',
meridiemParse : /(오전|오후)/,
meridiemParse : /오전|오후/,
isPM : function (token) {
return token === "오후";
},
meridiem : function (hour, minute, isUpper) {
return hour < 12 ? '오전' : '오후';
}
});
}));
@@ -47,6 +47,10 @@
y : "ഒരു വർഷം",
yy : "%d വർഷം"
},
meridiemParse: /രാത്രി|രാവിലെ|ഉച്ച കഴിഞ്ഞ്|വൈകുന്നേരം|രാത്രി/i,
isPM : function (input) {
return /^(ഉച്ച കഴിഞ്ഞ്|വൈകുന്നേരം|രാത്രി)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 4) {
return "രാത്രി";
@@ -82,6 +82,11 @@
return symbolMap[match];
});
},
meridiemParse: /रात्री|सकाळी|दुपारी|सायंकाळी|रात्री/,
isPM : function (input) {
// TODO: This is wrong.
return /^(दुपारी|सायंकाळी|रात्री)$/.test(input);
},
meridiem: function (hour, minute, isLower)
{
if (hour < 4) {
@@ -24,6 +24,11 @@
LLL : "D MMMM YYYY [pukul] LT",
LLLL : "dddd, D MMMM YYYY [pukul] LT"
},
meridiemParse: /pagi|tengahari|petang|malam/,
isPM: function (input) {
// TODO: This is wrong.
return /^(tengahari|petang|malam)$/.test(input);
},
meridiem : function (hours, minutes, isLower) {
if (hours < 11) {
return 'pagi';
@@ -59,6 +59,11 @@
return symbolMap[match];
});
},
meridiemParse: /राती|बिहान|दिउँसो|बेलुका|साँझ|राती/,
isPM : function (input) {
// TODO: This is wrong.
return /^(दिउँसो|बेलुका|साँझ|राती)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 3) {
return "राती";
@@ -19,6 +19,10 @@
weekdays : "E Diel_E Hënë_E Martë_E Mërkurë_E Enjte_E Premte_E Shtunë".split("_"),
weekdaysShort : "Die_Hën_Mar_Mër_Enj_Pre_Sht".split("_"),
weekdaysMin : "D_H_Ma_Më_E_P_Sh".split("_"),
meridiemParse: /PD|MD/,
isPM: function (input) {
return input.charAt(0) === 'M';
},
meridiem : function (hours, minutes, isLower) {
return hours < 12 ? 'PD' : 'MD';
},
@@ -89,6 +89,8 @@
// refer http://ta.wikipedia.org/s/1er1
// TODO: This is pretty wrong (when hour is equal to 6 10, 14, 18, 20,
// 24 (0). Also it doesn't split at 12 (noon).
meridiem : function (hour, minute, isLower) {
if (hour >= 6 && hour <= 10) {
return " காலை";
@@ -24,6 +24,10 @@
LLL : "D MMMM YYYY เวลา LT",
LLLL : "วันddddที่ D MMMM YYYY เวลา LT"
},
meridiemParse: /ก่อนเที่ยง|หลังเที่ยง/,
isPM: function (input) {
return input === 'หลังเที่ยง';
},
meridiem : function (hour, minute, isLower) {
if (hour < 12) {
return "ก่อนเที่ยง";
@@ -122,6 +122,10 @@
// M. E.: those two are virtually unused but a user might want to implement them for his/her website for some reason
meridiemParse: /ночі|ранку|дня|вечора/,
isPM: function (input) {
return /^(дня|вечора)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 4) {
return "ночі";
@@ -29,6 +29,11 @@
lll : "YYYY年MMMD日LT",
llll : "YYYY年MMMD日ddddLT"
},
meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
isPM: function (input) {
// TODO: This is wrong.
return /^(中午|下午|晚上)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
var hm = hour * 100 + minute;
if (hm < 600) {
@@ -28,6 +28,11 @@
lll : "YYYY年MMMD日LT",
llll : "YYYY年MMMD日ddddLT"
},
meridiemParse: /早上|上午|中午|下午|晚上/,
isPM: function (input) {
// TODO: This is wrong.
return /^(中午|下午|晚上)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
var hm = hour * 100 + minute;
if (hm < 900) {
+6 -8
View File
@@ -295,15 +295,13 @@ QWeb2.Engine = (function() {
}
req.open('GET', s, async);
if (async) {
req.onreadystatechange = function() {
if (req.readyState == 4) {
if (req.status == 200) {
callback(null, self._parse_from_request(req));
} else {
callback(new Error("Can't load template " + s + ", http status " + req.status));
}
req.addEventListener("load", function() {
if (req.status == 200) {
callback(null, self._parse_from_request(req));
} else {
callback(new Error("Can't load template " + s + ", http status " + req.status));
}
};
});
}
req.send(null);
if (!async) {
@@ -1160,6 +1160,9 @@ var One2ManyListView = X2ManyListView.extend({
return this._super();
},
_on_focus_one2many: function () {
if(!this.editor.is_editing()) {
return;
}
this.dataset.x2m.internal_dataset_changed = true;
this._dataset_changed = false;
this.__focus = true;
+7 -3
View File
@@ -17,7 +17,6 @@ var View = require('web.View');
var _lt = core._lt;
var _t = core._t;
var QWeb = core.qweb;
var total = _t("Total");
var PivotView = View.extend({
template: 'PivotView',
@@ -184,7 +183,7 @@ var PivotView = View.extend({
}
}
});
this.measures.__count__ = {string: "Quantity", type: "integer"};
this.measures.__count__ = {string: _t("Quantity"), type: "integer"};
},
do_search: function (domain, context, group_by) {
if (!this.ready) {
@@ -509,6 +508,7 @@ var PivotView = View.extend({
return name + (numbers[id] > 1 ? " (" + numbers[id] + ")" : "");
},
make_header: function (data_pt, root, i, j, parent_header) {
var total = _t("Total");
var attrs = data_pt.attributes,
value = attrs.value,
title = value.length ? value[value.length - 1] : total;
@@ -537,6 +537,7 @@ var PivotView = View.extend({
},
get_header: function (data_pt, root, i, j, parent) {
var path;
var total = _t("Total");
if (parent) {
path = parent.path.concat(data_pt.attributes.value.slice(i,j));
} else {
@@ -798,6 +799,10 @@ var PivotView = View.extend({
nbr_measures: nbr_measures,
title: this.title,
};
if(table.measure_row.length + 1 > 256) {
c.show_message(_t("For Excel compatibility, data cannot be exported if there is more than 256 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures."));
return;
}
session.get_file({
url: '/web/pivot/export_xls',
data: {data: JSON.stringify(table)},
@@ -834,4 +839,3 @@ core.view_registry.add('pivot', PivotView);
return PivotView;
});
+1 -1
View File
@@ -53,7 +53,7 @@ var QuickCreate = Dialog.extend({
return _t("Create");
}
var title = (_.isUndefined(parent.field_widget)) ?
(parent.string || parent.name) :
(parent.title || parent.string || parent.name) :
(parent.field_widget.string || parent.field_widget.name || '');
return _t("Create: ") + title;
},
@@ -164,7 +164,7 @@ var KanbanColumn = Widget.extend({
},
update_column: function () {
var title = this.folded ? this.title + ' (' + this.records.length + ')' : this.title;
var title = this.folded ? this.title + ' (' + this.dataset.size() + ')' : this.title;
this.$header.find('.o_column_title').text(title);
this.$header.find('.o-kanban-count').text(this.records.length);
@@ -437,6 +437,7 @@ var KanbanView = View.extend({
this.$el.sortable({
axis: 'x',
items: '> .o_kanban_group',
handle: '.o_kanban_header',
cursor: 'move',
revert: 150,
delay: 100,
@@ -83,8 +83,8 @@ base.ready().done(function () {
},
},
{
title: "Check that the subtotal is 3,856.96",
element: '#order_total_untaxed .oe_currency_value:contains("3,856.96")',
title: "Check that the subtotal is 5,500.00",
element: '#order_total_untaxed .oe_currency_value:contains("5,500.00")',
},
{
title: "Select `Wire Transfer` payment method",
+1 -1
View File
@@ -483,7 +483,7 @@ class Post(models.Model):
elif post.state == 'pending' and not post.parent_id:
# TDE FIXME: in master, you should probably use a subtype;
# however here we remove subtype but set partner_ids
partners = post.sudo().message_partner_ids.filtered(lambda partner: partner.user_ids and partner.user_ids.karma >= post.forum_id.karma_moderate)
partners = post.sudo().message_partner_ids.filtered(lambda partner: partner.user_ids and any(user.karma >= post.forum_id.karma_moderate for user in partner.user_ids))
note_subtype = self.sudo().env.ref('mail.mt_note')
body = _('<p>A new question <i>%s</i> has been asked on %s and require your validation. <a href="%s/forum/%s/question/%s">Click here to access the question.</a></p>') % \
(post.name, post.forum_id.name, base_url, slug(post.forum_id), slug(post))
+1 -1
View File
@@ -24,7 +24,7 @@ class PaymentTransaction(orm.Model):
tx = getattr(self, tx_find_method_name)(cr, uid, data, context=context)
if tx and tx.state == 'done' and tx.acquirer_id.auto_confirm == 'at_pay_confirm' and tx.sale_order_id and tx.sale_order_id.state in ['draft', 'sent']:
self.pool['sale.order'].action_confirm(cr, SUPERUSER_ID, [tx.sale_order_id.id], context=dict(context, send_email=True))
elif tx and tx.state not in ['cancel'] and tx.sale_order_id and tx.sale_order_id.state in ['draft']:
elif tx and tx.state not in ['cancel', 'error'] and tx.sale_order_id and tx.sale_order_id.state in ['draft']:
self.pool['sale.order'].force_quotation_send(cr, SUPERUSER_ID, [tx.sale_order_id.id], context=context)
return res
+32 -11
View File
@@ -199,9 +199,8 @@ class website(orm.Model):
:returns: pricelist recordset
"""
isocountry = request.session.geoip and request.session.geoip.get('country_code') or False
user_id = self.pool['res.users'].browse(cr, uid, request.uid or uid, context=context)
pl_ids = self._get_pl(cr, uid, isocountry, show_visible,
user_id.partner_id.property_product_pricelist.id,
request.website.user_id.sudo().partner_id.property_product_pricelist.id,
request.session.get('website_sale_current_pl'),
request.website.website_pricelist_ids)
return self.pool['product.pricelist'].browse(cr, uid, pl_ids, context=context)
@@ -220,16 +219,33 @@ class website(orm.Model):
"""
:returns: The current pricelist record
"""
pl_id = request.session.get('website_sale_current_pl')
if pl_id:
return self.pool['product.pricelist'].browse(cr, uid, [pl_id], context=context)[0]
# The list of available pricelists for this user.
# If the user is signed in, and has a pricelist set different than the public user pricelist
# then this pricelist will always be considered as available
available_pricelists = self.get_pricelist_available(cr, uid, context=context)
if request.session.get('website_sale_current_pl'):
# `website_sale_current_pl` is set only if the user specifically chose it:
# - Either, he chose it from the pricelist selection
# - Either, he entered a coupon code
return self.pool['product.pricelist'].browse(cr, uid, [request.session['website_sale_current_pl']], context=context)[0]
else:
available_pricelists = self.get_pricelist_available(cr, uid, context=context)
if available_pricelists:
partner = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id
# If the user has a saved cart, it take the pricelist of this cart, except if
# the order is no longer draft (It has already been confirmed, or cancelled, ...)
pl = partner.last_website_so_id and partner.last_website_so_id.state == 'draft' and partner.last_website_so_id.pricelist_id
if not pl:
# The pricelist of the user set on its partner form.
# If the user is not signed in, it's the public user pricelist
pl = partner.property_product_pricelist
if available_pricelists and pl not in available_pricelists:
# If there is at least one pricelist in the available pricelists
# and the chosen pricelist is not within them
# it then choose the first available pricelist.
# This can only happen when the pricelist is the public user pricelist and this pricelist is not in the available pricelist for this localization
# If the user is signed in, and has a special pricelist (different than the public user pricelist),
# then this special pricelist is amongs these available pricelists, and therefore it won't fall in this case.
pl = available_pricelists[0]
else:
pl = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.property_product_pricelist
request.session['website_sale_current_pl'] = pl.id
return pl
def sale_product_domain(self, cr, uid, ids, context=None):
@@ -251,7 +267,12 @@ class website(orm.Model):
"""
partner = self.get_partner(cr, uid)
sale_order_obj = self.pool['sale.order']
sale_order_id = request.session.get('sale_order_id') or (partner.last_website_so_id.id if partner.last_website_so_id and partner.last_website_so_id.state == 'draft' else False)
sale_order_id = request.session.get('sale_order_id')
if not sale_order_id:
last_order = partner.last_website_so_id
available_pricelists = self.get_pricelist_available(cr, uid, context=context)
# Do not reload the cart of this user last visit if the cart is no longer draft or uses a pricelist no longer available.
sale_order_id = last_order and last_order.state == 'draft' and last_order.pricelist_id in available_pricelists and last_order.id
sale_order = None
# Test validity of the sale_order_id
@@ -74,8 +74,8 @@
<field name="tag_ids" eval="[(4, ref('tag1'))]"/>
</record>
<record id="slide_5" model="slide.slide">
<field name="name">Odoo POS Indiegogo Campaign</field>
<field name="url">https://www.youtube.com/watch?v=C_Laaz4mORg</field>
<field name="name">Awesome Timesheet by Odoo</field>
<field name="url">https://www.youtube.com/watch?v=v2O0YZU19ms</field>
<field name="channel_id" ref="channel_public"/>
<field name="category_id" ref="category_1"/>
<field name="website_published" eval="True"/>
+4 -1
View File
@@ -485,7 +485,10 @@ class Slide(models.Model):
return fetch_res
values = {'slide_type': 'video', 'document_id': document_id}
youtube_values = fetch_res['values'].get('items', list(dict()))[0]
items = fetch_res['values'].get('items')
if not items:
return {'error': _('Please enter valid Youtube or Google Doc URL')}
youtube_values = items[0]
if youtube_values.get('snippet'):
snippet = youtube_values['snippet']
if only_preview_fields:
+1
View File
@@ -10,3 +10,4 @@ Setting Up
setup/install
setup/deploy
setup/cdn
setup/enterprise
+110
View File
@@ -0,0 +1,110 @@
============================
From Community to Enterprise
============================
Depending on your current installation, there are multiple ways to upgrade
your community version.
In any case the basic guidelines are:
* Backup your community database
.. image:: enterprise/db_manager.png
:class: img-responsive
* Shutdown your server
* Upgrade the web module
* Restart your server
* Enter your Odoo Enterprise Subscription code
.. image:: enterprise/enterprise_code.png
:class: img-responsive
On Linux, using an installer
============================
* Backup your community database
* Stop the odoo service
.. code-block:: console
$ sudo service odoo stop
* Install the enterprise .deb (it should install over the community package)
.. code-block:: console
$ sudo dpkg -i <path_to_enterprise_deb>
* Update your database to the enterprise packages using
.. code-block:: console
$ python /usr/bin/odoo.py -d <database_name> -u web --stop-after-init
* You should be able to connect to your Odoo Enterprise instance using your usual mean of identification.
You can then link your database with your Odoo Enterprise Subscription by entering the code you received
by e-mail in the form input
On Linux, using the source code
===============================
There are many ways to launch your server when using sources, and you probably
have your own favourite. You may need to adapt sections to your usual workflow.
* Shutdown your server
* Backup your community database
* Update the ``--addons-path`` parameter of your launch command (``init.d``, custom script,
configuration file, etc.). The Enterprise addons folder should be included" **before** the community modules.
For example:
.. code-block:: console
$ --addons-path=~/src/custom_modules,~/src/enterprise,~/src/odoo/addons
* Update the web module by using
.. code-block:: console
$ -d <database_name> -u web --stop-after-init
Depending on the size of your database, this may take some time.
* Restart your server with the updated addons path of point 3.
You should be able to connect to your instance. You can then link your database with your
Odoo Enterprise Subscription by entering the code you received by e-mail in the form input
On Windows
==========
* Backup your community database
* Uninstall Odoo Community (using the Uninstall executable in the installation folder) -
PostgreSQL will remain installed
.. image:: enterprise/windows_uninstall.png
:class: img-responsive
* Launch the Odoo Enterprise Installer and follow the steps normally. When choosing
the installation path, you can set the folder of the Community installation
(this folder still contains the PostgreSQL installation).
Uncheck ``Start Odoo`` at the end of the installation
.. image:: enterprise/windows_setup.png
:class: img-responsive
* Using a command window, update your Odoo Database using this command (from the Odoo
installation path, in the server subfolder)
.. code-block:: console
$ odoo.exe -d <database_name> -u web --stop-after-init
* No need to manually launch the server, the service is running.
You should be able to connect to your Odoo Enterprise instance using your usual
mean of identification. You can then link your database with your Odoo Enterprise
Subscription by entering the code you received by e-mail in the form input
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+1 -1
View File
@@ -198,7 +198,7 @@ class res_company(osv.osv):
self.cache_restart(cr)
return super(res_company, self).create(cr, uid, vals, context=context)
obj_partner = self.pool.get('res.partner')
partner_id = obj_partner.create(cr, uid, {'name': vals['name'], 'is_company':True, 'image': vals.get('logo', False)}, context=context)
partner_id = obj_partner.create(cr, uid, {'name': vals['name'], 'is_company':True, 'image': vals.get('logo', False), 'customer': False}, context=context)
vals.update({'partner_id': partner_id})
self.cache_restart(cr)
company_id = super(res_company, self).create(cr, uid, vals, context=context)
+1 -1
View File
@@ -1007,7 +1007,7 @@ class change_password_user(osv.TransientModel):
_description = 'Change Password Wizard User'
_columns = {
'wizard_id': fields.many2one('change.password.wizard', string='Wizard', required=True),
'user_id': fields.many2one('res.users', string='User', required=True),
'user_id': fields.many2one('res.users', string='User', required=True, ondelete='cascade'),
'user_login': fields.char('User Login', readonly=True),
'new_passwd': fields.char('New Password'),
}
+12
View File
@@ -149,6 +149,9 @@ class Message(models.Model):
size = fields.Integer(compute='_compute_size', search='_search_size')
double_size = fields.Integer(compute='_compute_double_size')
discussion_name = fields.Char(related='discussion.name')
author_partner = fields.Many2one(
'res.partner', compute='_compute_author_partner',
search='_search_author_partner')
@api.one
@api.constrains('author', 'discussion')
@@ -194,6 +197,15 @@ class Message(models.Model):
size = self.size
self.double_size = self.double_size + size
@api.one
@api.depends('author', 'author.partner_id')
def _compute_author_partner(self):
self.author_partner = author.partner_id
@api.model
def _search_author_partner(self, operator, value):
return [('author.partner_id', operator, value)]
class MixedModel(models.Model):
_name = 'test_new_api.mixed'
@@ -5,6 +5,7 @@ from datetime import date, datetime
from openerp.exceptions import AccessError
from openerp.tests import common
from openerp.tools import mute_logger
class TestNewFields(common.TransactionCase):
@@ -419,7 +420,31 @@ class TestNewFields(common.TransactionCase):
self.assertEqual(message.name, "[%s] %s" % (discussion.name, ''))
self.assertEqual(message.size, len(BODY))
def test_41_defaults(self):
@mute_logger('openerp.addons.base.ir.ir_model')
def test_41_new_related(self):
""" test the behavior of related fields on new records. """
discussion = self.env.ref('test_new_api.discussion_0')
access = self.env.ref('test_new_api.access_discussion')
# make discussions unreadable for demo user
access.write({'perm_read': False})
# create an environment for demo user
demo_env = self.env(user=self.env.ref('base.user_demo'))
self.assertEqual(demo_env.user.login, "demo")
# create a new message as demo user
values = {'discussion': discussion.id}
message = demo_env['test_new_api.message'].new(values)
self.assertEqual(message.discussion, discussion)
# read the related field discussion_name
self.assertEqual(message.discussion.env, demo_env)
self.assertEqual(message.discussion_name, discussion.name)
with self.assertRaises(AccessError):
message.discussion.name
def test_50_defaults(self):
""" test default values. """
fields = ['discussion', 'body', 'author', 'size']
defaults = self.env['test_new_api.message'].default_get(fields)
@@ -428,6 +453,13 @@ class TestNewFields(common.TransactionCase):
defaults = self.env['test_new_api.mixed'].default_get(['number'])
self.assertEqual(defaults, {'number': 3.14})
def test_50_search_many2one(self):
""" test search through a path of computed fields"""
messages = self.env['test_new_api.message'].search(
[('author_partner.name', '=', 'Demo User')])
self.assertEqual(messages, self.env.ref('test_new_api.message_0_1'))
class TestMagicFields(common.TransactionCase):
+5 -2
View File
@@ -560,8 +560,11 @@ class Field(object):
# when related_sudo, bypass access rights checks when reading values
others = records.sudo() if self.related_sudo else records
for record, other in zip(records, others):
# do not switch to another environment if record is a draft one
other, field = self.traverse_related(other if record.id else record)
if not record.id:
# draft records: copy record's cache to other's cache first
for name, value in record._cache.iteritems():
other[name] = value
other, field = self.traverse_related(other)
record[self.name] = other[field.name]
def _inverse_related(self, records):

Some files were not shown because too many files have changed in this diff Show More