[MERGE] forward port of branch saas-8 up to 99b6cd8
This commit is contained in:
@@ -177,6 +177,16 @@ class AccountAccount(models.Model):
|
|||||||
def mark_as_reconciled(self):
|
def mark_as_reconciled(self):
|
||||||
return self.write({'last_time_entries_checked': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
|
return self.write({'last_time_entries_checked': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_open_reconcile(self):
|
||||||
|
# Open reconciliation view for this account
|
||||||
|
action_context = {'show_mode_selector': False, 'account_ids': [self.id,]}
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.client',
|
||||||
|
'tag': 'manual_reconciliation_view',
|
||||||
|
'context': action_context,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AccountJournal(models.Model):
|
class AccountJournal(models.Model):
|
||||||
_name = "account.journal"
|
_name = "account.journal"
|
||||||
|
|||||||
@@ -1162,7 +1162,7 @@ class AccountInvoiceLine(models.Model):
|
|||||||
if not self.product_id:
|
if not self.product_id:
|
||||||
fpos = self.invoice_id.fiscal_position_id
|
fpos = self.invoice_id.fiscal_position_id
|
||||||
self.invoice_line_tax_ids = fpos.map_tax(self.account_id.tax_ids).ids
|
self.invoice_line_tax_ids = fpos.map_tax(self.account_id.tax_ids).ids
|
||||||
else:
|
elif not self.price_unit:
|
||||||
self._set_taxes()
|
self._set_taxes()
|
||||||
|
|
||||||
@api.onchange('uom_id')
|
@api.onchange('uom_id')
|
||||||
|
|||||||
@@ -213,6 +213,10 @@ class AccountMove(models.Model):
|
|||||||
return [x.id for x in reversed_moves]
|
return [x.id for x in reversed_moves]
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def open_reconcile_view(self):
|
||||||
|
return self.line_ids.open_reconcile_view()
|
||||||
|
|
||||||
|
|
||||||
class AccountMoveLine(models.Model):
|
class AccountMoveLine(models.Model):
|
||||||
_name = "account.move.line"
|
_name = "account.move.line"
|
||||||
@@ -421,6 +425,7 @@ class AccountMoveLine(models.Model):
|
|||||||
# Note : this short-circuiting is better for performances, but also required
|
# Note : this short-circuiting is better for performances, but also required
|
||||||
# since postgresql doesn't implement empty list (so 'AND id in ()' is useless)
|
# since postgresql doesn't implement empty list (so 'AND id in ()' is useless)
|
||||||
return []
|
return []
|
||||||
|
res_ids = res_ids and tuple(res_ids)
|
||||||
|
|
||||||
assert res_type in ('partner', 'account')
|
assert res_type in ('partner', 'account')
|
||||||
assert account_type in ('payable', 'receivable', None)
|
assert account_type in ('payable', 'receivable', None)
|
||||||
@@ -447,21 +452,22 @@ class AccountMoveLine(models.Model):
|
|||||||
{3}
|
{3}
|
||||||
{4}
|
{4}
|
||||||
{5}
|
{5}
|
||||||
|
AND l.company_id = {6}
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT NULL
|
SELECT NULL
|
||||||
FROM account_move_line l
|
FROM account_move_line l
|
||||||
WHERE l.account_id = a.id
|
WHERE l.account_id = a.id
|
||||||
{6}
|
{7}
|
||||||
AND l.amount_residual > 0
|
AND l.amount_residual > 0
|
||||||
)
|
)
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT NULL
|
SELECT NULL
|
||||||
FROM account_move_line l
|
FROM account_move_line l
|
||||||
WHERE l.account_id = a.id
|
WHERE l.account_id = a.id
|
||||||
{6}
|
{7}
|
||||||
AND l.amount_residual < 0
|
AND l.amount_residual < 0
|
||||||
)
|
)
|
||||||
GROUP BY {7} a.id, a.name, a.code, {res_alias}.last_time_entries_checked
|
GROUP BY {8} a.id, a.name, a.code, {res_alias}.last_time_entries_checked
|
||||||
ORDER BY {res_alias}.last_time_entries_checked
|
ORDER BY {res_alias}.last_time_entries_checked
|
||||||
) as s
|
) as s
|
||||||
WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked)
|
WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked)
|
||||||
@@ -472,6 +478,7 @@ class AccountMoveLine(models.Model):
|
|||||||
is_partner and ' ' or "AND at.type <> 'payable' AND at.type <> 'receivable'",
|
is_partner and ' ' or "AND at.type <> 'payable' AND at.type <> 'receivable'",
|
||||||
account_type and "AND at.type = %(account_type)s" or '',
|
account_type and "AND at.type = %(account_type)s" or '',
|
||||||
res_ids and 'AND ' + res_alias + '.id in %(res_ids)s' or '',
|
res_ids and 'AND ' + res_alias + '.id in %(res_ids)s' or '',
|
||||||
|
self.env.user.company_id.id,
|
||||||
is_partner and 'AND l.partner_id = p.id' or ' ',
|
is_partner and 'AND l.partner_id = p.id' or ' ',
|
||||||
is_partner and 'l.partner_id, p.id,' or ' ',
|
is_partner and 'l.partner_id, p.id,' or ' ',
|
||||||
res_alias=res_alias
|
res_alias=res_alias
|
||||||
@@ -1141,6 +1148,18 @@ class AccountMoveLine(models.Model):
|
|||||||
tables, where_clause, where_clause_params = query.get_sql()
|
tables, where_clause, where_clause_params = query.get_sql()
|
||||||
return tables, where_clause, where_clause_params
|
return tables, where_clause, where_clause_params
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def open_reconcile_view(self):
|
||||||
|
model, action_id = self.pool['ir.model.data'].get_object_reference(self._cr, self._uid, 'account', "action_account_moves_all_a")
|
||||||
|
action = self.pool[model].read(self._cr, self._uid, action_id, context=self._context)
|
||||||
|
ids = []
|
||||||
|
for aml in self:
|
||||||
|
if aml.account_id.reconcile:
|
||||||
|
ids.extend([r.debit_move_id.id for r in aml.matched_debit_ids] if aml.credit > 0 else [r.credit_move_id.id for r in aml.matched_credit_ids])
|
||||||
|
ids.append(aml.id)
|
||||||
|
action['domain'] = [('id', 'in', ids)]
|
||||||
|
return action
|
||||||
|
|
||||||
|
|
||||||
class AccountPartialReconcile(models.Model):
|
class AccountPartialReconcile(models.Model):
|
||||||
_name = "account.partial.reconcile"
|
_name = "account.partial.reconcile"
|
||||||
|
|||||||
@@ -749,7 +749,7 @@ class WizardMultiChartsAccounts(models.TransientModel):
|
|||||||
|
|
||||||
# Create the current year earning account (outside of the CoA)
|
# Create the current year earning account (outside of the CoA)
|
||||||
self.env['account.account'].create({
|
self.env['account.account'].create({
|
||||||
'code': '9999',
|
'code': '999999',
|
||||||
'name': _('Undistributed Profits/Losses'),
|
'name': _('Undistributed Profits/Losses'),
|
||||||
'user_type_id': self.env.ref("account.data_unaffected_earnings").id,
|
'user_type_id': self.env.ref("account.data_unaffected_earnings").id,
|
||||||
'company_id': company.id,})
|
'company_id': company.id,})
|
||||||
|
|||||||
@@ -306,7 +306,8 @@ class ResPartner(models.Model):
|
|||||||
|
|
||||||
# generate where clause to include multicompany rules
|
# generate where clause to include multicompany rules
|
||||||
where_query = account_invoice_report._where_calc([
|
where_query = account_invoice_report._where_calc([
|
||||||
('partner_id', 'in', all_partner_ids), ('state', 'not in', ['draft', 'cancel']), ('company_id', '=', self.env.user.company_id.id)
|
('partner_id', 'in', all_partner_ids), ('state', 'not in', ['draft', 'cancel']), ('company_id', '=', self.env.user.company_id.id),
|
||||||
|
('type', 'in', ('out_invoice', 'out_refund'))
|
||||||
])
|
])
|
||||||
account_invoice_report._apply_ir_rules(where_query, 'read')
|
account_invoice_report._apply_ir_rules(where_query, 'read')
|
||||||
from_clause, where_clause, where_clause_params = where_query.get_sql()
|
from_clause, where_clause, where_clause_params = where_query.get_sql()
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from . import test_chart_of_account
|
|||||||
from . import test_fiscal_position
|
from . import test_fiscal_position
|
||||||
from . import test_manual_reconciliation
|
from . import test_manual_reconciliation
|
||||||
from . import test_payment
|
from . import test_payment
|
||||||
|
from . import test_product_id_change
|
||||||
from . import test_reconciliation
|
from . import test_reconciliation
|
||||||
from . import test_search
|
from . import test_search
|
||||||
from . import test_tax
|
from . import test_tax
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
from openerp.addons.account.tests.account_test_classes import AccountingTestCase
|
||||||
|
import time
|
||||||
|
|
||||||
|
class TestProductIdChange(AccountingTestCase):
|
||||||
|
"""Test that when an included tax is mapped by a fiscal position, the included tax must be
|
||||||
|
subtracted to the price of the product.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestProductIdChange, self).setUp()
|
||||||
|
self.invoice_model = self.env['account.invoice']
|
||||||
|
self.fiscal_position_model = self.env['account.fiscal.position']
|
||||||
|
self.fiscal_position_tax_model = self.env['account.fiscal.position.tax']
|
||||||
|
self.tax_model = self.env['account.tax']
|
||||||
|
self.pricelist_model = self.env['product.pricelist']
|
||||||
|
self.res_partner_model = self.env['res.partner']
|
||||||
|
self.product_tmpl_model = self.env['product.template']
|
||||||
|
self.product_model = self.env['product.product']
|
||||||
|
self.invoice_line_model = self.env['account.invoice.line']
|
||||||
|
self.account_receivable = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1)
|
||||||
|
self.account_revenue = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1)
|
||||||
|
|
||||||
|
def test_product_id_change(self):
|
||||||
|
partner = self.res_partner_model.create(dict(name="George"))
|
||||||
|
tax_include_sale = self.tax_model.create(dict(name="Include tax",
|
||||||
|
type_tax_use='sale',
|
||||||
|
amount='21.00',
|
||||||
|
price_include=True))
|
||||||
|
tax_include_purchase = self.tax_model.create(dict(name="Include tax",
|
||||||
|
type_tax_use='purchase',
|
||||||
|
amount='21.00',
|
||||||
|
price_include=True))
|
||||||
|
tax_exclude_sale = self.tax_model.create(dict(name="Exclude tax",
|
||||||
|
type_tax_use='sale',
|
||||||
|
amount='0.00'))
|
||||||
|
tax_exclude_purchase = self.tax_model.create(dict(name="Exclude tax",
|
||||||
|
type_tax_use='purchase',
|
||||||
|
amount='0.00'))
|
||||||
|
product_tmpl = self.product_tmpl_model.create(dict(name="Voiture",
|
||||||
|
list_price='121',
|
||||||
|
standard_price='121',
|
||||||
|
taxes_id=[(6, 0, [tax_include_sale.id])],
|
||||||
|
supplier_taxes_id=[(6, 0, [tax_include_purchase.id])]))
|
||||||
|
product = self.product_model.create(dict(product_tmpl_id=product_tmpl.id))
|
||||||
|
fp = self.fiscal_position_model.create(dict(name="fiscal position", sequence=1))
|
||||||
|
fp_tax_sale = self.fiscal_position_tax_model.create(dict(position_id=fp.id,
|
||||||
|
tax_src_id=tax_include_sale.id,
|
||||||
|
tax_dest_id=tax_exclude_sale.id))
|
||||||
|
fp_tax_purchase = self.fiscal_position_tax_model.create(dict(position_id=fp.id,
|
||||||
|
tax_src_id=tax_include_purchase.id,
|
||||||
|
tax_dest_id=tax_exclude_purchase.id))
|
||||||
|
|
||||||
|
out_invoice = self.invoice_model.create({
|
||||||
|
'partner_id': partner.id,
|
||||||
|
'reference_type': 'none',
|
||||||
|
'name': 'invoice to client',
|
||||||
|
'account_id': self.account_receivable.id,
|
||||||
|
'type': 'out_invoice',
|
||||||
|
'date_invoice': time.strftime('%Y') + '-06-26',
|
||||||
|
'fiscal_position_id': fp.id,
|
||||||
|
})
|
||||||
|
out_line = self.invoice_line_model.create({
|
||||||
|
'product_id': product.id,
|
||||||
|
'quantity': 1,
|
||||||
|
'price_unit': 121.0,
|
||||||
|
'invoice_id': out_invoice.id,
|
||||||
|
'name': 'something out',
|
||||||
|
'account_id': self.account_revenue.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
in_invoice = self.invoice_model.create({
|
||||||
|
'partner_id': partner.id,
|
||||||
|
'reference_type': 'none',
|
||||||
|
'name': 'invoice to supplier',
|
||||||
|
'account_id': self.account_receivable.id,
|
||||||
|
'type': 'in_invoice',
|
||||||
|
'date_invoice': time.strftime('%Y') + '-06-26',
|
||||||
|
'fiscal_position_id': fp.id,
|
||||||
|
})
|
||||||
|
in_line = self.invoice_line_model.create({
|
||||||
|
'product_id': product.id,
|
||||||
|
'quantity': 1,
|
||||||
|
'price_unit': 121.0,
|
||||||
|
'invoice_id': in_invoice.id,
|
||||||
|
'name': 'something in',
|
||||||
|
'account_id': self.account_revenue.id,
|
||||||
|
})
|
||||||
|
out_line._onchange_product_id()
|
||||||
|
self.assertEquals(100, out_line.price_unit, "The included tax must be subtracted to the price")
|
||||||
|
in_line._onchange_product_id()
|
||||||
|
self.assertEquals(100, in_line.price_unit, "The included tax must be subtracted to the price")
|
||||||
@@ -21,7 +21,11 @@
|
|||||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||||
<field name="currency_id" options="{'no_create': True}" groups="base.group_multi_currency"/>
|
<field name="currency_id" options="{'no_create': True}" groups="base.group_multi_currency"/>
|
||||||
<field name="internal_type" invisible="1" readonly="1"/>
|
<field name="internal_type" invisible="1" readonly="1"/>
|
||||||
<field name="reconcile"/>
|
<label for="reconcile"/>
|
||||||
|
<div>
|
||||||
|
<field name="reconcile"/>
|
||||||
|
<button name="action_open_reconcile" class="oe_link" type="object" string=" -> Reconcile" attrs="{'invisible': [('reconcile', '=', False)]}"/>
|
||||||
|
</div>
|
||||||
<field name="deprecated"/>
|
<field name="deprecated"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
@@ -1021,7 +1025,18 @@
|
|||||||
<field name="amount_currency"/>
|
<field name="amount_currency"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Reconciliation">
|
<group string="Reconciliation">
|
||||||
<field name="reconciled"/>
|
<label for="reconciled"/>
|
||||||
|
<div>
|
||||||
|
<field name="reconciled"/>
|
||||||
|
<field name="matched_debit_ids" invisible="1"/>
|
||||||
|
<field name="matched_credit_ids" invisible="1"/>
|
||||||
|
<button name="open_reconcile_view"
|
||||||
|
class="oe_link"
|
||||||
|
type="object"
|
||||||
|
string="-> View reconciled entries"
|
||||||
|
attrs="{'invisible': [('matched_debit_ids', '=', []),('matched_credit_ids', '=', [])]}">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</group>
|
</group>
|
||||||
<group string="States">
|
<group string="States">
|
||||||
<field name="blocked"/>
|
<field name="blocked"/>
|
||||||
@@ -1238,6 +1253,14 @@
|
|||||||
<field name="state" widget="statusbar"/>
|
<field name="state" widget="statusbar"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
|
<div class="oe_button_box">
|
||||||
|
<button name="open_reconcile_view"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-bars"
|
||||||
|
type="object"
|
||||||
|
string="View reconciled entries">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<label for="name" class="oe_edit_only" attrs="{'invisible':[('name','=','/')]}"/>
|
<label for="name" class="oe_edit_only" attrs="{'invisible':[('name','=','/')]}"/>
|
||||||
<h1>
|
<h1>
|
||||||
<field name="name" readonly="True" attrs="{'invisible':[('name','=','/')]}"/>
|
<field name="name" readonly="True" attrs="{'invisible':[('name','=','/')]}"/>
|
||||||
|
|||||||
@@ -34,7 +34,11 @@ class AuthSignupHome(Home):
|
|||||||
self.do_signup(qcontext)
|
self.do_signup(qcontext)
|
||||||
return super(AuthSignupHome, self).web_login(*args, **kw)
|
return super(AuthSignupHome, self).web_login(*args, **kw)
|
||||||
except (SignupError, AssertionError), e:
|
except (SignupError, AssertionError), e:
|
||||||
qcontext['error'] = _(e.message)
|
if request.env["res.users"].sudo().search([("login", "=", qcontext.get("login"))]):
|
||||||
|
qcontext["error"] = _("Another user is already registered using this email address.")
|
||||||
|
else:
|
||||||
|
_logger.error(e.message)
|
||||||
|
qcontext['error'] = _("Could not create a new account.")
|
||||||
|
|
||||||
return request.render('auth_signup.signup', qcontext)
|
return request.render('auth_signup.signup', qcontext)
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Odoo Server 9.0\n"
|
"Project-Id-Version: Odoo Server 9.0c\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2015-09-14 10:27+0000\n"
|
"POT-Creation-Date: 2016-01-06 07:13+0000\n"
|
||||||
"PO-Revision-Date: 2015-09-14 10:27+0000\n"
|
"PO-Revision-Date: 2016-01-06 07:13+0000\n"
|
||||||
"Last-Translator: <>\n"
|
"Last-Translator: <>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@@ -75,13 +75,19 @@ msgid "Allow external users to sign up"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: auth_signup
|
#. module: auth_signup
|
||||||
#: code:addons/auth_signup/controllers/main.py:60
|
#: code:addons/auth_signup/controllers/main.py:64
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "An email has been sent with credentials to reset your password"
|
msgid "An email has been sent with credentials to reset your password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: auth_signup
|
#. module: auth_signup
|
||||||
#: code:addons/auth_signup/controllers/main.py:108
|
#: code:addons/auth_signup/controllers/main.py:40
|
||||||
|
#, python-format
|
||||||
|
msgid "Another user is already registered using this email address."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: auth_signup
|
||||||
|
#: code:addons/auth_signup/controllers/main.py:115
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Authentication Failed."
|
msgid "Authentication Failed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -114,7 +120,13 @@ msgid "Connected"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: auth_signup
|
#. module: auth_signup
|
||||||
#: code:addons/auth_signup/controllers/main.py:62
|
#: code:addons/auth_signup/controllers/main.py:43
|
||||||
|
#, python-format
|
||||||
|
msgid "Could not create a new account."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: auth_signup
|
||||||
|
#: code:addons/auth_signup/controllers/main.py:66
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Could not reset your password"
|
msgid "Could not reset your password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -155,18 +167,13 @@ msgstr ""
|
|||||||
msgid "Password reset"
|
msgid "Password reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: auth_signup
|
|
||||||
#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_private
|
|
||||||
msgid "Private Profile"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: auth_signup
|
#. module: auth_signup
|
||||||
#: model:ir.ui.view,arch_db:auth_signup.login
|
#: model:ir.ui.view,arch_db:auth_signup.login
|
||||||
msgid "Reset Password"
|
msgid "Reset Password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: auth_signup
|
#. module: auth_signup
|
||||||
#: code:addons/auth_signup/res_users.py:250
|
#: code:addons/auth_signup/res_users.py:253
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Reset password: invalid username or email"
|
msgid "Reset password: invalid username or email"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -227,11 +234,6 @@ msgstr ""
|
|||||||
msgid "Template user for new users created through signup"
|
msgid "Template user for new users created through signup"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: auth_signup
|
|
||||||
#: model:ir.model.fields,help:auth_signup.field_res_users_website_url
|
|
||||||
msgid "The full URL to access the document through the website."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: auth_signup
|
#. module: auth_signup
|
||||||
#: model:ir.model.fields,help:auth_signup.field_base_config_settings_auth_signup_reset_password
|
#: model:ir.model.fields,help:auth_signup.field_base_config_settings_auth_signup_reset_password
|
||||||
msgid "This allows users to trigger a password reset from the Login page."
|
msgid "This allows users to trigger a password reset from the Login page."
|
||||||
@@ -242,41 +244,6 @@ msgstr ""
|
|||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: auth_signup
|
|
||||||
#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_published
|
|
||||||
msgid "Visible in Website"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: auth_signup
|
|
||||||
#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_description
|
|
||||||
msgid "Website Partner Full Description"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: auth_signup
|
|
||||||
#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_short_description
|
|
||||||
msgid "Website Partner Short Description"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: auth_signup
|
|
||||||
#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_url
|
|
||||||
msgid "Website URL"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: auth_signup
|
|
||||||
#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_meta_description
|
|
||||||
msgid "Website meta description"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: auth_signup
|
|
||||||
#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_meta_keywords
|
|
||||||
msgid "Website meta keywords"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: auth_signup
|
|
||||||
#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_meta_title
|
|
||||||
msgid "Website meta title"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: auth_signup
|
#. module: auth_signup
|
||||||
#: model:ir.ui.view,arch_db:auth_signup.fields
|
#: model:ir.ui.view,arch_db:auth_signup.fields
|
||||||
#: model:ir.ui.view,arch_db:auth_signup.reset_password
|
#: model:ir.ui.view,arch_db:auth_signup.reset_password
|
||||||
@@ -298,8 +265,3 @@ msgstr ""
|
|||||||
msgid "e.g. John Doe"
|
msgid "e.g. John Doe"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: auth_signup
|
|
||||||
#: model:ir.model.fields,field_description:auth_signup.field_res_users_self
|
|
||||||
msgid "unknown"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
|
|||||||
@@ -1459,7 +1459,7 @@ class calendar_event(osv.Model):
|
|||||||
for arg in args:
|
for arg in args:
|
||||||
new_arg = arg
|
new_arg = arg
|
||||||
|
|
||||||
if arg[0] in ('start_date', 'start_datetime', 'start',) and arg[1] == ">=":
|
if arg[0] in ('stop_date', 'stop_datetime', 'stop',) and arg[1] == ">=":
|
||||||
if context.get('virtual_id', True):
|
if context.get('virtual_id', True):
|
||||||
new_args += ['|', '&', ('recurrency', '=', 1), ('final_date', arg[1], arg[2])]
|
new_args += ['|', '&', ('recurrency', '=', 1), ('final_date', arg[1], arg[2])]
|
||||||
elif arg[0] == "id":
|
elif arg[0] == "id":
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<!-- Preload all the partner's information -->
|
<!-- Preload all the partner's information -->
|
||||||
<field name="partner_id" string="Customer"
|
<field name="partner_id" string="Customer"
|
||||||
|
on_change="on_change_partner_id(partner_id)"
|
||||||
options='{"create_name_field": "name"}'
|
options='{"create_name_field": "name"}'
|
||||||
context="{'default_name': contact_name, 'default_street': street, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_function': function, 'default_phone': phone, 'default_mobile': mobile, 'default_fax': fax, 'default_email': email_from, 'default_user_id': user_id, 'default_team_id': team_id}" groups="base.group_no_one"/>
|
context="{'default_name': contact_name, 'default_street': street, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_function': function, 'default_phone': phone, 'default_mobile': mobile, 'default_fax': fax, 'default_email': email_from, 'default_user_id': user_id, 'default_team_id': team_id}" groups="base.group_no_one"/>
|
||||||
<field name="partner_name" string="Company Name"/>
|
<field name="partner_name" string="Company Name"/>
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||||||
context = {}
|
context = {}
|
||||||
|
|
||||||
lead_obj = self.pool['crm.lead']
|
lead_obj = self.pool['crm.lead']
|
||||||
|
partner_obj = self.pool['res.partner']
|
||||||
|
|
||||||
w = self.browse(cr, uid, ids, context=context)[0]
|
w = self.browse(cr, uid, ids, context=context)[0]
|
||||||
opp_ids = [o.id for o in w.opportunity_ids]
|
opp_ids = [o.id for o in w.opportunity_ids]
|
||||||
@@ -144,6 +145,9 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||||||
lead_ids = context.get('active_ids', [])
|
lead_ids = context.get('active_ids', [])
|
||||||
vals.update({'lead_ids': lead_ids, 'user_ids': [w.user_id.id]})
|
vals.update({'lead_ids': lead_ids, 'user_ids': [w.user_id.id]})
|
||||||
self._convert_opportunity(cr, uid, ids, vals, context=context)
|
self._convert_opportunity(cr, uid, ids, vals, context=context)
|
||||||
|
for lead in lead_obj.browse(cr, uid, lead_ids, context=context):
|
||||||
|
if lead.partner_id and lead.partner_id.user_id != lead.user_id:
|
||||||
|
partner_obj.write(cr, uid, [lead.partner_id.id], {'user_id': lead.user_id.id}, context=context)
|
||||||
|
|
||||||
return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
|
return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from openerp import api, fields, models, _
|
from openerp import api, fields, models, _
|
||||||
from openerp.exceptions import UserError
|
from openerp.exceptions import UserError, ValidationError
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -52,6 +52,25 @@ class DeliveryCarrier(models.Model):
|
|||||||
fixed_price = fields.Float(compute='_compute_fixed_price', inverse='_set_product_fixed_price', store=True, string='Fixed Price',help="Keep empty if the pricing depends on the advanced pricing per destination")
|
fixed_price = fields.Float(compute='_compute_fixed_price', inverse='_set_product_fixed_price', store=True, string='Fixed Price',help="Keep empty if the pricing depends on the advanced pricing per destination")
|
||||||
shipping_enabled = fields.Boolean(string="Shipping enabled", default=True, help="Uncheck this box to disable package shipping while validating Delivery Orders")
|
shipping_enabled = fields.Boolean(string="Shipping enabled", default=True, help="Uncheck this box to disable package shipping while validating Delivery Orders")
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def name_get(self):
|
||||||
|
display_delivery = self.env.context.get('display_delivery', False)
|
||||||
|
order_id = self.env.context.get('order_id', False)
|
||||||
|
if display_delivery and order_id:
|
||||||
|
order = self.env['sale.order'].browse(order_id)
|
||||||
|
currency = order.pricelist_id.currency_id.name or ''
|
||||||
|
res = []
|
||||||
|
for carrier_id in self.ids:
|
||||||
|
try:
|
||||||
|
r = self.read([carrier_id], ['name', 'price'])[0]
|
||||||
|
res.append((r['id'], r['name'] + ' (' + (str(r['price'])) + ' ' + currency + ')'))
|
||||||
|
except ValidationError:
|
||||||
|
r = self.read([carrier_id], ['name'])[0]
|
||||||
|
res.append((r['id'], r['name']))
|
||||||
|
else:
|
||||||
|
res = super(DeliveryCarrier, self).name_get()
|
||||||
|
return res
|
||||||
|
|
||||||
@api.depends('product_id.list_price', 'product_id.product_tmpl_id.list_price')
|
@api.depends('product_id.list_price', 'product_id.product_tmpl_id.list_price')
|
||||||
def _compute_fixed_price(self):
|
def _compute_fixed_price(self):
|
||||||
for carrier in self:
|
for carrier in self:
|
||||||
@@ -234,6 +253,8 @@ class DeliveryCarrier(models.Model):
|
|||||||
quantity += qty
|
quantity += qty
|
||||||
total = (order.amount_total or 0.0) - total_delivery
|
total = (order.amount_total or 0.0) - total_delivery
|
||||||
|
|
||||||
|
total = order.currency_id.with_context(date=order.date_order).compute(total, order.company_id.currency_id)
|
||||||
|
|
||||||
return self.get_price_from_picking(total, weight, volume, quantity)
|
return self.get_price_from_picking(total, weight, volume, quantity)
|
||||||
|
|
||||||
def get_price_from_picking(self, total, weight, volume, quantity):
|
def get_price_from_picking(self, total, weight, volume, quantity):
|
||||||
|
|||||||
@@ -171,7 +171,8 @@
|
|||||||
</group>
|
</group>
|
||||||
</xpath>
|
</xpath>
|
||||||
<div name="button_box" position="inside">
|
<div name="button_box" position="inside">
|
||||||
<button type="object" name="open_website_url" class="oe_stat_button" icon='fa-truck' string="Tracking" attrs="{'invisible': ['|',('carrier_tracking_ref','=',False),('delivery_type','in', ['fixed', 'base_on_rule'])]}" />
|
<button type="object" name="open_website_url" class="oe_stat_button" icon='fa-truck' string="Tracking"
|
||||||
|
attrs="{'invisible': ['|','|',('carrier_tracking_ref','=',False),('carrier_id', '=', False),('delivery_type','=','grid')]}" />
|
||||||
</div>
|
</div>
|
||||||
<xpath expr="/form/header/button[last()]" position="after">
|
<xpath expr="/form/header/button[last()]" position="after">
|
||||||
<button name="send_to_shipper" string="Send to Shipper" type="object" attrs="{'invisible':['|','|','|',('carrier_tracking_ref','!=',False),('delivery_type','in', ['fixed', 'base_on_rule']),('delivery_type','=',False),('state','not in',('done'))]}"/>
|
<button name="send_to_shipper" string="Send to Shipper" type="object" attrs="{'invisible':['|','|','|',('carrier_tracking_ref','!=',False),('delivery_type','in', ['fixed', 'base_on_rule']),('delivery_type','=',False),('state','not in',('done'))]}"/>
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ class sale_order_line(osv.osv):
|
|||||||
help="Choose an event ticket and it will automatically create a registration for this event ticket."),
|
help="Choose an event ticket and it will automatically create a registration for this event ticket."),
|
||||||
# those 2 fields are used for dynamic domains and filled by onchange
|
# those 2 fields are used for dynamic domains and filled by onchange
|
||||||
# TDE: really necessary ? ...
|
# TDE: really necessary ? ...
|
||||||
'event_type_id': fields.related('product_id', 'event_type_id', type='many2one', relation="event.type", string="Event Type"),
|
'event_type_id': fields.related('product_id', 'event_type_id', type='many2one', relation="event.type", string="Event Type", readonly=True),
|
||||||
'event_ok': fields.related('product_id', 'event_ok', string='event_ok', type='boolean'),
|
'event_ok': fields.related('product_id', 'event_ok', string='event_ok', type='boolean', readonly=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _prepare_order_line_invoice_line(self, cr, uid, line, account_id=False, context=None):
|
def _prepare_order_line_invoice_line(self, cr, uid, line, account_id=False, context=None):
|
||||||
|
|||||||
@@ -6,13 +6,17 @@
|
|||||||
<field name="model">sale.order</field>
|
<field name="model">sale.order</field>
|
||||||
<field name="inherit_id" ref="sale.view_order_form" />
|
<field name="inherit_id" ref="sale.view_order_form" />
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//field[@name='product_id']" position="after">
|
<xpath expr="//field[@name='order_line']//form//field[@name='product_id']" position="after">
|
||||||
<field name="event_id" domain="['|', ('event_type_id','=', False),('event_type_id', '=', event_type_id),('date_end','>=',time.strftime('%Y-%m-%d 00:00:00'))]" attrs="{'invisible': [('event_ok', '=', False)],'required': [('event_ok', '!=', False)]}"/>
|
<field name="event_id" domain="['|', ('event_type_id','=', False),('event_type_id', '=', event_type_id),('date_end','>=',time.strftime('%Y-%m-%d 00:00:00'))]" attrs="{'invisible': [('event_ok', '=', False)],'required': [('event_ok', '!=', False)]}"/>
|
||||||
<field name="event_ticket_id" domain="[('event_id', '=', event_id), ('seats_available', '>', 0)]" attrs="{'invisible': [('event_id', '=', False)], 'required': [('event_id', '!=', False)]}"
|
<field name="event_ticket_id" domain="[('event_id', '=', event_id), ('seats_available', '>', 0)]" attrs="{'invisible': [('event_id', '=', False)], 'required': [('event_id', '!=', False)]}"
|
||||||
on_change="onchange_event_ticket_id(event_ticket_id, context)"/>
|
on_change="onchange_event_ticket_id(event_ticket_id, context)"/>
|
||||||
<field name="event_type_id" invisible="1"/>
|
<field name="event_type_id" invisible="1"/>
|
||||||
<field name="event_ok" invisible="1"/>
|
<field name="event_ok" invisible="1"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='order_line']//tree//field[@name='product_id']" position="after">
|
||||||
|
<field name="event_type_id" invisible="1"/>
|
||||||
|
<field name="event_ok" invisible="1"/>
|
||||||
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|||||||
@@ -232,12 +232,12 @@ class google_calendar(osv.AbstractModel):
|
|||||||
"start": {
|
"start": {
|
||||||
type: start_date,
|
type: start_date,
|
||||||
vstype: None,
|
vstype: None,
|
||||||
'timeZone': context.get('tz', 'UTC'),
|
'timeZone': context.get('tz') or 'UTC',
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
type: final_date,
|
type: final_date,
|
||||||
vstype: None,
|
vstype: None,
|
||||||
'timeZone': context.get('tz', 'UTC'),
|
'timeZone': context.get('tz') or 'UTC',
|
||||||
},
|
},
|
||||||
"attendees": attendee_list,
|
"attendees": attendee_list,
|
||||||
"reminders": {
|
"reminders": {
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
import models
|
import models
|
||||||
|
import report
|
||||||
import wizard
|
import wizard
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import hr_expense_report
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import time
|
||||||
|
from openerp.osv import osv
|
||||||
|
from openerp.tools.misc import formatLang
|
||||||
|
from openerp.tools.translate import _
|
||||||
|
from openerp.report import report_sxw
|
||||||
|
from openerp.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
|
class report_expense(report_sxw.rml_parse):
|
||||||
|
|
||||||
|
def set_context(self, objects, data, ids, report_type=None):
|
||||||
|
res = super(report_expense, self).set_context(objects, data, ids, report_type=report_type)
|
||||||
|
|
||||||
|
hr_expense_obj = self.pool.get('hr.expense')
|
||||||
|
|
||||||
|
context = {'lang': self.pool.get('res.users').browse(self.cr, self.uid, self.uid).lang}
|
||||||
|
state_field = hr_expense_obj.fields_get(self.cr, self.uid, 'state', context=context)['state']['selection']
|
||||||
|
state_dict = {}
|
||||||
|
for state_tuple in state_field:
|
||||||
|
state_dict[state_tuple[0]] = state_tuple[1]
|
||||||
|
|
||||||
|
expenses_info = {}
|
||||||
|
expenses = hr_expense_obj.search(self.cr, self.uid, [('id', 'in', ids)], order="employee_id, currency_id, state, date")
|
||||||
|
for expense in hr_expense_obj.browse(self.cr, self.uid, expenses):
|
||||||
|
key = expense.employee_id.name + '-' + expense.currency_id.name + '-' + expense.state
|
||||||
|
if expenses_info.get(key):
|
||||||
|
expenses_info[key]['lines'] += expense
|
||||||
|
expenses_info[key]['total_amount'] += expense.total_amount
|
||||||
|
else:
|
||||||
|
expenses_info[key] = {
|
||||||
|
'employee_name': expense.employee_id.name,
|
||||||
|
'total_amount': expense.total_amount,
|
||||||
|
'lines': expense,
|
||||||
|
'currency': expense.currency_id,
|
||||||
|
'validator_name': expense.employee_id.parent_id.name,
|
||||||
|
'notes': [],
|
||||||
|
'notes_index': {},
|
||||||
|
'state': state_dict[expense.state],
|
||||||
|
}
|
||||||
|
if expense.description:
|
||||||
|
index = len(expenses_info[key]['notes']) + 1
|
||||||
|
expenses_info[key]['notes'].append({'description': expense.description, 'index':index})
|
||||||
|
expenses_info[key]['notes_index'][expense.id] = index
|
||||||
|
|
||||||
|
# Qweb for-each do not work on dict, so we send a list and we sort it by the name of the employee
|
||||||
|
# that way if we have two sheet for the same employee they will follow in the report
|
||||||
|
self.localcontext.update({
|
||||||
|
'get_expenses': lambda : [v for k,v in sorted(expenses_info.items())],
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class report_hr_expense(osv.AbstractModel):
|
||||||
|
_name = 'report.hr_expense.report_expense'
|
||||||
|
_inherit = 'report.abstract_report'
|
||||||
|
_template = 'hr_expense.report_expense'
|
||||||
|
_wrapped_report_class = report_expense
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<odoo>
|
<odoo>
|
||||||
<template id="report_expense">
|
<template id="report_expense">
|
||||||
<t t-call="report.html_container">
|
<t t-call="report.html_container">
|
||||||
<t t-foreach="docs" t-as="o">
|
<t t-foreach="get_expenses()" t-as="o">
|
||||||
<t t-call="report.external_layout">
|
<t t-call="report.external_layout">
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<h2>HR Expenses</h2>
|
<h2>HR Expenses</h2>
|
||||||
@@ -10,25 +10,26 @@
|
|||||||
<div class="row mt32 mb32">
|
<div class="row mt32 mb32">
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<strong>Employee:</strong>
|
<strong>Employee:</strong>
|
||||||
<p t-field="o.employee_id.name"/>
|
<p t-esc="o['employee_name']"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<strong>Date:</strong>
|
<strong>Manager:</strong>
|
||||||
<p t-field="o.date"/>
|
<p t-esc="o['validator_name']"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<strong>Description:</strong>
|
<strong>Status:</strong>
|
||||||
<p t-field="o.name"/>
|
<p t-esc="o['state']"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<strong>Validated By:</strong>
|
<strong>Currency:</strong>
|
||||||
<p t-field="o.employee_id.parent_id.name"/>
|
<p t-field="o['currency'].name"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table table-condensed">
|
<table class="table table-condensed">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Unit Price</th>
|
<th>Unit Price</th>
|
||||||
<th class="text-center">Qty</th>
|
<th class="text-center">Qty</th>
|
||||||
@@ -38,35 +39,46 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<t t-foreach="o['lines']" t-as="line">
|
||||||
<td>
|
<tr>
|
||||||
<span t-field="o.name"/>
|
<td>
|
||||||
<span t-field="o.analytic_account_id.name"/>
|
<span t-field='line.date'/>
|
||||||
</td>
|
<t t-if="line.description">
|
||||||
<td>
|
<sup><span t-esc="o['notes_index'][line.id]"/></sup>
|
||||||
<span t-field="o.unit_amount"/>
|
</t>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td>
|
||||||
<span t-field="o.quantity"/>
|
<span t-field="line.name"/>
|
||||||
</td>
|
<t t-if="line.analytic_account_id.name">
|
||||||
<td groups="product.group_uom">
|
(<span t-field="line.analytic_account_id.name"/>)
|
||||||
<span t-field="o.product_uom_id"/>
|
</t>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<t t-foreach="o.tax_ids" t-as="tax">
|
<span t-field="line.unit_amount"
|
||||||
<t t-if="tax.description">
|
t-field-options='{"widget": "monetary", "display_currency":"line.currency_id"}'/>
|
||||||
<span t-field="tax.description"/>
|
</td>
|
||||||
</t>
|
<td class="text-center">
|
||||||
<t t-if="not tax.description">
|
<span t-field="line.quantity"/>
|
||||||
<span t-field="tax.name"/>
|
</td>
|
||||||
</t>
|
<td groups="product.group_uom">
|
||||||
</t>
|
<span t-field="line.product_uom_id"/>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td>
|
||||||
<span t-field="o.total_amount"
|
<t t-foreach="line.tax_ids" t-as="tax">
|
||||||
t-field-options='{"widget": "monetary", "display_currency":"o.currency_id"}'/>
|
<t t-if="tax.description">
|
||||||
</td>
|
<span t-field="tax.description"/>
|
||||||
</tr>
|
</t>
|
||||||
|
<t t-if="not tax.description">
|
||||||
|
<span t-field="tax.name"/>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="line.total_amount"
|
||||||
|
t-field-options='{"widget": "monetary", "display_currency":"line.currency_id"}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -76,15 +88,18 @@
|
|||||||
<tr class="border-black">
|
<tr class="border-black">
|
||||||
<td><strong>Total</strong></td>
|
<td><strong>Total</strong></td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<span t-field="o.total_amount"
|
<span t-esc="formatLang(o['total_amount'], currency_obj=o['currency'])"/>
|
||||||
t-field-options='{"widget": "monetary", "display_currency": "o.currency_id"}'/>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<t t-foreach="o['notes']" t-as="note">
|
||||||
<p t-field="o.description"/>
|
<span t-esc="note['index']"/>. <span t-esc="note['description']"/>
|
||||||
|
<br/>
|
||||||
|
</t>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
<p>Certified honest and conform,<br/>(Date and signature).<br/><br/></p>
|
<p>Certified honest and conform,<br/>(Date and signature).<br/><br/></p>
|
||||||
<p>This document must be dated and signed for reimbursement.</p>
|
<p>This document must be dated and signed for reimbursement.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class payslip_details_report(report_sxw.rml_parse):
|
|||||||
if not rule_categories:
|
if not rule_categories:
|
||||||
return []
|
return []
|
||||||
if rule_categories[0].parent_id:
|
if rule_categories[0].parent_id:
|
||||||
rule_categories.insert(0, rule_categories[0].parent_id)
|
rule_categories = rule_categories[0].parent_id | rule_categories
|
||||||
get_recursive_parent(rule_categories)
|
get_recursive_parent(rule_categories)
|
||||||
return rule_categories
|
return rule_categories
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import controllers
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
{
|
||||||
|
'name': 'Blackbox Hardware Driver',
|
||||||
|
'version': '1.0',
|
||||||
|
'category': 'Hardware Drivers',
|
||||||
|
'sequence': 6,
|
||||||
|
'summary': 'Hardware Driver for Belgian Fiscal Data Modules',
|
||||||
|
'website': 'https://www.odoo.com/page/point-of-sale',
|
||||||
|
'description': """
|
||||||
|
Fiscal Data Module Hardware Driver
|
||||||
|
==================================
|
||||||
|
|
||||||
|
This module allows a Point Of Sale client to communicate with a
|
||||||
|
connected Belgian Fiscal Data Module.
|
||||||
|
""",
|
||||||
|
'author': 'OpenERP SA',
|
||||||
|
'depends': ['hw_proxy'],
|
||||||
|
'external_dependencies': {'python': ['serial']},
|
||||||
|
'test': [
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'auto_install': False,
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import main
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import serial
|
||||||
|
from os import listdir
|
||||||
|
from threading import Thread, Lock
|
||||||
|
|
||||||
|
from openerp import http
|
||||||
|
|
||||||
|
import openerp.addons.hw_proxy.controllers.main as hw_proxy
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class Blackbox(Thread):
|
||||||
|
def __init__(self):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.blackbox_lock = Lock()
|
||||||
|
self.set_status('connecting')
|
||||||
|
self.device_path = self._find_device_path_by_probing()
|
||||||
|
|
||||||
|
def set_status(self, status, messages=[]):
|
||||||
|
self.status = {
|
||||||
|
'status': status,
|
||||||
|
'messages': messages
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
return self.status
|
||||||
|
|
||||||
|
# There is no real way to find a serial device, all you can really
|
||||||
|
# find is the name of the serial to usb interface, which in the
|
||||||
|
# case of the blackbox is not defined because it doesn't always
|
||||||
|
# come with it's own interface (eg. Retail Cleancash SC-B). So, in
|
||||||
|
# order to differentiate between other devices like this, what
|
||||||
|
# we'll do is probe every serial device with an FDM status
|
||||||
|
# request. The first device to give an answer that makes sense
|
||||||
|
# wins.
|
||||||
|
def _find_device_path_by_probing(self):
|
||||||
|
path = "/dev/serial/by-id/"
|
||||||
|
probe_message = self._wrap_low_level_message_around("S000")
|
||||||
|
|
||||||
|
try:
|
||||||
|
devices = listdir(path)
|
||||||
|
except OSError:
|
||||||
|
_logger.warning(path + " doesn't exist")
|
||||||
|
else:
|
||||||
|
for device in listdir(path):
|
||||||
|
path_to_device = path + device
|
||||||
|
_logger.debug("Probing " + device)
|
||||||
|
|
||||||
|
if self._send_to_blackbox(probe_message, 21, path_to_device, just_wait_for_ack=True):
|
||||||
|
_logger.info(device + " will be used as the blackbox")
|
||||||
|
self.set_status("connected", [device])
|
||||||
|
return path_to_device
|
||||||
|
|
||||||
|
_logger.warning("Blackbox could not be found")
|
||||||
|
self.set_status("error", ["Couldn't find the Fiscal Data Module"])
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _lrc(self, msg):
|
||||||
|
lrc = 0
|
||||||
|
|
||||||
|
for character in msg:
|
||||||
|
byte = ord(character)
|
||||||
|
lrc = (lrc + byte) & 0xFF
|
||||||
|
|
||||||
|
lrc = ((lrc ^ 0xFF) + 1) & 0xFF
|
||||||
|
|
||||||
|
return lrc
|
||||||
|
|
||||||
|
def _wrap_low_level_message_around(self, high_level_message):
|
||||||
|
bcc = self._lrc(high_level_message)
|
||||||
|
high_level_message_bytes = map(ord, high_level_message)
|
||||||
|
|
||||||
|
low_level_message = bytearray()
|
||||||
|
low_level_message.append(0x02)
|
||||||
|
low_level_message.extend(high_level_message_bytes)
|
||||||
|
low_level_message.append(0x03)
|
||||||
|
low_level_message.append(bcc)
|
||||||
|
|
||||||
|
return low_level_message
|
||||||
|
|
||||||
|
def _send_and_wait_for_ack(self, packet, serial):
|
||||||
|
ack = 0
|
||||||
|
MAX_RETRIES = 1
|
||||||
|
|
||||||
|
while ack != 0x06 and int(chr(packet[4])) < MAX_RETRIES:
|
||||||
|
serial.write(packet)
|
||||||
|
ack = serial.read(1)
|
||||||
|
|
||||||
|
# This violates the principle that we do high level
|
||||||
|
# client-side and low level posbox-side but the retry
|
||||||
|
# counter is always in a fixed position in the high level
|
||||||
|
# message so it's safe to do it. Also it would be a pain
|
||||||
|
# to have to throw this all the way back to js just so it
|
||||||
|
# can increment the retry counter and then try again.
|
||||||
|
packet = packet[:4] + str(int(packet[4]) + 1) + packet[5:]
|
||||||
|
|
||||||
|
if ack:
|
||||||
|
ack = ord(ack)
|
||||||
|
else:
|
||||||
|
_logger.warning("did not get ACK, retrying...")
|
||||||
|
ack = 0
|
||||||
|
|
||||||
|
if ack == 0x06:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
_logger.error("retried " + str(MAX_RETRIES) + " times without receiving ACK, is blackbox properly connected?")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _send_to_blackbox(self, packet, response_size, device_path, just_wait_for_ack=False):
|
||||||
|
if not device_path:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
ser = serial.Serial(port=device_path,
|
||||||
|
baudrate=19200,
|
||||||
|
timeout=3)
|
||||||
|
MAX_NACKS = 1
|
||||||
|
got_response = False
|
||||||
|
sent_nacks = 0
|
||||||
|
|
||||||
|
if self._send_and_wait_for_ack(packet, ser):
|
||||||
|
if just_wait_for_ack:
|
||||||
|
return True
|
||||||
|
|
||||||
|
while not got_response and sent_nacks < MAX_NACKS:
|
||||||
|
stx = ser.read(1)
|
||||||
|
response = ser.read(response_size)
|
||||||
|
etx = ser.read(1)
|
||||||
|
bcc = ser.read(1)
|
||||||
|
|
||||||
|
if stx == chr(0x02) and etx == chr(0x03) and bcc and self._lrc(response) == ord(bcc):
|
||||||
|
got_response = True
|
||||||
|
ser.write(chr(0x06))
|
||||||
|
else:
|
||||||
|
_logger.warning("received ACK but not a valid response, sending NACK...")
|
||||||
|
sent_nacks += 1
|
||||||
|
ser.write(chr(0x15))
|
||||||
|
|
||||||
|
if not got_response:
|
||||||
|
_logger.error("sent " + str(MAX_NACKS) + " NACKS without receiving response, giving up.")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
ser.close()
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
ser.close()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
blackbox_thread = Blackbox()
|
||||||
|
hw_proxy.drivers['fiscal_data_module'] = blackbox_thread
|
||||||
|
|
||||||
|
class BlackboxDriver(hw_proxy.Proxy):
|
||||||
|
@http.route('/hw_proxy/request_blackbox/', type='json', auth='none', cors='*')
|
||||||
|
def request_blackbox(self, high_level_message, response_size):
|
||||||
|
to_send = blackbox_thread._wrap_low_level_message_around(high_level_message)
|
||||||
|
|
||||||
|
with blackbox_thread.blackbox_lock:
|
||||||
|
response = blackbox_thread._send_to_blackbox(to_send, response_size, blackbox_thread.device_path)
|
||||||
|
|
||||||
|
return response
|
||||||
@@ -30,7 +30,8 @@ class LivechatController(http.Controller):
|
|||||||
username = kwargs.get("username", _("Visitor"))
|
username = kwargs.get("username", _("Visitor"))
|
||||||
channel = request.env['im_livechat.channel'].sudo().browse(channel_id)
|
channel = request.env['im_livechat.channel'].sudo().browse(channel_id)
|
||||||
info = request.env['im_livechat.channel'].get_livechat_info(channel.id, username=username)
|
info = request.env['im_livechat.channel'].get_livechat_info(channel.id, username=username)
|
||||||
return request.render('im_livechat.loader', {'info': info, 'web_session_required': True}) if info['available'] else False
|
js = request.render('im_livechat.loader', {'info': info, 'web_session_required': True})
|
||||||
|
return request.make_response(js, headers=[('Content-Type', 'application/javascript')])
|
||||||
|
|
||||||
@http.route('/im_livechat/init', type='json', auth="public")
|
@http.route('/im_livechat/init', type='json', auth="public")
|
||||||
def livechat_init(self, channel_id):
|
def livechat_init(self, channel_id):
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ class ImLivechatChannel(models.Model):
|
|||||||
@api.model
|
@api.model
|
||||||
def get_livechat_info(self, channel_id, username='Visitor'):
|
def get_livechat_info(self, channel_id, username='Visitor'):
|
||||||
info = {}
|
info = {}
|
||||||
info['available'] = len(self.browse(channel_id).get_available_users())
|
info['available'] = len(self.browse(channel_id).get_available_users()) > 0
|
||||||
if info['available']:
|
if info['available']:
|
||||||
info['server_url'] = self.env['ir.config_parameter'].get_param('web.base.url')
|
info['server_url'] = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||||
info['options'] = self.sudo().get_channel_infos(channel_id)
|
info['options'] = self.sudo().get_channel_infos(channel_id)
|
||||||
|
|||||||
@@ -143,7 +143,6 @@
|
|||||||
<t t-set="info" t-value="request.env['im_livechat.channel'].get_livechat_info(channel)"/>
|
<t t-set="info" t-value="request.env['im_livechat.channel'].get_livechat_info(channel)"/>
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
<t t-if="info['available']" t-translation="off">
|
|
||||||
document.addEventListener("DOMContentLoaded", function(event) {
|
document.addEventListener("DOMContentLoaded", function(event) {
|
||||||
<t t-if="web_session_required">
|
<t t-if="web_session_required">
|
||||||
odoo.define('web.session', function (require) {
|
odoo.define('web.session', function (require) {
|
||||||
@@ -154,6 +153,7 @@
|
|||||||
</t>
|
</t>
|
||||||
|
|
||||||
odoo.define('im_livechat.livesupport', function (require) {
|
odoo.define('im_livechat.livesupport', function (require) {
|
||||||
|
<t t-if="info['available']" t-translation="off">
|
||||||
var im_livechat = require('im_livechat.im_livechat');
|
var im_livechat = require('im_livechat.im_livechat');
|
||||||
var button = new im_livechat.LivechatButton(
|
var button = new im_livechat.LivechatButton(
|
||||||
$('body'),
|
$('body'),
|
||||||
@@ -162,9 +162,9 @@
|
|||||||
);
|
);
|
||||||
button.appendTo($('body'));
|
button.appendTo($('body'));
|
||||||
window.livechat_button = button;
|
window.livechat_button = button;
|
||||||
|
</t>
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</t>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ class account_invoice(osv.osv):
|
|||||||
required=True, readonly=True),
|
required=True, readonly=True),
|
||||||
}
|
}
|
||||||
_constraints = [
|
_constraints = [
|
||||||
(_check_communication, 'Invalid BBA Structured Communication !', ['Communication']),
|
(_check_communication, 'Invalid BBA Structured Communication !', ['reference', 'reference_type']),
|
||||||
]
|
]
|
||||||
|
|
||||||
account_invoice()
|
account_invoice()
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class MailMail(models.Model):
|
|||||||
if partner:
|
if partner:
|
||||||
email_to = [formataddr((partner.name, partner.email))]
|
email_to = [formataddr((partner.name, partner.email))]
|
||||||
else:
|
else:
|
||||||
email_to = tools.email_split(self.email_to)
|
email_to = tools.email_split_and_format(self.email_to)
|
||||||
return email_to
|
return email_to
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def format_tz(pool, cr, uid, dt, tz=False, format=False, context=None):
|
|||||||
format_date = lang_params.get("date_format", '%B-%d-%Y')
|
format_date = lang_params.get("date_format", '%B-%d-%Y')
|
||||||
format_time = lang_params.get("time_format", '%I-%M %p')
|
format_time = lang_params.get("time_format", '%I-%M %p')
|
||||||
|
|
||||||
fdate = ts.strftime(format_date)
|
fdate = ts.strftime(format_date).decode('utf-8')
|
||||||
ftime = ts.strftime(format_time)
|
ftime = ts.strftime(format_time)
|
||||||
return "%s %s%s" % (fdate, ftime, (' (%s)' % tz) if tz else '')
|
return "%s %s%s" % (fdate, ftime, (' (%s)' % tz) if tz else '')
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class TestMail(common.SavepointCase):
|
|||||||
cls.env['ir.mail_server']._patch_method('build_email', build_email)
|
cls.env['ir.mail_server']._patch_method('build_email', build_email)
|
||||||
cls.env['ir.mail_server']._patch_method('send_email', send_email)
|
cls.env['ir.mail_server']._patch_method('send_email', send_email)
|
||||||
|
|
||||||
|
|
||||||
# User groups
|
# User groups
|
||||||
user_group_employee = cls.env.ref('base.group_user')
|
user_group_employee = cls.env.ref('base.group_user')
|
||||||
user_group_portal = cls.env.ref('base.group_portal')
|
user_group_portal = cls.env.ref('base.group_portal')
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from email.utils import formataddr
|
||||||
|
|
||||||
from .common import TestMail
|
from .common import TestMail
|
||||||
from openerp.tools import mute_logger
|
from openerp.tools import mute_logger
|
||||||
import socket
|
import socket
|
||||||
@@ -311,7 +313,7 @@ class TestMailgateway(TestMail):
|
|||||||
'message_process: incoming email on Partners alias should send a bounce email')
|
'message_process: incoming email on Partners alias should send a bounce email')
|
||||||
self.assertIn('New Frogs', self._mails[0].get('subject'),
|
self.assertIn('New Frogs', self._mails[0].get('subject'),
|
||||||
'message_process: bounce email on Partners alias should contain the original subject')
|
'message_process: bounce email on Partners alias should contain the original subject')
|
||||||
self.assertIn('test.sylvie.lelitre@agrolait.com', self._mails[0].get('email_to'),
|
self.assertIn(formataddr(('Sylvie Lelitre', 'test.sylvie.lelitre@agrolait.com')), self._mails[0].get('email_to'),
|
||||||
'message_process: bounce email on Partners alias should have original email sender as recipient')
|
'message_process: bounce email on Partners alias should have original email sender as recipient')
|
||||||
|
|
||||||
@mute_logger('openerp.addons.mail.models.mail_thread', 'openerp.models', 'openerp.addons.mail.models.mail_mail')
|
@mute_logger('openerp.addons.mail.models.mail_thread', 'openerp.models', 'openerp.addons.mail.models.mail_mail')
|
||||||
|
|||||||
@@ -96,6 +96,7 @@
|
|||||||
context="{
|
context="{
|
||||||
'default_composition_mode': 'mass_mail',
|
'default_composition_mode': 'mass_mail',
|
||||||
'default_partner_to': '${object.id or \'\'}',
|
'default_partner_to': '${object.id or \'\'}',
|
||||||
|
'default_active_domain': [['id', 'in', context.get('active_ids', [])]],
|
||||||
}"/>
|
}"/>
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Odoo Server 9.0\n"
|
"Project-Id-Version: Odoo Server 9.0c\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2015-10-09 09:20+0000\n"
|
"POT-Creation-Date: 2016-01-06 07:17+0000\n"
|
||||||
"PO-Revision-Date: 2015-10-09 09:20+0000\n"
|
"PO-Revision-Date: 2016-01-06 07:17+0000\n"
|
||||||
"Last-Translator: <>\n"
|
"Last-Translator: <>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@@ -640,10 +640,14 @@ msgstr ""
|
|||||||
|
|
||||||
#. module: mass_mailing
|
#. module: mass_mailing
|
||||||
#: model:ir.ui.view,arch_db:mass_mailing.email_designer_snippets
|
#: model:ir.ui.view,arch_db:mass_mailing.email_designer_snippets
|
||||||
#: model:mail.mass_mailing,body_html:mass_mailing.mass_mail_1
|
|
||||||
msgid "Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/>"
|
msgid "Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: mass_mailing
|
||||||
|
#: model:mail.mass_mailing,body_html:mass_mailing.mass_mail_1
|
||||||
|
msgid "Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. module: mass_mailing
|
#. module: mass_mailing
|
||||||
#: model:ir.ui.view,arch_db:mass_mailing.view_mail_mail_statistics_search
|
#: model:ir.ui.view,arch_db:mass_mailing.view_mail_mail_statistics_search
|
||||||
#: model:ir.ui.view,arch_db:mass_mailing.view_mail_mass_mailing_campaign_search
|
#: model:ir.ui.view,arch_db:mass_mailing.view_mail_mass_mailing_campaign_search
|
||||||
@@ -660,7 +664,7 @@ msgstr ""
|
|||||||
|
|
||||||
#. module: mass_mailing
|
#. module: mass_mailing
|
||||||
#: model:ir.ui.view,arch_db:mass_mailing.FieldTextHtmlInline
|
#: model:ir.ui.view,arch_db:mass_mailing.FieldTextHtmlInline
|
||||||
msgid "Help"
|
msgid "Here you can design your own template from scratch by selecting your favorite theme."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: mass_mailing
|
#. module: mass_mailing
|
||||||
@@ -1023,11 +1027,6 @@ msgstr ""
|
|||||||
msgid "Messages"
|
msgid "Messages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: mass_mailing
|
|
||||||
#: model:ir.model.fields,help:mass_mailing.field_mail_mass_mailing_contact_message_ids
|
|
||||||
msgid "Messages and communication history"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: mass_mailing
|
#. module: mass_mailing
|
||||||
#: model:ir.ui.view,arch_db:mass_mailing.email_designer_snippets
|
#: model:ir.ui.view,arch_db:mass_mailing.email_designer_snippets
|
||||||
#: model:mail.mass_mailing,body_html:mass_mailing.mass_mail_1
|
#: model:mail.mass_mailing,body_html:mass_mailing.mass_mail_1
|
||||||
@@ -1551,6 +1550,11 @@ msgstr ""
|
|||||||
msgid "This mass mailing is scheduled to"
|
msgid "This mass mailing is scheduled to"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: mass_mailing
|
||||||
|
#: model:ir.ui.view,arch_db:mass_mailing.view_mail_mass_mailing_form
|
||||||
|
msgid "This will send the email to all recipients. Do you still want to proceed ?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. module: mass_mailing
|
#. module: mass_mailing
|
||||||
#: model:ir.model.fields,field_description:mass_mailing.field_mail_mass_mailing_campaign_total
|
#: model:ir.model.fields,field_description:mass_mailing.field_mail_mass_mailing_campaign_total
|
||||||
#: model:ir.model.fields,field_description:mass_mailing.field_mail_mass_mailing_total
|
#: model:ir.model.fields,field_description:mass_mailing.field_mail_mass_mailing_total
|
||||||
@@ -1614,11 +1618,6 @@ msgstr ""
|
|||||||
msgid "We would like you to know that your order has shipped! To track your order or make any changes please click the \"my order\" button below."
|
msgid "We would like you to know that your order has shipped! To track your order or make any changes please click the \"my order\" button below."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: mass_mailing
|
|
||||||
#: model:ir.model.fields,field_description:mass_mailing.field_mail_mass_mailing_contact_website_message_ids
|
|
||||||
msgid "Website Messages"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: mass_mailing
|
#. module: mass_mailing
|
||||||
#: model:ir.model.fields,field_description:mass_mailing.field_mass_mailing_config_settings_group_website_popup_on_exit
|
#: model:ir.model.fields,field_description:mass_mailing.field_mass_mailing_config_settings_group_website_popup_on_exit
|
||||||
msgid "Website Pop-up"
|
msgid "Website Pop-up"
|
||||||
@@ -1634,11 +1633,6 @@ msgstr ""
|
|||||||
msgid "Website Popup Redirect URL"
|
msgid "Website Popup Redirect URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: mass_mailing
|
|
||||||
#: model:ir.model.fields,help:mass_mailing.field_mail_mass_mailing_contact_website_message_ids
|
|
||||||
msgid "Website communication history"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: mass_mailing
|
#. module: mass_mailing
|
||||||
#: model:mail.mass_mailing,body_html:mass_mailing.mass_mail_1
|
#: model:mail.mass_mailing,body_html:mass_mailing.mass_mail_1
|
||||||
msgid "You get a 20% discount for signing up!"
|
msgid "You get a 20% discount for signing up!"
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import urlparse
|
import urlparse
|
||||||
import re
|
|
||||||
import werkzeug.urls
|
import werkzeug.urls
|
||||||
|
|
||||||
from openerp import tools
|
from openerp import tools
|
||||||
|
|||||||
@@ -683,6 +683,8 @@ class MassMailing(osv.Model):
|
|||||||
value['mailing_domain'] = "[('list_id', 'in', %s), ('opt_out', '=', False)]" % list(mailing_list_ids)
|
value['mailing_domain'] = "[('list_id', 'in', %s), ('opt_out', '=', False)]" % list(mailing_list_ids)
|
||||||
else:
|
else:
|
||||||
value['mailing_domain'] = "[('list_id', '=', False)]"
|
value['mailing_domain'] = "[('list_id', '=', False)]"
|
||||||
|
elif 'opt_out' in self.pool[mailing_model]._fields:
|
||||||
|
value['mailing_domain'] = "[('opt_out', '=', False)]"
|
||||||
else:
|
else:
|
||||||
value['mailing_domain'] = []
|
value['mailing_domain'] = []
|
||||||
value['body_html'] = "on_change_model_and_list"
|
value['body_html'] = "on_change_model_and_list"
|
||||||
|
|||||||
@@ -379,7 +379,8 @@
|
|||||||
<form string="Mass Mailing">
|
<form string="Mass Mailing">
|
||||||
<header style="min-height:31px;">
|
<header style="min-height:31px;">
|
||||||
<button name="action_test_mailing" type="object" class="oe_highlight" string="Test Mailing"/>
|
<button name="action_test_mailing" type="object" class="oe_highlight" string="Test Mailing"/>
|
||||||
<button name="put_in_queue" type="object" attrs="{'invisible': ['|', ('state', 'in', ('in_queue', 'done'))]}" class="oe_highlight" string="Send to All"/>
|
<button name="put_in_queue" type="object" attrs="{'invisible': ['|', ('state', 'in', ('in_queue', 'done'))]}" class="oe_highlight" string="Send to All"
|
||||||
|
confirm="This will send the email to all recipients. Do you still want to proceed ?"/>
|
||||||
<button name="cancel_mass_mailing" type="object" attrs="{'invisible': [('state', '!=', 'in_queue')]}" class="oe_highlight" string="Cancel"/>
|
<button name="cancel_mass_mailing" type="object" attrs="{'invisible': [('state', '!=', 'in_queue')]}" class="oe_highlight" string="Cancel"/>
|
||||||
<button name="retry_failed_mail" type="object" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('failed', '=', 0)]}" class="oe_highlight" string="Retry"/>
|
<button name="retry_failed_mail" type="object" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('failed', '=', 0)]}" class="oe_highlight" string="Retry"/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-'8' "-*-"
|
# -*- coding: utf-'8' "-*-"
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
import logging
|
import logging
|
||||||
|
import urllib
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
from openerp.addons.payment.models.payment_acquirer import ValidationError
|
from openerp.addons.payment.models.payment_acquirer import ValidationError
|
||||||
@@ -12,6 +13,16 @@ from openerp.tools.translate import _
|
|||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_keys_upper(data):
|
||||||
|
"""Set all keys of a dictionnary to uppercase
|
||||||
|
|
||||||
|
Buckaroo parameters names are case insensitive
|
||||||
|
convert everything to upper case to be able to easily detected the presence
|
||||||
|
of a parameter by checking the uppercase key only
|
||||||
|
"""
|
||||||
|
return dict((key.upper(), val) for key, val in data.items())
|
||||||
|
|
||||||
|
|
||||||
class AcquirerBuckaroo(osv.Model):
|
class AcquirerBuckaroo(osv.Model):
|
||||||
_inherit = 'payment.acquirer'
|
_inherit = 'payment.acquirer'
|
||||||
|
|
||||||
@@ -61,17 +72,22 @@ class AcquirerBuckaroo(osv.Model):
|
|||||||
values = dict(values or {})
|
values = dict(values or {})
|
||||||
|
|
||||||
if inout == 'out':
|
if inout == 'out':
|
||||||
if 'BRQ_SIGNATURE' in values:
|
for key in values.keys():
|
||||||
del values['BRQ_SIGNATURE']
|
# case insensitive keys
|
||||||
items = sorted((k.upper(), v) for k, v in values.items())
|
if key.upper() == 'BRQ_SIGNATURE':
|
||||||
sign = ''.join('%s=%s' % (k, v) for k, v in items)
|
del values[key]
|
||||||
|
break
|
||||||
|
|
||||||
|
items = sorted(values.items(), key=lambda (x, y): x.lower())
|
||||||
|
sign = ''.join('%s=%s' % (k, urllib.unquote_plus(v)) for k, v in items)
|
||||||
else:
|
else:
|
||||||
sign = ''.join('%s=%s' % (k,get_value(k)) for k in keys)
|
sign = ''.join('%s=%s' % (k,get_value(k)) for k in keys)
|
||||||
#Add the pre-shared secret key at the end of the signature
|
#Add the pre-shared secret key at the end of the signature
|
||||||
sign = sign + acquirer.brq_secretkey
|
sign = sign + acquirer.brq_secretkey
|
||||||
if isinstance(sign, str):
|
if isinstance(sign, str):
|
||||||
|
# TODO: remove me? should not be used
|
||||||
sign = urlparse.parse_qsl(sign)
|
sign = urlparse.parse_qsl(sign)
|
||||||
shasign = sha1(sign).hexdigest()
|
shasign = sha1(sign.encode('utf-8')).hexdigest()
|
||||||
return shasign
|
return shasign
|
||||||
|
|
||||||
|
|
||||||
@@ -117,6 +133,8 @@ class TxBuckaroo(osv.Model):
|
|||||||
def _buckaroo_form_get_tx_from_data(self, cr, uid, data, context=None):
|
def _buckaroo_form_get_tx_from_data(self, cr, uid, data, context=None):
|
||||||
""" Given a data dict coming from buckaroo, verify it and find the related
|
""" Given a data dict coming from buckaroo, verify it and find the related
|
||||||
transaction record. """
|
transaction record. """
|
||||||
|
origin_data = dict(data)
|
||||||
|
data = normalize_keys_upper(data)
|
||||||
reference, pay_id, shasign = data.get('BRQ_INVOICENUMBER'), data.get('BRQ_PAYMENT'), data.get('BRQ_SIGNATURE')
|
reference, pay_id, shasign = data.get('BRQ_INVOICENUMBER'), data.get('BRQ_PAYMENT'), data.get('BRQ_SIGNATURE')
|
||||||
if not reference or not pay_id or not shasign:
|
if not reference or not pay_id or not shasign:
|
||||||
error_msg = _('Buckaroo: received data with missing reference (%s) or pay_id (%s) or shasign (%s)') % (reference, pay_id, shasign)
|
error_msg = _('Buckaroo: received data with missing reference (%s) or pay_id (%s) or shasign (%s)') % (reference, pay_id, shasign)
|
||||||
@@ -135,7 +153,7 @@ class TxBuckaroo(osv.Model):
|
|||||||
tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
|
tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
|
||||||
|
|
||||||
#verify shasign
|
#verify shasign
|
||||||
shasign_check = self.pool['payment.acquirer']._buckaroo_generate_digital_sign(tx.acquirer_id, 'out' ,data)
|
shasign_check = self.pool['payment.acquirer']._buckaroo_generate_digital_sign(tx.acquirer_id, 'out', origin_data)
|
||||||
if shasign_check.upper() != shasign.upper():
|
if shasign_check.upper() != shasign.upper():
|
||||||
error_msg = _('Buckaroo: invalid shasign, received %s, computed %s, for data %s') % (shasign, shasign_check, data)
|
error_msg = _('Buckaroo: invalid shasign, received %s, computed %s, for data %s') % (shasign, shasign_check, data)
|
||||||
_logger.info(error_msg)
|
_logger.info(error_msg)
|
||||||
@@ -145,7 +163,7 @@ class TxBuckaroo(osv.Model):
|
|||||||
|
|
||||||
def _buckaroo_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
|
def _buckaroo_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
|
||||||
invalid_parameters = []
|
invalid_parameters = []
|
||||||
|
data = normalize_keys_upper(data)
|
||||||
if tx.acquirer_reference and data.get('BRQ_TRANSACTIONS') != tx.acquirer_reference:
|
if tx.acquirer_reference and data.get('BRQ_TRANSACTIONS') != tx.acquirer_reference:
|
||||||
invalid_parameters.append(('Transaction Id', data.get('BRQ_TRANSACTIONS'), tx.acquirer_reference))
|
invalid_parameters.append(('Transaction Id', data.get('BRQ_TRANSACTIONS'), tx.acquirer_reference))
|
||||||
# check what is buyed
|
# check what is buyed
|
||||||
@@ -157,6 +175,7 @@ class TxBuckaroo(osv.Model):
|
|||||||
return invalid_parameters
|
return invalid_parameters
|
||||||
|
|
||||||
def _buckaroo_form_validate(self, cr, uid, tx, data, context=None):
|
def _buckaroo_form_validate(self, cr, uid, tx, data, context=None):
|
||||||
|
data = normalize_keys_upper(data)
|
||||||
status_code = int(data.get('BRQ_STATUSCODE','0'))
|
status_code = int(data.get('BRQ_STATUSCODE','0'))
|
||||||
if status_code in self._buckaroo_valid_tx_status:
|
if status_code in self._buckaroo_valid_tx_status:
|
||||||
tx.write({
|
tx.write({
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class BuckarooForm(BuckarooCommon):
|
|||||||
'BRQ_CURRENCY': u'EUR',
|
'BRQ_CURRENCY': u'EUR',
|
||||||
'BRQ_CUSTOMER_NAME': u'Jan de Tester',
|
'BRQ_CUSTOMER_NAME': u'Jan de Tester',
|
||||||
'BRQ_INVOICENUMBER': u'SO004',
|
'BRQ_INVOICENUMBER': u'SO004',
|
||||||
'BRQ_PAYMENT': u'573311D081B04069BD6336001611DBD4',
|
'brq_payment': u'573311D081B04069BD6336001611DBD4',
|
||||||
'BRQ_PAYMENT_METHOD': u'paypal',
|
'BRQ_PAYMENT_METHOD': u'paypal',
|
||||||
'BRQ_SERVICE_PAYPAL_PAYERCOUNTRY': u'NL',
|
'BRQ_SERVICE_PAYPAL_PAYERCOUNTRY': u'NL',
|
||||||
'BRQ_SERVICE_PAYPAL_PAYEREMAIL': u'fhe@openerp.com',
|
'BRQ_SERVICE_PAYPAL_PAYEREMAIL': u'fhe@openerp.com',
|
||||||
@@ -130,7 +130,7 @@ class BuckarooForm(BuckarooCommon):
|
|||||||
'BRQ_SERVICE_PAYPAL_PAYERLASTNAME': u'Tester',
|
'BRQ_SERVICE_PAYPAL_PAYERLASTNAME': u'Tester',
|
||||||
'BRQ_SERVICE_PAYPAL_PAYERMIDDLENAME': u'de',
|
'BRQ_SERVICE_PAYPAL_PAYERMIDDLENAME': u'de',
|
||||||
'BRQ_SERVICE_PAYPAL_PAYERSTATUS': u'verified',
|
'BRQ_SERVICE_PAYPAL_PAYERSTATUS': u'verified',
|
||||||
'BRQ_SIGNATURE': u'175d82dd53a02bad393fee32cb1eafa3b6fbbd91',
|
'Brq_signature': u'175d82dd53a02bad393fee32cb1eafa3b6fbbd91',
|
||||||
'BRQ_STATUSCODE': u'190',
|
'BRQ_STATUSCODE': u'190',
|
||||||
'BRQ_STATUSCODE_DETAIL': u'S001',
|
'BRQ_STATUSCODE_DETAIL': u'S001',
|
||||||
'BRQ_STATUSMESSAGE': u'Transaction successfully processed',
|
'BRQ_STATUSMESSAGE': u'Transaction successfully processed',
|
||||||
|
|||||||
@@ -645,7 +645,7 @@ class pos_order(osv.osv):
|
|||||||
_order = "id desc"
|
_order = "id desc"
|
||||||
|
|
||||||
def _amount_line_tax(self, cr, uid, line, fiscal_position_id, context=None):
|
def _amount_line_tax(self, cr, uid, line, fiscal_position_id, context=None):
|
||||||
taxes = line.product_id.taxes_id.filtered(lambda t: t.company_id.id == line.order_id.company_id.id)
|
taxes = line.tax_ids.filtered(lambda t: t.company_id.id == line.order_id.company_id.id)
|
||||||
if fiscal_position_id:
|
if fiscal_position_id:
|
||||||
taxes = fiscal_position_id.map_tax(taxes)
|
taxes = fiscal_position_id.map_tax(taxes)
|
||||||
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
|
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
|
||||||
@@ -1399,7 +1399,7 @@ class pos_order_line(osv.osv):
|
|||||||
account_tax_obj = self.pool.get('account.tax')
|
account_tax_obj = self.pool.get('account.tax')
|
||||||
for line in self.browse(cr, uid, ids, context=context):
|
for line in self.browse(cr, uid, ids, context=context):
|
||||||
cur = line.order_id.pricelist_id.currency_id
|
cur = line.order_id.pricelist_id.currency_id
|
||||||
taxes = [ tax for tax in line.product_id.taxes_id if tax.company_id.id == line.order_id.company_id.id ]
|
taxes = [ tax for tax in line.tax_ids if tax.company_id.id == line.order_id.company_id.id ]
|
||||||
fiscal_position_id = line.order_id.fiscal_position_id
|
fiscal_position_id = line.order_id.fiscal_position_id
|
||||||
if fiscal_position_id:
|
if fiscal_position_id:
|
||||||
taxes = fiscal_position_id.map_tax(taxes)
|
taxes = fiscal_position_id.map_tax(taxes)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ odoo.define('point_of_sale.gui', function (require) {
|
|||||||
|
|
||||||
var core = require('web.core');
|
var core = require('web.core');
|
||||||
var Model = require('web.DataModel');
|
var Model = require('web.DataModel');
|
||||||
|
var formats = require('web.formats');
|
||||||
|
|
||||||
var _t = core._t;
|
var _t = core._t;
|
||||||
|
|
||||||
@@ -411,14 +412,16 @@ var Gui = core.Class.extend({
|
|||||||
numpad_input: function(buffer, input, options) {
|
numpad_input: function(buffer, input, options) {
|
||||||
var newbuf = buffer.slice(0);
|
var newbuf = buffer.slice(0);
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
var newbuf_float = formats.parse_value(newbuf, {type: "float"}, 0);
|
||||||
|
var decimal_point = _t.database.parameters.decimal_point;
|
||||||
|
|
||||||
if (input === '.') {
|
if (input === decimal_point) {
|
||||||
if (options.firstinput) {
|
if (options.firstinput) {
|
||||||
newbuf = "0.";
|
newbuf = "0.";
|
||||||
}else if (!newbuf.length || newbuf === '-') {
|
}else if (!newbuf.length || newbuf === '-') {
|
||||||
newbuf += "0.";
|
newbuf += "0.";
|
||||||
} else if (newbuf.indexOf('.') < 0){
|
} else if (newbuf.indexOf(decimal_point) < 0){
|
||||||
newbuf = newbuf + '.';
|
newbuf = newbuf + decimal_point;
|
||||||
}
|
}
|
||||||
} else if (input === 'CLEAR') {
|
} else if (input === 'CLEAR') {
|
||||||
newbuf = "";
|
newbuf = "";
|
||||||
@@ -435,7 +438,7 @@ var Gui = core.Class.extend({
|
|||||||
newbuf = '-' + newbuf;
|
newbuf = '-' + newbuf;
|
||||||
}
|
}
|
||||||
} else if (input[0] === '+' && !isNaN(parseFloat(input))) {
|
} else if (input[0] === '+' && !isNaN(parseFloat(input))) {
|
||||||
newbuf = '' + ((parseFloat(newbuf) || 0) + parseFloat(input));
|
newbuf = this.chrome.format_currency_no_symbol(newbuf_float + parseFloat(input));
|
||||||
} else if (!isNaN(parseInt(input))) {
|
} else if (!isNaN(parseInt(input))) {
|
||||||
if (options.firstinput) {
|
if (options.firstinput) {
|
||||||
newbuf = '' + input;
|
newbuf = '' + input;
|
||||||
|
|||||||
@@ -1092,10 +1092,11 @@ exports.Orderline = Backbone.Model.extend({
|
|||||||
clone: function(){
|
clone: function(){
|
||||||
var orderline = new exports.Orderline({},{
|
var orderline = new exports.Orderline({},{
|
||||||
pos: this.pos,
|
pos: this.pos,
|
||||||
order: null,
|
order: this.order,
|
||||||
product: this.product,
|
product: this.product,
|
||||||
price: this.price,
|
price: this.price,
|
||||||
});
|
});
|
||||||
|
orderline.order = null;
|
||||||
orderline.quantity = this.quantity;
|
orderline.quantity = this.quantity;
|
||||||
orderline.quantityStr = this.quantityStr;
|
orderline.quantityStr = this.quantityStr;
|
||||||
orderline.discount = this.discount;
|
orderline.discount = this.discount;
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ var NumberPopupWidget = PopupWidget.extend({
|
|||||||
this._super(options);
|
this._super(options);
|
||||||
|
|
||||||
this.inputbuffer = '' + (options.value || '');
|
this.inputbuffer = '' + (options.value || '');
|
||||||
|
this.decimal_separator = _t.database.parameters.decimal_point;
|
||||||
this.renderElement();
|
this.renderElement();
|
||||||
this.firstinput = true;
|
this.firstinput = true;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1300,7 +1300,7 @@ var ClientListScreenWidget = ScreenWidget.extend({
|
|||||||
contents.append($(QWeb.render('ClientDetailsEdit',{widget:this,partner:partner})));
|
contents.append($(QWeb.render('ClientDetailsEdit',{widget:this,partner:partner})));
|
||||||
this.toggle_save_button();
|
this.toggle_save_button();
|
||||||
|
|
||||||
contents.find('.image-uploader').on('change',function(){
|
contents.find('.image-uploader').on('change',function(event){
|
||||||
self.load_image_file(event.target.files[0],function(res){
|
self.load_image_file(event.target.files[0],function(res){
|
||||||
if (res) {
|
if (res) {
|
||||||
contents.find('.client-picture img, .client-picture .fa').remove();
|
contents.find('.client-picture img, .client-picture .fa').remove();
|
||||||
@@ -1519,7 +1519,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
|
|||||||
event.keyCode === 110 || // Decimal point (numpad)
|
event.keyCode === 110 || // Decimal point (numpad)
|
||||||
event.keyCode === 188 || // Comma
|
event.keyCode === 188 || // Comma
|
||||||
event.keyCode === 46 ) { // Numpad dot
|
event.keyCode === 46 ) { // Numpad dot
|
||||||
key = '.';
|
key = self.decimal_point;
|
||||||
} else if (event.keyCode >= 48 && event.keyCode <= 57) { // Numbers
|
} else if (event.keyCode >= 48 && event.keyCode <= 57) { // Numbers
|
||||||
key = '' + (event.keyCode - 48);
|
key = '' + (event.keyCode - 48);
|
||||||
} else if (event.keyCode === 45) { // Minus
|
} else if (event.keyCode === 45) { // Minus
|
||||||
@@ -1578,7 +1578,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
|
|||||||
order.selected_paymentline.set_amount(amount);
|
order.selected_paymentline.set_amount(amount);
|
||||||
this.order_changes();
|
this.order_changes();
|
||||||
this.render_paymentlines();
|
this.render_paymentlines();
|
||||||
this.$('.paymentline.selected .edit').text(this.inputbuffer);
|
this.$('.paymentline.selected .edit').text(this.format_currency_no_symbol(amount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1704,9 +1704,9 @@ var PaymentScreenWidget = ScreenWidget.extend({
|
|||||||
|
|
||||||
this.gui.show_popup('number',{
|
this.gui.show_popup('number',{
|
||||||
'title': tip ? _t('Change Tip') : _t('Add Tip'),
|
'title': tip ? _t('Change Tip') : _t('Add Tip'),
|
||||||
'value': value,
|
'value': self.format_currency_no_symbol(value),
|
||||||
'confirm': function(value) {
|
'confirm': function(value) {
|
||||||
order.set_tip(Number(value));
|
order.set_tip(formats.parse_value(value, {type: "float"}, 0));
|
||||||
self.order_changes();
|
self.order_changes();
|
||||||
self.render_paymentlines();
|
self.render_paymentlines();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -559,7 +559,7 @@
|
|||||||
<br />
|
<br />
|
||||||
<button class="input-button numpad-char" data-action='CLEAR' >C</button>
|
<button class="input-button numpad-char" data-action='CLEAR' >C</button>
|
||||||
<button class="input-button number-char" data-action='0'>0</button>
|
<button class="input-button number-char" data-action='0'>0</button>
|
||||||
<button class="input-button number-char" data-action='.'>.</button>
|
<button class="input-button number-char" t-att-data-action='widget.decimal_point'><t t-esc='widget.decimal_point'/></button>
|
||||||
<button class="input-button numpad-backspace" data-action='BACKSPACE' >
|
<button class="input-button numpad-backspace" data-action='BACKSPACE' >
|
||||||
<img src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" />
|
<img src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" />
|
||||||
</button>
|
</button>
|
||||||
@@ -702,9 +702,6 @@
|
|||||||
<div>Served by <t t-esc='receipt.cashier' /></div>
|
<div>Served by <t t-esc='receipt.cashier' /></div>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-if='!receipt.customer_count'>
|
|
||||||
<div>Guests: <t t-esc='receipt.customer_count' /></div>
|
|
||||||
</t>
|
|
||||||
</div>
|
</div>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
@@ -1006,7 +1003,7 @@
|
|||||||
<br />
|
<br />
|
||||||
<button class="input-button numpad-char" data-action='CLEAR' >C</button>
|
<button class="input-button numpad-char" data-action='CLEAR' >C</button>
|
||||||
<button class="input-button number-char" data-action='0'>0</button>
|
<button class="input-button number-char" data-action='0'>0</button>
|
||||||
<button class="input-button number-char dot" data-action='.'>.</button>
|
<button class="input-button number-char dot" t-att-data-action='widget.decimal_separator'><t t-esc='widget.decimal_separator'/></button>
|
||||||
<button class="input-button numpad-backspace" data-action='BACKSPACE' >
|
<button class="input-button numpad-backspace" data-action='BACKSPACE' >
|
||||||
<img style="pointer-events: none;" src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" />
|
<img style="pointer-events: none;" src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" />
|
||||||
</button>
|
</button>
|
||||||
@@ -1298,7 +1295,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Subtotal:</td>
|
<td>Subtotal:</td>
|
||||||
<td class="pos-right-align">
|
<td class="pos-right-align">
|
||||||
<t t-esc="widget.format_currency(order.get_subtotal())"/>
|
<t t-esc="widget.format_currency(order.get_total_without_tax())"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<t t-foreach="order.get_tax_details()" t-as="taxdetail">
|
<t t-foreach="order.get_tax_details()" t-as="taxdetail">
|
||||||
|
|||||||
@@ -3,4 +3,3 @@ data_dir = /var/run/odoo
|
|||||||
log_level = error
|
log_level = error
|
||||||
logfile = /var/log/odoo/odoo-server.log
|
logfile = /var/log/odoo/odoo-server.log
|
||||||
pidfile = /var/run/odoo/odoo.pid
|
pidfile = /var/run/odoo/odoo.pid
|
||||||
server_wide_modules = web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos
|
|
||||||
@@ -26,7 +26,7 @@ test -x $DAEMON || exit 0
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
function _start() {
|
function _start() {
|
||||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos
|
start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos,hw_blackbox_be
|
||||||
}
|
}
|
||||||
|
|
||||||
function _stop() {
|
function _stop() {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
<t t-if='receipt.table'>
|
<t t-if='receipt.table'>
|
||||||
at table <t t-esc='receipt.table' />
|
at table <t t-esc='receipt.table' />
|
||||||
</t>
|
</t>
|
||||||
|
<t t-if='receipt.customer_count'>
|
||||||
|
<div>Guests: <t t-esc='receipt.customer_count' /></div>
|
||||||
|
</t>
|
||||||
</t>
|
</t>
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
@@ -14,6 +17,9 @@
|
|||||||
<t t-if='receipt.table'>
|
<t t-if='receipt.table'>
|
||||||
at table <t t-esc='receipt.table' />
|
at table <t t-esc='receipt.table' />
|
||||||
</t>
|
</t>
|
||||||
|
<t t-if='receipt.customer_count'>
|
||||||
|
<div>Guests: <t t-esc='receipt.customer_count' /></div>
|
||||||
|
</t>
|
||||||
</t>
|
</t>
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
|
|||||||
@@ -571,7 +571,7 @@ class product_template(osv.osv):
|
|||||||
if ptype != 'standard_price':
|
if ptype != 'standard_price':
|
||||||
res[product.id] = product[ptype] or 0.0
|
res[product.id] = product[ptype] or 0.0
|
||||||
else:
|
else:
|
||||||
company_id = product.env.user.company_id.id
|
company_id = context.get('force_company') or product.env.user.company_id.id
|
||||||
product = product.with_context(force_company=company_id)
|
product = product.with_context(force_company=company_id)
|
||||||
res[product.id] = res[product.id] = product.sudo()[ptype]
|
res[product.id] = res[product.id] = product.sudo()[ptype]
|
||||||
if ptype == 'list_price':
|
if ptype == 'list_price':
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from openerp import api, fields, models, _
|
from openerp import api, fields, models, _, SUPERUSER_ID
|
||||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
from openerp.tools.float_utils import float_is_zero, float_compare
|
from openerp.tools.float_utils import float_is_zero, float_compare
|
||||||
@@ -150,7 +150,7 @@ class PurchaseOrder(models.Model):
|
|||||||
picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=READONLY_STATES, required=True, default=_default_picking_type,\
|
picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=READONLY_STATES, required=True, default=_default_picking_type,\
|
||||||
help="This will determine picking type of incoming shipment")
|
help="This will determine picking type of incoming shipment")
|
||||||
default_location_dest_id_usage = fields.Selection(related='picking_type_id.default_location_dest_id.usage', string='Destination Location Type',\
|
default_location_dest_id_usage = fields.Selection(related='picking_type_id.default_location_dest_id.usage', string='Destination Location Type',\
|
||||||
help="Technical field used to display the Drop Ship Address")
|
help="Technical field used to display the Drop Ship Address", readonly=True)
|
||||||
group_id = fields.Many2one('procurement.group', string="Procurement Group")
|
group_id = fields.Many2one('procurement.group', string="Procurement Group")
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
@@ -434,6 +434,7 @@ class PurchaseOrderLine(models.Model):
|
|||||||
|
|
||||||
@api.depends('order_id.state', 'move_ids.state')
|
@api.depends('order_id.state', 'move_ids.state')
|
||||||
def _compute_qty_received(self):
|
def _compute_qty_received(self):
|
||||||
|
ProductUom = self.env['product.uom']
|
||||||
for line in self:
|
for line in self:
|
||||||
if line.order_id.state not in ['purchase', 'done']:
|
if line.order_id.state not in ['purchase', 'done']:
|
||||||
line.qty_received = 0.0
|
line.qty_received = 0.0
|
||||||
@@ -444,7 +445,10 @@ class PurchaseOrderLine(models.Model):
|
|||||||
total = 0.0
|
total = 0.0
|
||||||
for move in line.move_ids:
|
for move in line.move_ids:
|
||||||
if move.state == 'done':
|
if move.state == 'done':
|
||||||
total += move.product_uom_qty
|
if move.product_uom != line.product_uom:
|
||||||
|
total += ProductUom._compute_qty_obj(move.product_uom, move.product_uom_qty, line.product_uom)
|
||||||
|
else:
|
||||||
|
total += move.product_uom_qty
|
||||||
line.qty_received = total
|
line.qty_received = total
|
||||||
|
|
||||||
name = fields.Text(string='Description', required=True)
|
name = fields.Text(string='Description', required=True)
|
||||||
@@ -581,7 +585,11 @@ class PurchaseOrderLine(models.Model):
|
|||||||
self.name += '\n' + product_lang.description_purchase
|
self.name += '\n' + product_lang.description_purchase
|
||||||
|
|
||||||
fpos = self.order_id.fiscal_position_id
|
fpos = self.order_id.fiscal_position_id
|
||||||
self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id)
|
if self.env.uid == SUPERUSER_ID:
|
||||||
|
company_id = self.env.user.company_id.id
|
||||||
|
self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id.filtered(lambda r: r.company_id.id == company_id))
|
||||||
|
else:
|
||||||
|
self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id)
|
||||||
|
|
||||||
self._suggest_quantity()
|
self._suggest_quantity()
|
||||||
self._onchange_quantity()
|
self._onchange_quantity()
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ class stock_picking(osv.osv):
|
|||||||
readonly=True, relation="many2one"),
|
readonly=True, relation="many2one"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _prepare_values_extra_move(self, cr, uid, op, product, remaining_qty, context=None):
|
||||||
|
res = super(stock_picking, self)._prepare_values_extra_move(cr, uid, op, product, remaining_qty, context=context)
|
||||||
|
for m in op.linked_move_operation_ids:
|
||||||
|
if m.move_id.purchase_line_id and m.move_id.product_id == product:
|
||||||
|
res['purchase_line_id'] = m.move_id.purchase_line_id.id
|
||||||
|
break
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
class stock_move(osv.osv):
|
class stock_move(osv.osv):
|
||||||
_inherit = 'stock.move'
|
_inherit = 'stock.move'
|
||||||
|
|||||||
+11
-1
@@ -740,7 +740,8 @@ class SaleOrderLine(models.Model):
|
|||||||
quantity=self.product_uom_qty,
|
quantity=self.product_uom_qty,
|
||||||
date_order=self.order_id.date_order,
|
date_order=self.order_id.date_order,
|
||||||
pricelist=self.order_id.pricelist_id.id,
|
pricelist=self.order_id.pricelist_id.id,
|
||||||
uom=self.product_uom.id
|
uom=self.product_uom.id,
|
||||||
|
fiscal_position=self.env.context.get('fiscal_position')
|
||||||
)
|
)
|
||||||
self.price_unit = self.env['account.tax']._fix_tax_included_price(product.price, product.taxes_id, self.tax_id)
|
self.price_unit = self.env['account.tax']._fix_tax_included_price(product.price, product.taxes_id, self.tax_id)
|
||||||
|
|
||||||
@@ -750,6 +751,15 @@ class SaleOrderLine(models.Model):
|
|||||||
raise UserError(_('You can not remove a sale order line.\nDiscard changes and try setting the quantity to 0.'))
|
raise UserError(_('You can not remove a sale order line.\nDiscard changes and try setting the quantity to 0.'))
|
||||||
return super(SaleOrderLine, self).unlink()
|
return super(SaleOrderLine, self).unlink()
|
||||||
|
|
||||||
|
def onchange_product_uom(self, cursor, user, ids, pricelist, product, qty=0,
|
||||||
|
uom=False, qty_uos=0, uos=False, name='', partner_id=False,
|
||||||
|
lang=False, update_tax=True, date_order=False, fiscal_position=False, context=None):
|
||||||
|
ctx = dict(context or {}, fiscal_position=fiscal_position)
|
||||||
|
return self.product_uom_change(cursor, user, ids, pricelist, product,
|
||||||
|
qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
|
||||||
|
partner_id=partner_id, lang=lang, update_tax=update_tax,
|
||||||
|
date_order=date_order, context=ctx)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _get_delivered_qty(self):
|
def _get_delivered_qty(self):
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ class SaleOrderLine(models.Model):
|
|||||||
def _compute_analytic(self, domain=None):
|
def _compute_analytic(self, domain=None):
|
||||||
lines = {}
|
lines = {}
|
||||||
if not domain:
|
if not domain:
|
||||||
domain = [('so_line', 'in', self.ids), ('unit_amount', '<=', 0.0)]
|
# To filter on analyic lines linked to an expense
|
||||||
|
domain = [('so_line', 'in', self.ids), ('amount', '<=', 0.0)]
|
||||||
data = self.env['account.analytic.line'].read_group(
|
data = self.env['account.analytic.line'].read_group(
|
||||||
domain,
|
domain,
|
||||||
['so_line', 'unit_amount', 'product_uom_id'], ['product_uom_id', 'so_line'], lazy=False
|
['so_line', 'unit_amount', 'product_uom_id'], ['product_uom_id', 'so_line'], lazy=False
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from openerp import api, fields, models, _
|
from openerp import api, fields, models, _
|
||||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare
|
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare
|
||||||
|
from openerp.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
class SaleOrder(models.Model):
|
class SaleOrder(models.Model):
|
||||||
@@ -203,7 +204,7 @@ class SaleOrderLine(models.Model):
|
|||||||
mto_route_id = False
|
mto_route_id = False
|
||||||
try:
|
try:
|
||||||
mto_route_id = self.env['stock.warehouse']._get_mto_route()
|
mto_route_id = self.env['stock.warehouse']._get_mto_route()
|
||||||
except models.except_orm:
|
except UserError:
|
||||||
# if route MTO not found in ir_model_data, we treat the product as in MTS
|
# if route MTO not found in ir_model_data, we treat the product as in MTS
|
||||||
pass
|
pass
|
||||||
if mto_route_id and mto_route_id in product_routes.ids:
|
if mto_route_id and mto_route_id in product_routes.ids:
|
||||||
|
|||||||
@@ -158,7 +158,8 @@ class SaleOrderLine(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def _compute_analytic(self, domain=None):
|
def _compute_analytic(self, domain=None):
|
||||||
if not domain:
|
if not domain:
|
||||||
domain = [('so_line', 'in', self.ids), '|', ('unit_amount', '<=', 0.0), ('is_timesheet', '=', True)]
|
# To filter on analyic lines linked to an expense
|
||||||
|
domain = [('so_line', 'in', self.ids), '|', ('amount', '<=', 0.0), ('is_timesheet', '=', True)]
|
||||||
return super(SaleOrderLine, self)._compute_analytic(domain=domain)
|
return super(SaleOrderLine, self)._compute_analytic(domain=domain)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
|
|||||||
@@ -1556,7 +1556,6 @@ class stock_picking(models.Model):
|
|||||||
for pack in pick.pack_operation_ids:
|
for pack in pick.pack_operation_ids:
|
||||||
if pack.product_id and pack.product_id.tracking != 'none':
|
if pack.product_id and pack.product_id.tracking != 'none':
|
||||||
raise UserError(_('Some products require lots, so you need to specify those first!'))
|
raise UserError(_('Some products require lots, so you need to specify those first!'))
|
||||||
|
|
||||||
view = data_obj.xmlid_to_res_id(cr, uid, 'stock.view_immediate_transfer')
|
view = data_obj.xmlid_to_res_id(cr, uid, 'stock.view_immediate_transfer')
|
||||||
wiz_id = self.pool['stock.immediate.transfer'].create(cr, uid, {'pick_id': pick.id}, context=context)
|
wiz_id = self.pool['stock.immediate.transfer'].create(cr, uid, {'pick_id': pick.id}, context=context)
|
||||||
return {
|
return {
|
||||||
@@ -4206,7 +4205,7 @@ class stock_package(osv.osv):
|
|||||||
quant_obj = self.pool.get('stock.quant')
|
quant_obj = self.pool.get('stock.quant')
|
||||||
for package in self.browse(cr, uid, ids, context=context):
|
for package in self.browse(cr, uid, ids, context=context):
|
||||||
quant_ids = [quant.id for quant in package.quant_ids]
|
quant_ids = [quant.id for quant in package.quant_ids]
|
||||||
quant_obj.write(cr, uid, quant_ids, {'package_id': package.parent_id.id or False}, context=context)
|
quant_obj.write(cr, SUPERUSER_ID, quant_ids, {'package_id': package.parent_id.id or False}, context=context)
|
||||||
children_package_ids = [child_package.id for child_package in package.children_ids]
|
children_package_ids = [child_package.id for child_package in package.children_ids]
|
||||||
self.write(cr, uid, children_package_ids, {'parent_id': package.parent_id.id or False}, context=context)
|
self.write(cr, uid, children_package_ids, {'parent_id': package.parent_id.id or False}, context=context)
|
||||||
#delete current package since it contains nothing anymore
|
#delete current package since it contains nothing anymore
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ class stock_move(osv.osv):
|
|||||||
def _store_average_cost_price(self, cr, uid, move, context=None):
|
def _store_average_cost_price(self, cr, uid, move, context=None):
|
||||||
''' move is a browe record '''
|
''' move is a browe record '''
|
||||||
product_obj = self.pool.get('product.product')
|
product_obj = self.pool.get('product.product')
|
||||||
if any([q.qty <= 0 for q in move.quant_ids]):
|
if any([q.qty <= 0 for q in move.quant_ids]) or move.product_qty == 0:
|
||||||
#if there is a negative quant, the standard price shouldn't be updated
|
#if there is a negative quant, the standard price shouldn't be updated
|
||||||
return
|
return
|
||||||
#Note: here we can't store a quant.cost directly as we may have moved out 2 units (1 unit to 5€ and 1 unit to 7€) and in case of a product return of 1 unit, we can't know which of the 2 costs has to be used (5€ or 7€?). So at that time, thanks to the average valuation price we are storing we will valuate it at 6€
|
#Note: here we can't store a quant.cost directly as we may have moved out 2 units (1 unit to 5€ and 1 unit to 7€) and in case of a product return of 1 unit, we can't know which of the 2 costs has to be used (5€ or 7€?). So at that time, thanks to the average valuation price we are storing we will valuate it at 6€
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ class procurement_order(osv.osv):
|
|||||||
date_planned = datetime.strptime(procurement.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
|
date_planned = datetime.strptime(procurement.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
purchase_date, delivery_date = self._get_previous_dates(cr, uid, orderpoint, date_planned, context=context)
|
purchase_date, delivery_date = self._get_previous_dates(cr, uid, orderpoint, date_planned, context=context)
|
||||||
if purchase_date and delivery_date:
|
if purchase_date and delivery_date:
|
||||||
self.write(cr, uid, {'next_delivery_date': self._convert_to_UTC(cr, uid, delivery_date, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
self.write(cr, uid, {'next_delivery_date': delivery_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||||
'next_purchase_date': self._convert_to_UTC(cr, uid, purchase_date, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT),}, context=context)
|
'next_purchase_date': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),}, context=context)
|
||||||
|
|
||||||
@api.v8
|
@api.v8
|
||||||
def _get_purchase_order_date(self, schedule_date):
|
def _get_purchase_order_date(self, schedule_date):
|
||||||
@@ -105,8 +105,6 @@ class procurement_order(osv.osv):
|
|||||||
att_obj = self.pool.get('resource.calendar.attendance')
|
att_obj = self.pool.get('resource.calendar.attendance')
|
||||||
context = context or {}
|
context = context or {}
|
||||||
context['no_round_hours'] = True
|
context['no_round_hours'] = True
|
||||||
# Date should be converted to the correct timezone
|
|
||||||
start_date = self._convert_to_tz(cr, uid, start_date, context=context)
|
|
||||||
# First check if the orderpoint has a Calendar as it should be delivered at this calendar date
|
# First check if the orderpoint has a Calendar as it should be delivered at this calendar date
|
||||||
purchase_date = False
|
purchase_date = False
|
||||||
delivery_date = start_date
|
delivery_date = start_date
|
||||||
@@ -135,8 +133,8 @@ class procurement_order(osv.osv):
|
|||||||
context = context or {}
|
context = context or {}
|
||||||
context['no_round_hours'] = True
|
context['no_round_hours'] = True
|
||||||
if not new_date:
|
if not new_date:
|
||||||
new_date = self._convert_to_tz(cr, uid, datetime.utcnow(), context=context)
|
new_date = datetime.utcnow()
|
||||||
now_date = self._convert_to_tz(cr, uid, datetime.utcnow(), context=context)
|
now_date = datetime.utcnow()
|
||||||
|
|
||||||
# Search first calendar day (without group)
|
# Search first calendar day (without group)
|
||||||
res = calendar_obj._schedule_days(cr, uid, orderpoint.calendar_id.id, 1, new_date, compute_leaves=True, context=context)
|
res = calendar_obj._schedule_days(cr, uid, orderpoint.calendar_id.id, 1, new_date, compute_leaves=True, context=context)
|
||||||
@@ -178,21 +176,6 @@ class procurement_order(osv.osv):
|
|||||||
return (date1, res[0][1])
|
return (date1, res[0][1])
|
||||||
return (False, False)
|
return (False, False)
|
||||||
|
|
||||||
def _convert_to_tz(self, cr, uid, date, context=None):
|
|
||||||
if not context or not context.get('tz'):
|
|
||||||
return date
|
|
||||||
utc_date = pytz.UTC.localize(date)
|
|
||||||
timezone = pytz.timezone(context['tz'])
|
|
||||||
return utc_date.astimezone(timezone)
|
|
||||||
|
|
||||||
def _convert_to_UTC(self, cr, uid, date, context=None):
|
|
||||||
"""
|
|
||||||
The date should be timezone aware
|
|
||||||
"""
|
|
||||||
if not context or not context.get('tz'):
|
|
||||||
return date
|
|
||||||
return date.astimezone(pytz.UTC)
|
|
||||||
|
|
||||||
def _get_group(self, cr, uid, orderpoint, context=None):
|
def _get_group(self, cr, uid, orderpoint, context=None):
|
||||||
"""
|
"""
|
||||||
Will return the groups and the end dates of the intervals of the purchase calendar
|
Will return the groups and the end dates of the intervals of the purchase calendar
|
||||||
@@ -207,15 +190,13 @@ class procurement_order(osv.osv):
|
|||||||
context = context or {}
|
context = context or {}
|
||||||
context['no_round_hours'] = True
|
context['no_round_hours'] = True
|
||||||
date = False
|
date = False
|
||||||
now_date = self._convert_to_tz(cr, uid, datetime.utcnow(), context=context)
|
now_date = datetime.utcnow()
|
||||||
res_intervals = []
|
res_intervals = []
|
||||||
if orderpoint.purchase_calendar_id and orderpoint.purchase_calendar_id.attendance_ids:
|
if orderpoint.purchase_calendar_id and orderpoint.purchase_calendar_id.attendance_ids:
|
||||||
if orderpoint.last_execution_date:
|
if orderpoint.last_execution_date:
|
||||||
new_date = datetime.strptime(orderpoint.last_execution_date, DEFAULT_SERVER_DATETIME_FORMAT)
|
new_date = datetime.strptime(orderpoint.last_execution_date, DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
else:
|
else:
|
||||||
new_date = datetime.utcnow()
|
new_date = datetime.utcnow()
|
||||||
# Convert to timezone of user
|
|
||||||
new_date = self._convert_to_tz(cr, uid, new_date, context=context)
|
|
||||||
intervals = calendar_obj._schedule_days(cr, uid, orderpoint.purchase_calendar_id.id, 1, new_date, compute_leaves=True, context=context)
|
intervals = calendar_obj._schedule_days(cr, uid, orderpoint.purchase_calendar_id.id, 1, new_date, compute_leaves=True, context=context)
|
||||||
for interval in intervals:
|
for interval in intervals:
|
||||||
# If last execution date, interval should start after it in order not to execute the same orderpoint twice
|
# If last execution date, interval should start after it in order not to execute the same orderpoint twice
|
||||||
@@ -285,8 +266,8 @@ class procurement_order(osv.osv):
|
|||||||
date = res_group[1]
|
date = res_group[1]
|
||||||
subtract_qty = orderpoint_obj.subtract_procurements_from_orderpoints(cr, uid, [x.id for x in ops_dict[key]], context=context)
|
subtract_qty = orderpoint_obj.subtract_procurements_from_orderpoints(cr, uid, [x.id for x in ops_dict[key]], context=context)
|
||||||
first_op = True
|
first_op = True
|
||||||
ndelivery = date and self._convert_to_UTC(cr, uid, date, context=context) or False
|
ndelivery = date
|
||||||
npurchase = res_group[3] and self._convert_to_UTC(cr, uid, res_group[3], context=context) or False
|
npurchase = res_group[3]
|
||||||
for op in ops_dict[key]:
|
for op in ops_dict[key]:
|
||||||
try:
|
try:
|
||||||
prods = prod_qty[op.product_id.id]['virtual_available']
|
prods = prod_qty[op.product_id.id]['virtual_available']
|
||||||
|
|||||||
@@ -48,21 +48,16 @@ class resource_calendar(osv.osv):
|
|||||||
leave intervals
|
leave intervals
|
||||||
"""
|
"""
|
||||||
resource_calendar = self.browse(cr, uid, id, context=context)
|
resource_calendar = self.browse(cr, uid, id, context=context)
|
||||||
proc_obj = self.pool.get("procurement.order")
|
|
||||||
leaves = []
|
leaves = []
|
||||||
for leave in resource_calendar.leave_ids:
|
for leave in resource_calendar.leave_ids:
|
||||||
if leave.resource_id and not resource_id == leave.resource_id.id:
|
if leave.resource_id and not resource_id == leave.resource_id.id:
|
||||||
continue
|
continue
|
||||||
date_from_db = datetime.datetime.strptime(leave.date_from, DEFAULT_SERVER_DATETIME_FORMAT)
|
date_from = datetime.datetime.strptime(leave.date_from, DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
date_from = proc_obj._convert_to_tz(cr, uid, date_from_db, context=context)
|
|
||||||
if end_datetime and date_from > end_datetime:
|
if end_datetime and date_from > end_datetime:
|
||||||
continue
|
continue
|
||||||
|
date_to = datetime.datetime.strptime(leave.date_to, DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
date_to_db = datetime.datetime.strptime(leave.date_to, DEFAULT_SERVER_DATETIME_FORMAT)
|
|
||||||
date_to = proc_obj._convert_to_tz(cr, uid, date_to_db, context=context)
|
|
||||||
if start_datetime and date_to < start_datetime:
|
if start_datetime and date_to < start_datetime:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
leaves.append((date_from, date_to, leave.group_id.id))
|
leaves.append((date_from, date_to, leave.group_id.id))
|
||||||
return leaves
|
return leaves
|
||||||
|
|
||||||
|
|||||||
@@ -1481,7 +1481,7 @@ class Reports(http.Controller):
|
|||||||
if 'name' not in action:
|
if 'name' not in action:
|
||||||
reports = request.session.model('ir.actions.report.xml')
|
reports = request.session.model('ir.actions.report.xml')
|
||||||
res_id = reports.search([('report_name', '=', action['report_name']),],
|
res_id = reports.search([('report_name', '=', action['report_name']),],
|
||||||
0, False, False, context)
|
context=context)
|
||||||
if len(res_id) > 0:
|
if len(res_id) > 0:
|
||||||
file_name = reports.read(res_id[0], ['name'], context)['name']
|
file_name = reports.read(res_id[0], ['name'], context)['name']
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -843,7 +843,7 @@ var CalendarView = View.extend({
|
|||||||
var index = this.dataset.get_id_index(id);
|
var index = this.dataset.get_id_index(id);
|
||||||
if (index !== null) {
|
if (index !== null) {
|
||||||
event_id = this.dataset.ids[index];
|
event_id = this.dataset.ids[index];
|
||||||
this.dataset.write(event_id, data, {}).done(function() {
|
this.dataset.write(event_id, data, {}).always(function() {
|
||||||
if (is_virtual_id(event_id)) {
|
if (is_virtual_id(event_id)) {
|
||||||
// this is a virtual ID and so this will create a new event
|
// this is a virtual ID and so this will create a new event
|
||||||
// with an unknown id for us.
|
// with an unknown id for us.
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ var FieldTextHtml = widget.extend({
|
|||||||
return def;
|
return def;
|
||||||
},
|
},
|
||||||
get_url: function (_attr) {
|
get_url: function (_attr) {
|
||||||
var src = this.options.editor_url ? this.options.editor_url+"?" : "/web_editor/field/html?";
|
var src = this.options.editor_url || "/web_editor/field/html";
|
||||||
var datarecord = this.view.get_fields_values();
|
var datarecord = this.view.get_fields_values();
|
||||||
|
|
||||||
var attr = {
|
var attr = {
|
||||||
@@ -211,6 +211,9 @@ var FieldTextHtml = widget.extend({
|
|||||||
if (this.options.snippets) {
|
if (this.options.snippets) {
|
||||||
attr.snippets = this.options.snippets;
|
attr.snippets = this.options.snippets;
|
||||||
}
|
}
|
||||||
|
if (this.options.template) {
|
||||||
|
attr.template = this.options.template;
|
||||||
|
}
|
||||||
if (!this.get("effective_readonly")) {
|
if (!this.get("effective_readonly")) {
|
||||||
attr.enable_editor = 1;
|
attr.enable_editor = 1;
|
||||||
}
|
}
|
||||||
@@ -227,6 +230,10 @@ var FieldTextHtml = widget.extend({
|
|||||||
attr[k] = _attr[k];
|
attr[k] = _attr[k];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (src.indexOf('?') === -1) {
|
||||||
|
src += "?";
|
||||||
|
}
|
||||||
|
|
||||||
for (var k in attr) {
|
for (var k in attr) {
|
||||||
if (attr[k] !== null) {
|
if (attr[k] !== null) {
|
||||||
src += "&"+k+"="+(_.isBoolean(attr[k]) ? +attr[k] : attr[k]);
|
src += "&"+k+"="+(_.isBoolean(attr[k]) ? +attr[k] : attr[k]);
|
||||||
|
|||||||
@@ -191,9 +191,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.o_kanban_mobile {
|
&.o_kanban_mobile {
|
||||||
.o_kanban_record {
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
span, div.label {
|
span, div.label {
|
||||||
.o_text_overflow;
|
.o_text_overflow;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -675,6 +675,7 @@ table.well tr td {
|
|||||||
}
|
}
|
||||||
.oe_website_spinner input {
|
.oe_website_spinner input {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
min-width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.carousel .container > .carousel-caption {
|
div.carousel .container > .carousel-caption {
|
||||||
|
|||||||
@@ -545,6 +545,7 @@ table.well tr
|
|||||||
width: 121px
|
width: 121px
|
||||||
input
|
input
|
||||||
text-align: center
|
text-align: center
|
||||||
|
min-width: 50px
|
||||||
|
|
||||||
div.carousel
|
div.carousel
|
||||||
.container
|
.container
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<t t-call="website_portal.contact" />
|
<t t-call="website_portal.contact" />
|
||||||
<div class="oe_structure"/>
|
<div class="oe_structure"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 col-md-offset-1 o_my_sidebar">
|
<div id="o_my_sidebar" class="col-md-3 col-md-offset-1 o_my_sidebar">
|
||||||
<div class="o_my_details">
|
<div class="o_my_details">
|
||||||
<h3 class="page-header">Your Details <a href="/my/account" class="btn btn-default btn-xs">Change</a></h3>
|
<h3 class="page-header">Your Details <a href="/my/account" class="btn btn-default btn-xs">Change</a></h3>
|
||||||
<div t-field="user.partner_id" t-field-options='{"widget": "contact", "fields": ["email", "phone", "address", "name"]}'/>
|
<div t-field="user.partner_id" t-field-options='{"widget": "contact", "fields": ["email", "phone", "address", "name"]}'/>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="website_portal.custom_panel" inherit_id='website_portal.account' customize_show="True" name="Editable Panel" active="False">
|
<template id="website_portal.custom_panel" inherit_id='website_portal.account' customize_show="True" name="Editable Panel" active="False">
|
||||||
<xpath expr="//div[@class='o_my_sidebar']" position="inside">
|
<xpath expr="//div[@id='o_my_sidebar']" position="inside">
|
||||||
<div class="oe_structure">
|
<div class="oe_structure">
|
||||||
<h3 class="page-header">Custom Panel</h3>
|
<h3 class="page-header">Custom Panel</h3>
|
||||||
<p>Place your content here.</p>
|
<p>Place your content here.</p>
|
||||||
|
|||||||
@@ -133,6 +133,32 @@ class sale_order_line(osv.Model):
|
|||||||
'discounted_price': fields.function(_fnct_get_discounted_price, string='Discounted price', type='float', digits_compute=dp.get_precision('Product Price')),
|
'discounted_price': fields.function(_fnct_get_discounted_price, string='Discounted price', type='float', digits_compute=dp.get_precision('Product Price')),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class sale_order_line(osv.Model):
|
||||||
|
_inherit = "sale.order.line"
|
||||||
|
|
||||||
|
def _fnct_get_discounted_price(self, cr, uid, ids, field_name, args, context=None):
|
||||||
|
res = dict.fromkeys(ids, False)
|
||||||
|
for line in self.browse(cr, uid, ids, context=context):
|
||||||
|
res[line.id] = (line.price_unit * (1.0 - (line.discount or 0.0) / 100.0))
|
||||||
|
return res
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'discounted_price': fields.function(_fnct_get_discounted_price, string='Discounted price', type='float', digits_compute=dp.get_precision('Product Price')),
|
||||||
|
}
|
||||||
|
|
||||||
|
class sale_order_line(osv.Model):
|
||||||
|
_inherit = "sale.order.line"
|
||||||
|
|
||||||
|
def _fnct_get_discounted_price(self, cr, uid, ids, field_name, args, context=None):
|
||||||
|
res = dict.fromkeys(ids, False)
|
||||||
|
for line in self.browse(cr, uid, ids, context=context):
|
||||||
|
res[line.id] = (line.price_unit * (1.0 - (line.discount or 0.0) / 100.0))
|
||||||
|
return res
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'discounted_price': fields.function(_fnct_get_discounted_price, string='Discounted price', type='float', digits_compute=dp.get_precision('Product Price')),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class website(orm.Model):
|
class website(orm.Model):
|
||||||
_inherit = 'website'
|
_inherit = 'website'
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ $('.oe_website_sale').each(function () {
|
|||||||
|
|
||||||
$(oe_website_sale).on("change", 'input[name="add_qty"]', function (event) {
|
$(oe_website_sale).on("change", 'input[name="add_qty"]', function (event) {
|
||||||
var product_ids = [];
|
var product_ids = [];
|
||||||
var product_dom = $(".js_add_cart_variants[data-attribute_value_ids]").last();
|
var product_dom = $(".js_product .js_add_cart_variants[data-attribute_value_ids]").last();
|
||||||
if (!product_dom.length) {
|
if (!product_dom.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1478,10 +1478,13 @@
|
|||||||
|
|
||||||
<!-- Add a link to the product page in the portal orders followup page -->
|
<!-- Add a link to the product page in the portal orders followup page -->
|
||||||
<template id="orders_followup_products_links" name="Orders Followup Products Links" inherit_id="website_portal_sale.orders_followup">
|
<template id="orders_followup_products_links" name="Orders Followup Products Links" inherit_id="website_portal_sale.orders_followup">
|
||||||
<xpath expr="//span[@t-esc='ol.product_id.name']" position="replace">
|
<xpath expr="//div[@id='product_name']/*" position="replace">
|
||||||
<a t-att-href="ol.product_id.website_url">
|
<a t-if="ol.product_id.website_published" t-att-href="ol.product_id.website_url">
|
||||||
<span t-esc="ol.product_id.name"/>
|
<span t-esc="ol.name"/>
|
||||||
</a>
|
</a>
|
||||||
|
<t t-if="not ol.product_id.website_published">
|
||||||
|
<span t-esc="ol.name"/>
|
||||||
|
</t>
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ Delivery Costs
|
|||||||
'data': [
|
'data': [
|
||||||
'views/website_sale_delivery.xml',
|
'views/website_sale_delivery.xml',
|
||||||
'views/website_sale_delivery_view.xml',
|
'views/website_sale_delivery_view.xml',
|
||||||
'security/ir.model.access.csv',
|
|
||||||
'demo/website_sale_delivery_data.xml'
|
'demo/website_sale_delivery_data.xml'
|
||||||
],
|
],
|
||||||
'demo': [
|
'demo': [
|
||||||
|
|||||||
@@ -109,21 +109,24 @@ class SaleOrder(orm.Model):
|
|||||||
|
|
||||||
def _get_delivery_methods(self, cr, uid, order, context=None):
|
def _get_delivery_methods(self, cr, uid, order, context=None):
|
||||||
carrier_obj = self.pool.get('delivery.carrier')
|
carrier_obj = self.pool.get('delivery.carrier')
|
||||||
delivery_ids = carrier_obj.search(cr, uid, [('website_published', '=', True)], context=context)
|
carrier_ids = carrier_obj.search(cr, SUPERUSER_ID, [('website_published', '=', True)], context=context)
|
||||||
available_carrier_ids = []
|
available_carrier_ids = []
|
||||||
# Following loop is done to avoid displaying delivery methods who are not available for this order
|
# Following loop is done to avoid displaying delivery methods who are not available for this order
|
||||||
# This can surely be done in a more efficient way, but at the moment, it mimics the way it's
|
# This can surely be done in a more efficient way, but at the moment, it mimics the way it's
|
||||||
# done in delivery_set method of sale.py, from delivery module
|
# done in delivery_set method of sale.py, from delivery module
|
||||||
for carrier in carrier_obj.browse(cr, SUPERUSER_ID, delivery_ids, context=dict(context, order_id=order.id)):
|
|
||||||
|
new_context = dict(context, order_id=order.id)
|
||||||
|
for carrier in carrier_ids:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_logger.debug("Checking availability of %s" % carrier.name)
|
_logger.debug("Checking availability of carrier #%s" % carrier)
|
||||||
if carrier.available:
|
available = carrier_obj.read(cr, SUPERUSER_ID, [carrier], fields=['available'], context=new_context)[0]['available']
|
||||||
available_carrier_ids.append(carrier.id)
|
if available:
|
||||||
|
available_carrier_ids = available_carrier_ids + [carrier]
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
# RIM: hack to remove in master, because available field should not depend on a SOAP call to external shipping provider
|
# RIM: hack to remove in master, because available field should not depend on a SOAP call to external shipping provider
|
||||||
# The validation error is used in backend to display errors in fedex config, but should fail silently in frontend
|
# The validation error is used in backend to display errors in fedex config, but should fail silently in frontend
|
||||||
_logger.debug("%s removed from e-commerce carrier list. %s" % (carrier.name, e))
|
_logger.debug("Carrier #%s removed from e-commerce carrier list. %s" % (carrier, e))
|
||||||
|
|
||||||
return available_carrier_ids
|
return available_carrier_ids
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
|
||||||
access_delivery_carrier_public,delivery.carrier.public,delivery.model_delivery_carrier,,1,0,0,0
|
|
||||||
|
@@ -12,6 +12,11 @@
|
|||||||
@MAX-Z-INDEX : 2147483647;
|
@MAX-Z-INDEX : 2147483647;
|
||||||
} .define-color-if-not-defined();
|
} .define-color-if-not-defined();
|
||||||
|
|
||||||
|
.top_covered_image {
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: top;
|
||||||
|
}
|
||||||
|
|
||||||
.oe_slides_panel_footer {
|
.oe_slides_panel_footer {
|
||||||
background-color: @navbar-default-bg;
|
background-color: @navbar-default-bg;
|
||||||
}
|
}
|
||||||
@@ -45,6 +50,7 @@ section.oe_slides_promote_box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
img.oe_slides_channel_thumbnail {
|
img.oe_slides_channel_thumbnail {
|
||||||
|
.top_covered_image();
|
||||||
height: 250px;
|
height: 250px;
|
||||||
width: 100%
|
width: 100%
|
||||||
}
|
}
|
||||||
@@ -54,6 +60,7 @@ img.oe_slides_opacity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
img.oe_slides_promote_image {
|
img.oe_slides_promote_image {
|
||||||
|
.top_covered_image();
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -65,6 +72,7 @@ img.oe_slides_promote_image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.oe_slides_apart_small {
|
.oe_slides_apart_small {
|
||||||
|
.top_covered_image();
|
||||||
height: 60px;
|
height: 60px;
|
||||||
width: 90px;
|
width: 90px;
|
||||||
}
|
}
|
||||||
@@ -97,6 +105,7 @@ textarea.o_slides_comment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.oe_slides_grid_thumbnail {
|
.oe_slides_grid_thumbnail {
|
||||||
|
.top_covered_image();
|
||||||
height: 200px;
|
height: 200px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
Italy, 2015-11-05
|
||||||
|
|
||||||
|
Abstract Srl agrees to the terms of the Odoo Corporate Contributor License
|
||||||
|
Agreement v1.0.
|
||||||
|
|
||||||
|
I declare that I am authorized and able to make this agreement and sign this
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
Signed,
|
||||||
|
|
||||||
|
Davide Corio davide.corio@abstract.it https://github.com/dcorio
|
||||||
|
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
|
||||||
|
Davide Corio davide.corio@abstract.it https://github.com/dcorio
|
||||||
|
Simone Orsi simone.orsi@abstract.it https://github.com/simahawk
|
||||||
|
Giacomo Spettoli giacomo.spettoli@abstract.it https://github.com/giacomos
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
Switzerland, 2015-11-09
|
||||||
|
|
||||||
|
brain-tec AG agrees to the terms of the Odoo Corporate Contributor License
|
||||||
|
Agreement v1.0.
|
||||||
|
|
||||||
|
I declare that I am authorized and able to make this agreement and sign this
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
Signed,
|
||||||
|
|
||||||
|
Pascal Zenklusen pascal.zenklusen@braintec-group.com https://github.com/BT-pzenklusen
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
|
||||||
|
Álvaro Estébanez López alvaro.estebanez@braintec-group.com https://github.com/BT-aestebanez
|
||||||
|
Andreas Stauder andreas.stauder@braintec-group.com https://github.com/BT-astauder
|
||||||
|
Carlos Millán de Silva carlos.millan@braintec-group.com https://github.com/BT-cmillan
|
||||||
|
Cesar Andres Sanchez cesar-andres.sanchez@braintec-group.com https://github.com/BT-csanchez
|
||||||
|
Carlos Serra Toro carlos.serra@braintec-group.com https://github.com/BT-cserra
|
||||||
|
Dominik Schleich dominik.schleich@braintec-group.com https://github.com/BT-dschleich
|
||||||
|
Frédéric Aebi frederic.aebi@braintec-group.com https://github.com/BT-faebi
|
||||||
|
Fréderic Garbely frederic.garbely@braintec-group.com https://github.com/BT-fgarbely
|
||||||
|
Florian Wonneberger florian.wonneberger@braintec-group.com https://github.com/BT-fwonneberger
|
||||||
|
Jorge Asensio jorge.asensio@braintec-group.com https://github.com/BT-jasensio
|
||||||
|
Jose J. Duran-Martin jose-javier.duran@braintec-group.com https://github.com/BT-jduran
|
||||||
|
Federico Javier Mesa Hidalgo javier.mesa@braintec-group.com https://github.com/BT-jmesa
|
||||||
|
Joël Michaud joel.michaud@braintec-group.com https://github.com/BT-jmichaud
|
||||||
|
Jose Montero jose.montero@braintec-group.com https://github.com/BT-jmontero
|
||||||
|
José Luis Vallejo Diaz jose.vallejo@braintec-group.com https://github.com/BT-jvallejo
|
||||||
|
Kumar Aberer kumar.aberer@braintec-group.com https://github.com/BT-kaberer
|
||||||
|
Matt Fasola matt.fasola@braintec-group.com https://github.com/BT-mfasola
|
||||||
|
Miguel Tallon Benitez miguel.tallon@braintec-group.com https://github.com/BT-mtallon
|
||||||
|
Nicolas Frei nicolas.frei@braintec-group.com https://github.com/BT-nfrei
|
||||||
|
Nathanael Leutenegger nathanael.leutenegger@braintec-group.com https://github.com/BT-nleutenegger
|
||||||
|
Olivier Jossen olivier.jossen@braintec-group.com https://github.com/BT-ojossen
|
||||||
|
Philipp Fux philipp.fux@braintec-group.com https://github.com/BT-pfux
|
||||||
|
Raúl Martín raul.martin@braintec-group.com https://github.com/BT-rmartin
|
||||||
|
Silvan Wyden silvan.wyden@braintec-group.com https://github.com/BT-swyden
|
||||||
|
Timka Piric Muratovic timka.piric@braintec-group.com https://github.com/BT-tpiric
|
||||||
|
Pascal Zenklusen pascal.zenklusen@braintec-group.com https://github.com/BT-pzenklusen
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
China, 2015-12-10
|
||||||
|
|
||||||
|
Elico Corporation agrees to the terms of the Odoo Corporate Contributor License
|
||||||
|
Agreement v1.0.
|
||||||
|
|
||||||
|
I declare that I am authorized and able to make this agreement and sign this
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
Signed,
|
||||||
|
|
||||||
|
Eric Caudal eric.caudal@elico-corp.com https://github.com/elicoidal
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Eric Caudal eric.caudal@elico-corp.com https://github.com/elicoidal
|
||||||
|
Sébastien Maillard sebastien.maillard@elico-corp.com https://github.com/seb-elico
|
||||||
|
Siyuan Gu siyuancn@hotmail.com https://github.com/siyuancn
|
||||||
|
Wang Liping lynn.config@gmail.com https://github.com/lynndotconfig
|
||||||
|
Luke Zheng cialuo@126.com https://github.com/cialuo
|
||||||
|
Gary Wei gary.wei@elico-corp.com https://github.com/Garywei1314
|
||||||
|
Yaxi Zheng zheng.yaxi@elico-corp.com https://github.com/Elico-Yaxi
|
||||||
|
Faust Huang faust_huang@hotmail.com https://github.com/fausthuang
|
||||||
|
Yu Lin yu_lin@me.com https://github.com/uynil
|
||||||
|
Xie Xiaopeng hellomyjob@163.com https://github.com/xie8899
|
||||||
|
Connie Xiao connie.xiao@elico-corp.com https://github.com/ConnieXiao
|
||||||
|
Alex Duan alex.duan@elico-corp.com https://github.com/duanyp1991
|
||||||
|
Liu Lixia liulixia19901988@163.com https://github.com/liulixia1990
|
||||||
|
Liliane Li liliane.li@elico-corp.com http://github.com/lilianeandbrody
|
||||||
|
Rona Lin ssauapw@qq.com https://github.com/Rona111
|
||||||
|
Bob Luo luo.yumeng0831@gmail.com https://github.com/m6519578
|
||||||
|
Waller Zhang waller.zhang@elico-corp.com https://github.com/wallerzhang
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
KMEE, Brazil, 2015-12-04
|
||||||
|
|
||||||
|
KMEE INFORMATICA LTDA agrees to the terms of the Odoo Corporate
|
||||||
|
Contributor License Agreement v1.0.
|
||||||
|
|
||||||
|
I declare that I am authorized and able to make this agreement and sign this
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
Signed,
|
||||||
|
|
||||||
|
Luis Felipe Miléo mileo@kmee.com.br https://github.com/kmee
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
|
||||||
|
Luis Felipe Miléo mileo@kmee.com.br https://github.com/mileo
|
||||||
|
Ananias Pereira Batista Filho ananias@kmee.com.br https://github.com/ananiasfilho
|
||||||
|
Michell Stuttigart michell.stuttigart@kmee.com.br https://github.com/mstuttgart
|
||||||
|
Fernando Marcato Rodrigues fernando.marcato@kmee.com.br https://github.com/fernandomr
|
||||||
|
Daniel Sadamo daniel.sadamo@kmee.com.br https://github.com/sadamo
|
||||||
|
Bianca Tella bianca.tella@kmee.com.br https://github.com/biancatella
|
||||||
|
Luiz Felipe do Divino luiz.divino@kmee.com.br https://github.com/lfdivino
|
||||||
@@ -17,3 +17,4 @@ Suganthi Karunanithi ksuganthi@sodexis.com https://github.com/suganthikarunanith
|
|||||||
Xavier Dass xavier@sodexis.com https://github.com/xavier-dass
|
Xavier Dass xavier@sodexis.com https://github.com/xavier-dass
|
||||||
Atchuthan atchuthan@sodexis.com https://github.com/atchuthan
|
Atchuthan atchuthan@sodexis.com https://github.com/atchuthan
|
||||||
Dhinesh dhinesh@sodexis.com https://github.com/dvdhinesh
|
Dhinesh dhinesh@sodexis.com https://github.com/dvdhinesh
|
||||||
|
SodexisTeam dev@sodexis.com https://github.com/SodexisTeam
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
FRANCE, 2016-01-06
|
||||||
|
|
||||||
|
Sudokeys agrees to the terms of the Odoo Corporate Contributor License
|
||||||
|
Agreement v1.0.
|
||||||
|
|
||||||
|
I declare that I am authorized and able to make this agreement and sign this
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
Signed,
|
||||||
|
|
||||||
|
David GUESSAB david@sudokeys.com https://github.com/chouab
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
|
||||||
|
David GUESSAB david@sudokeys.com https://github.com/chouab
|
||||||
|
Nicolas JEUDY nicolas@sudokeys.com https://github.com/njeudy
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
Switzerland, 2015-12-11
|
||||||
|
|
||||||
|
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||||
|
Agreement v1.0.
|
||||||
|
|
||||||
|
I declare that I am authorized and able to make this agreement and sign this
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
Signed,
|
||||||
|
|
||||||
|
Florian Bruhin git@the-compiler.org https://github.com/The-Compiler
|
||||||
|
Florian Bruhin me@the-compiler.org https://github.com/The-Compiler
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
Spain, 2015-12-15
|
||||||
|
|
||||||
|
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||||
|
Agreement v1.0.
|
||||||
|
|
||||||
|
I declare that I am authorized and able to make this agreement and sign this
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
Signed,
|
||||||
|
|
||||||
|
Juan Carlos Montoya juancarlos.montoya.chamba@gmail.com https://github.com/jcarlosmontoya
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
United States of America, 2015-10-22
|
||||||
|
|
||||||
|
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||||
|
Agreement v1.0.
|
||||||
|
|
||||||
|
I declare that I am authorized and able to make this agreement and sign this
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
Signed,
|
||||||
|
|
||||||
|
Stephen Medina stephen@lilmail.xyz https://github.com/stephen144
|
||||||
@@ -13,10 +13,6 @@ Ready to create your own theme? Great. Here are some things you should know befo
|
|||||||
|
|
||||||
.. image:: theme_tutorial_assets/img/Intro.jpg
|
.. image:: theme_tutorial_assets/img/Intro.jpg
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This tutorial requires having installed Odoo v9.0 and the Theme Support Engine (website_less) module. https://www.odoo.com/apps/9.0/website_less/
|
|
||||||
|
|
||||||
|
|
||||||
An introduction for web designers
|
An introduction for web designers
|
||||||
=================================
|
=================================
|
||||||
@@ -250,7 +246,7 @@ Open the ``__openerp__.py`` you created and copy/paste the following:
|
|||||||
'data': [
|
'data': [
|
||||||
],
|
],
|
||||||
'category': 'Theme/Creative',
|
'category': 'Theme/Creative',
|
||||||
'depends': ['website', 'website_less'],
|
'depends': ['website'],
|
||||||
}
|
}
|
||||||
|
|
||||||
Replace the first four property’s values with anything you like.
|
Replace the first four property’s values with anything you like.
|
||||||
@@ -263,7 +259,7 @@ The ``data`` property will contain the xml files list. Right now it’s empty, b
|
|||||||
``category`` defines your module category (always “Theme”) and, after a slash, the subcategory. You can use one subcategory from the Odoo Apps categories list. (https://www.odoo.com/apps/themes)
|
``category`` defines your module category (always “Theme”) and, after a slash, the subcategory. You can use one subcategory from the Odoo Apps categories list. (https://www.odoo.com/apps/themes)
|
||||||
|
|
||||||
|
|
||||||
``depends`` specifies the modules needed by our theme to work properly. For our tutorial theme, we only need website and website_less. If you need blogging or eCommerce features as well, you have to add those modules too.
|
``depends`` specifies the modules needed by our theme to work properly. For our tutorial theme, we only need website. If you need blogging or eCommerce features as well, you have to add those modules too.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -691,7 +687,7 @@ We start by adding a new file in our views folder - name it **options.xml** and
|
|||||||
|
|
||||||
.. code-block:: xml
|
.. code-block:: xml
|
||||||
|
|
||||||
<template id="snippet_testimonial_opt" name="Snippet Testimonial Options" inherit_id="website_less.snippet_options">
|
<template id="snippet_testimonial_opt" name="Snippet Testimonial Options" inherit_id="website.snippet_options">
|
||||||
<xpath expr="//div[@data-js='background']" position="after">
|
<xpath expr="//div[@data-js='background']" position="after">
|
||||||
<div data-selector=".snippet_testimonial"> <!-- Options group -->
|
<div data-selector=".snippet_testimonial"> <!-- Options group -->
|
||||||
<li class="dropdown-submenu">
|
<li class="dropdown-submenu">
|
||||||
@@ -832,7 +828,7 @@ This time we have to inherit ``assets_editor`` instead of ``assets_frontend``.
|
|||||||
|
|
||||||
.. code-block:: xml
|
.. code-block:: xml
|
||||||
|
|
||||||
<template id="my_js" inherit_id="website_less.assets_editor" name="My Js">
|
<template id="my_js" inherit_id="website.assets_editor" name="My Js">
|
||||||
<xpath expr="script[last()]" position="after">
|
<xpath expr="script[last()]" position="after">
|
||||||
<script type="text/javascript" src="/theme_tutorial/static/src/js/tutorial_editor.js" />
|
<script type="text/javascript" src="/theme_tutorial/static/src/js/tutorial_editor.js" />
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|||||||
+3
-1
@@ -78,7 +78,7 @@ not be fetched by browsers. The ``src/css``, ``src/js`` and ``src/xml``
|
|||||||
sub-folders are conventional and not strictly necessary.
|
sub-folders are conventional and not strictly necessary.
|
||||||
|
|
||||||
``oepetstore/static/css/petstore.css``
|
``oepetstore/static/css/petstore.css``
|
||||||
currently empty, will hold the CSS_ for pet store content
|
Currently empty, will hold the CSS_ for pet store content
|
||||||
``oepetstore/static/xml/petstore.xml``
|
``oepetstore/static/xml/petstore.xml``
|
||||||
Mostly empty, will hold :ref:`reference/qweb` templates
|
Mostly empty, will hold :ref:`reference/qweb` templates
|
||||||
``oepetstore/static/js/petstore.js``
|
``oepetstore/static/js/petstore.js``
|
||||||
@@ -103,6 +103,8 @@ sub-folders are conventional and not strictly necessary.
|
|||||||
|
|
||||||
Which only prints a small message in the browser's console.
|
Which only prints a small message in the browser's console.
|
||||||
|
|
||||||
|
The files in the ``static`` folder, need to be defined within the module in order for them to be loaded correctly. Everything in ``src/xml`` is defined in ``__openerp__.py`` while the contents of ``src/css`` and ``src/js`` are defined in ``petstore.xml``, or a similar file.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
All JavaScript files are concatenated and :term:`minified` to improve
|
All JavaScript files are concatenated and :term:`minified` to improve
|
||||||
|
|||||||
@@ -1099,10 +1099,31 @@ class ir_model_data(osv.osv):
|
|||||||
},context=context)
|
},context=context)
|
||||||
else:
|
else:
|
||||||
if mode=='init' or (mode=='update' and xml_id):
|
if mode=='init' or (mode=='update' and xml_id):
|
||||||
|
inherit_xml_ids = []
|
||||||
|
for table, field_name in model_obj._inherits.items():
|
||||||
|
xml_ids = self.pool['ir.model.data'].search(cr, uid, [
|
||||||
|
('module', '=', module),
|
||||||
|
('name', '=', xml_id + '_' + table.replace('.', '_')),
|
||||||
|
], context=context)
|
||||||
|
# XML ID found in the database, try to recover an existing record
|
||||||
|
if xml_ids:
|
||||||
|
found_xml_id = self.pool['ir.model.data'].browse(cr, uid, xml_ids[0], context=context)
|
||||||
|
record = self.pool[found_xml_id.model].browse(cr, uid, [found_xml_id.res_id], context=context)[0]
|
||||||
|
# The record exists, store the id and don't recreate the XML ID
|
||||||
|
if record.exists():
|
||||||
|
inherit_xml_ids.append(found_xml_id.model)
|
||||||
|
values[field_name] = found_xml_id.res_id
|
||||||
|
# Orphan XML ID, delete it
|
||||||
|
else:
|
||||||
|
found_xml_id.unlink()
|
||||||
|
|
||||||
res_id = model_obj.create(cr, uid, values, context=context)
|
res_id = model_obj.create(cr, uid, values, context=context)
|
||||||
if xml_id:
|
if xml_id:
|
||||||
if model_obj._inherits:
|
if model_obj._inherits:
|
||||||
for table in model_obj._inherits:
|
for table in model_obj._inherits:
|
||||||
|
if table in inherit_xml_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
inherit_id = model_obj.browse(cr, uid,
|
inherit_id = model_obj.browse(cr, uid,
|
||||||
res_id,context=context)[model_obj._inherits[table]]
|
res_id,context=context)[model_obj._inherits[table]]
|
||||||
self.create(cr, SUPERUSER_ID, {
|
self.create(cr, SUPERUSER_ID, {
|
||||||
|
|||||||
@@ -675,7 +675,7 @@ class ir_translation(osv.osv):
|
|||||||
return ir_translation_import_cursor(cr, uid, self, context=context)
|
return ir_translation_import_cursor(cr, uid, self, context=context)
|
||||||
|
|
||||||
def load_module_terms(self, cr, modules, langs, context=None):
|
def load_module_terms(self, cr, modules, langs, context=None):
|
||||||
context = dict(context or {}) # local copy
|
context_template = dict(context or {}) # local copy
|
||||||
# make sure the given languages are active
|
# make sure the given languages are active
|
||||||
lang_obj = self.pool['res.lang']
|
lang_obj = self.pool['res.lang']
|
||||||
for lang in langs:
|
for lang in langs:
|
||||||
@@ -686,6 +686,7 @@ class ir_translation(osv.osv):
|
|||||||
if not modpath:
|
if not modpath:
|
||||||
continue
|
continue
|
||||||
for lang in langs:
|
for lang in langs:
|
||||||
|
context = dict(context_template)
|
||||||
lang_code = tools.get_iso_codes(lang)
|
lang_code = tools.get_iso_codes(lang)
|
||||||
base_lang_code = None
|
base_lang_code = None
|
||||||
if '_' in lang_code:
|
if '_' in lang_code:
|
||||||
|
|||||||
@@ -619,10 +619,15 @@ class view(osv.osv):
|
|||||||
requested (similar to ``id``)
|
requested (similar to ``id``)
|
||||||
"""
|
"""
|
||||||
if context is None: context = {}
|
if context is None: context = {}
|
||||||
|
context = context.copy()
|
||||||
|
|
||||||
# if view_id is not a root view, climb back to the top.
|
# if view_id is not a root view, climb back to the top.
|
||||||
base = v = self.browse(cr, uid, view_id, context=context)
|
base = v = self.browse(cr, uid, view_id, context=context)
|
||||||
|
check_view_ids = context.setdefault('check_view_ids', [])
|
||||||
while v.mode != 'primary':
|
while v.mode != 'primary':
|
||||||
|
# Add inherited views to the list of loading forced views
|
||||||
|
# Otherwise, inherited views could not find elements created in their direct parents if that parent is defined in the same module
|
||||||
|
check_view_ids.append(v.id)
|
||||||
v = v.inherit_id
|
v = v.inherit_id
|
||||||
root_id = v.id
|
root_id = v.id
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -433,8 +433,8 @@ def route(route=None, **kw):
|
|||||||
|
|
||||||
* ``user``: The user must be authenticated and the current request
|
* ``user``: The user must be authenticated and the current request
|
||||||
will perform using the rights of the user.
|
will perform using the rights of the user.
|
||||||
* ``admin``: The user may not be authenticated and the current request
|
* ``public``: The user may or may not be authenticated. If she isn't,
|
||||||
will perform using the admin user.
|
the current request will perform using the shared Public user.
|
||||||
* ``none``: The method is always active, even if there is no
|
* ``none``: The method is always active, even if there is no
|
||||||
database. Mainly used by the framework and authentication
|
database. Mainly used by the framework and authentication
|
||||||
modules. There request code will not have any facilities to access
|
modules. There request code will not have any facilities to access
|
||||||
|
|||||||
@@ -1384,6 +1384,7 @@ class BaseModel(object):
|
|||||||
from openerp.http import request
|
from openerp.http import request
|
||||||
Users = self.pool['res.users']
|
Users = self.pool['res.users']
|
||||||
for group_ext_id in groups.split(','):
|
for group_ext_id in groups.split(','):
|
||||||
|
group_ext_id = group_ext_id.strip()
|
||||||
if group_ext_id == 'base.group_no_one':
|
if group_ext_id == 'base.group_no_one':
|
||||||
# check: the group_no_one is effective in debug mode only
|
# check: the group_no_one is effective in debug mode only
|
||||||
if Users.has_group(cr, uid, group_ext_id) and request and request.debug:
|
if Users.has_group(cr, uid, group_ext_id) and request and request.debug:
|
||||||
|
|||||||
+15
-3
@@ -11,7 +11,7 @@ import re
|
|||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from email.utils import getaddresses
|
from email.utils import getaddresses, formataddr
|
||||||
|
|
||||||
import openerp
|
import openerp
|
||||||
from openerp.loglevels import ustr
|
from openerp.loglevels import ustr
|
||||||
@@ -632,10 +632,10 @@ def append_content_to_html(html, content, plaintext=True, preserve=False, contai
|
|||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
|
|
||||||
# matches any email in a body of text
|
# matches any email in a body of text
|
||||||
email_re = re.compile(r"""([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})""", re.VERBOSE)
|
email_re = re.compile(r"""([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63})""", re.VERBOSE)
|
||||||
|
|
||||||
# matches a string containing only one email
|
# matches a string containing only one email
|
||||||
single_email_re = re.compile(r"""^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$""", re.VERBOSE)
|
single_email_re = re.compile(r"""^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$""", re.VERBOSE)
|
||||||
|
|
||||||
res_re = re.compile(r"\[([0-9]+)\]", re.UNICODE)
|
res_re = re.compile(r"\[([0-9]+)\]", re.UNICODE)
|
||||||
command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
|
command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
|
||||||
@@ -709,3 +709,15 @@ def email_split(text):
|
|||||||
# is strictly required in RFC2822's `addr-spec`.
|
# is strictly required in RFC2822's `addr-spec`.
|
||||||
if addr[1]
|
if addr[1]
|
||||||
if '@' in addr[1]]
|
if '@' in addr[1]]
|
||||||
|
|
||||||
|
def email_split_and_format(text):
|
||||||
|
""" Return a list of email addresses found in ``text``, formatted using
|
||||||
|
formataddr. """
|
||||||
|
if not text:
|
||||||
|
return []
|
||||||
|
return [formataddr((addr[0], addr[1])) for addr in getaddresses([text])
|
||||||
|
# getaddresses() returns '' when email parsing fails, and
|
||||||
|
# sometimes returns emails without at least '@'. The '@'
|
||||||
|
# is strictly required in RFC2822's `addr-spec`.
|
||||||
|
if addr[1]
|
||||||
|
if '@' in addr[1]]
|
||||||
|
|||||||
Reference in New Issue
Block a user