[MERGE] forward port of branch 9.0 up to 4bbddc7
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 <> '/' 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>
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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']]
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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, ',');
|
||||
},
|
||||
|
||||
@@ -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ə";
|
||||
|
||||
@@ -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 "午前";
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -10,3 +10,4 @@ Setting Up
|
||||
setup/install
|
||||
setup/deploy
|
||||
setup/cdn
|
||||
setup/enterprise
|
||||
|
||||
@@ -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 |
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user