diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml
index b6776556d53..7b764fb4374 100644
--- a/addons/account/account_invoice_view.xml
+++ b/addons/account/account_invoice_view.xml
@@ -266,7 +266,7 @@
-
+
@@ -516,6 +516,19 @@
+
+
+ tree
+
+
+
+
+
+ form
+
+
+
+
Pending Invoice
account.invoice
diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py
index 6f92464e036..9bed6f7120c 100644
--- a/addons/account/account_move_line.py
+++ b/addons/account/account_move_line.py
@@ -851,7 +851,12 @@ class account_move_line(osv.osv):
ret.append(ret_line)
return ret
- def list_partners_to_reconcile(self, cr, uid, context=None):
+
+ def list_partners_to_reconcile(self, cr, uid, context=None, filter_domain=False):
+ line_ids = []
+ if filter_domain:
+ line_ids = self.search(cr, uid, filter_domain, context=context)
+ where_clause = filter_domain and "AND l.id = ANY(%s)" or ""
cr.execute(
"""SELECT partner_id FROM (
SELECT l.partner_id, p.last_reconciliation_date, SUM(l.debit) AS debit, SUM(l.credit) AS credit, MAX(l.create_date) AS max_date
@@ -861,10 +866,12 @@ class account_move_line(osv.osv):
WHERE a.reconcile IS TRUE
AND l.reconcile_id IS NULL
AND l.state <> 'draft'
+ %s
GROUP BY l.partner_id, p.last_reconciliation_date
) AS s
WHERE debit > 0 AND credit > 0 AND (last_reconciliation_date IS NULL OR max_date > last_reconciliation_date)
- ORDER BY last_reconciliation_date""")
+ ORDER BY last_reconciliation_date"""
+ % where_clause, (line_ids,))
ids = [x[0] for x in cr.fetchall()]
if not ids:
return []
diff --git a/addons/account/static/src/js/account_widgets.js b/addons/account/static/src/js/account_widgets.js
index a60b550e7cc..e61c8d72760 100644
--- a/addons/account/static/src/js/account_widgets.js
+++ b/addons/account/static/src/js/account_widgets.js
@@ -1791,7 +1791,7 @@ openerp.account = function (instance) {
this.last_group_by = group_by;
this.old_search = _.bind(this._super, this);
var mod = new instance.web.Model("account.move.line", context, domain);
- return mod.call("list_partners_to_reconcile", []).then(function(result) {
+ return mod.call("list_partners_to_reconcile", [context, domain]).then(function(result) {
var current = self.current_partner !== null ? self.partners[self.current_partner][0] : null;
self.partners = result;
var index = _.find(_.range(self.partners.length), function(el) {
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index 3d6ae546d29..138a9d952ce 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -396,7 +396,7 @@ class crm_lead(format_address, osv.osv):
"""
stages_leads = {}
for lead in self.browse(cr, uid, ids, context=context):
- stage_id = self.stage_find(cr, uid, [lead], lead.team_id.id or False, [('probability', '=', 0.0), ('fold', '=', True), ('sequence', '>', 1)], context=context)
+ stage_id = self.stage_find(cr, uid, [lead], lead.team_id.id or False, [('probability', '=', 0.0), ('on_change', '=', True), ('sequence', '>', 1)], context=context)
if stage_id:
if stages_leads.get(stage_id):
stages_leads[stage_id].append(lead.id)
@@ -415,7 +415,7 @@ class crm_lead(format_address, osv.osv):
"""
stages_leads = {}
for lead in self.browse(cr, uid, ids, context=context):
- stage_id = self.stage_find(cr, uid, [lead], lead.team_id.id or False, [('probability', '=', 100.0), ('fold', '=', True)], context=context)
+ stage_id = self.stage_find(cr, uid, [lead], lead.team_id.id or False, [('probability', '=', 100.0), ('on_change', '=', True)], context=context)
if stage_id:
if stages_leads.get(stage_id):
stages_leads[stage_id].append(lead.id)
@@ -887,6 +887,7 @@ class crm_lead(format_address, osv.osv):
(tree_view or False, 'tree'), (False, 'kanban'),
(False, 'calendar'), (False, 'graph')],
'type': 'ir.actions.act_window',
+ 'context': {'default_type': 'opportunity'}
}
def redirect_lead_view(self, cr, uid, lead_id, context=None):
diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
index f34e03b69a7..9a4c4f00dbf 100644
--- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
+++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
@@ -154,14 +154,106 @@ class account_analytic_line(osv.osv):
price = 0.0
return price
+ def _prepare_cost_invoice(self, cr, uid, partner, company_id, currency_id, analytic_lines, context=None):
+ """ returns values used to create main invoice from analytic lines"""
+ account_payment_term_obj = self.pool['account.payment.term']
+ invoice_name = analytic_lines[0].account_id.name
+
+ date_due = False
+ if partner.property_payment_term:
+ pterm_list = account_payment_term_obj.compute(cr, uid,
+ partner.property_payment_term.id, value=1,
+ date_ref=time.strftime('%Y-%m-%d'))
+ if pterm_list:
+ pterm_list = [line[0] for line in pterm_list]
+ pterm_list.sort()
+ date_due = pterm_list[-1]
+ return {
+ 'name': "%s - %s" % (time.strftime('%d/%m/%Y'), invoice_name),
+ 'partner_id': partner.id,
+ 'company_id': company_id,
+ 'payment_term': partner.property_payment_term.id or False,
+ 'account_id': partner.property_account_receivable.id,
+ 'currency_id': currency_id,
+ 'date_due': date_due,
+ 'fiscal_position': partner.property_account_position.id
+ }
+
+ def _prepare_cost_invoice_line(self, cr, uid, invoice_id, product_id, uom, user_id,
+ factor_id, account, analytic_lines, journal_type, data, context=None):
+ product_obj = self.pool['product.product']
+
+ uom_context = dict(context or {}, uom=uom)
+
+ total_price = sum(l.amount for l in analytic_lines)
+ total_qty = sum(l.unit_amount for l in analytic_lines)
+
+ if data.get('product'):
+ # force product, use its public price
+ if isinstance(data['product'], (tuple, list)):
+ product_id = data['product'][0]
+ else:
+ product_id = data['product']
+ unit_price = self._get_invoice_price(cr, uid, account, product_id, user_id, total_qty, uom_context)
+ elif journal_type == 'general' and product_id:
+ # timesheets, use sale price
+ unit_price = self._get_invoice_price(cr, uid, account, product_id, user_id, total_qty, uom_context)
+ else:
+ # expenses, using price from amount field
+ unit_price = total_price*-1.0 / total_qty
+
+ factor = self.pool['hr_timesheet_invoice.factor'].browse(cr, uid, factor_id, context=uom_context)
+ factor_name = factor.customer_name
+ curr_invoice_line = {
+ 'price_unit': unit_price,
+ 'quantity': total_qty,
+ 'product_id': product_id,
+ 'discount': factor.factor,
+ 'invoice_id': invoice_id,
+ 'name': factor_name,
+ 'uos_id': uom,
+ 'account_analytic_id': account.id,
+ }
+
+ if product_id:
+ product = product_obj.browse(cr, uid, product_id, context=uom_context)
+ factor_name = product_obj.name_get(cr, uid, [product_id], context=uom_context)[0][1]
+ if factor.customer_name:
+ factor_name += ' - ' + factor.customer_name
+
+ general_account = product.property_account_income or product.categ_id.property_account_income_categ
+ if not general_account:
+ raise UserError(_("Configuration Error!") + '\n' + _("Please define income account for product '%s'.") % product.name)
+ taxes = product.taxes_id or general_account.tax_ids
+ tax = self.pool['account.fiscal.position'].map_tax(cr, uid, account.partner_id.property_account_position, taxes)
+ curr_invoice_line.update({
+ 'invoice_line_tax_id': [(6, 0, tax)],
+ 'name': factor_name,
+ 'invoice_line_tax_id': [(6, 0, tax)],
+ 'account_id': general_account.id,
+ })
+
+ note = []
+ for line in analytic_lines:
+ # set invoice_line_note
+ details = []
+ if data.get('date', False):
+ details.append(line['date'])
+ if data.get('time', False):
+ if line['product_uom_id']:
+ details.append("%s %s" % (line.unit_amount, line.product_uom_id.name))
+ else:
+ details.append("%s" % (line['unit_amount'], ))
+ if data.get('name', False):
+ details.append(line['name'])
+ if details:
+ note.append(u' - '.join(map(lambda x: unicode(x) or '', details)))
+ if note:
+ curr_invoice_line['name'] += "\n" + ("\n".join(map(lambda x: unicode(x) or '', note)))
+ return curr_invoice_line
+
def invoice_cost_create(self, cr, uid, ids, data=None, context=None):
- analytic_account_obj = self.pool.get('account.analytic.account')
- account_payment_term_obj = self.pool.get('account.payment.term')
invoice_obj = self.pool.get('account.invoice')
- product_obj = self.pool.get('product.product')
- invoice_factor_obj = self.pool.get('hr_timesheet_invoice.factor')
- fiscal_pos_obj = self.pool.get('account.fiscal.position')
- product_uom_obj = self.pool.get('product.uom')
invoice_line_obj = self.pool.get('account.invoice.line')
invoices = []
if context is None:
@@ -169,131 +261,62 @@ class account_analytic_line(osv.osv):
if data is None:
data = {}
- journal_types = {}
+ # use key (partner/account, company, currency)
+ # creates one invoice per key
+ invoice_grouping = {}
+ currency_id = False
# prepare for iteration on journal and accounts
- for line in self.pool.get('account.analytic.line').browse(cr, uid, ids, context=context):
- if line.journal_id.type not in journal_types:
- journal_types[line.journal_id.type] = set()
- journal_types[line.journal_id.type].add(line.account_id.id)
- for journal_type, account_ids in journal_types.items():
- for account in analytic_account_obj.browse(cr, uid, list(account_ids), context=context):
- partner = account.partner_id
+ for line in self.browse(cr, uid, ids, context=context):
+
+ key = (line.account_id.id,
+ line.account_id.company_id.id,
+ line.account_id.pricelist_id.currency_id.id)
+ invoice_grouping.setdefault(key, []).append(line)
+
+ for (key_id, company_id, currency_id), analytic_lines in invoice_grouping.items():
+ # key_id is an account.analytic.account
+ partner = analytic_lines[0].account_id.partner_id # will be the same for every line
+
+ curr_invoice = self._prepare_cost_invoice(cr, uid, partner, company_id, currency_id, analytic_lines, context=context)
+ invoice_context = dict(context,
+ lang=partner.lang,
+ force_company=company_id, # set force_company in context so the correct product properties are selected (eg. income account)
+ company_id=company_id) # set company_id in context, so the correct default journal will be selected
+ last_invoice = invoice_obj.create(cr, uid, curr_invoice, context=invoice_context)
+ invoices.append(last_invoice)
+
+ # use key (product, uom, user, invoiceable, analytic account, journal type)
+ # creates one invoice line per key
+ invoice_lines_grouping = {}
+ for analytic_line in analytic_lines:
+ account = analytic_line.account_id
if (not partner) or not (account.pricelist_id):
- raise UserError(_('Contract incomplete. Please fill in the Customer and Pricelist fields.'))
+ raise UserError(_('Contract incomplete. Please fill in the Customer and Pricelist fields for %s.') % (account.name))
- date_due = False
- if partner.property_payment_term:
- pterm_list= account_payment_term_obj.compute(cr, uid,
- partner.property_payment_term.id, value=1,
- date_ref=time.strftime('%Y-%m-%d'))
- if pterm_list:
- pterm_list = [line[0] for line in pterm_list]
- pterm_list.sort()
- date_due = pterm_list[-1]
+ if not analytic_line.to_invoice:
+ raise UserError(_('Trying to invoice non invoiceable line for %s.') % (analytic_line.product_id.name))
- curr_invoice = {
- 'name': time.strftime('%d/%m/%Y') + ' - '+account.name,
- 'partner_id': account.partner_id.id,
- 'company_id': account.company_id.id,
- 'payment_term': partner.property_payment_term.id or False,
- 'account_id': partner.property_account_receivable.id,
- 'currency_id': account.pricelist_id.currency_id.id,
- 'date_due': date_due,
- 'fiscal_position': account.partner_id.property_account_position.id
- }
- context2 = context.copy()
- context2['lang'] = partner.lang
- # set company_id in context, so the correct default journal will be selected
- context2['force_company'] = curr_invoice['company_id']
- # set force_company in context so the correct product properties are selected (eg. income account)
- context2['company_id'] = curr_invoice['company_id']
+ key = (analytic_line.product_id.id,
+ analytic_line.product_uom_id.id,
+ analytic_line.user_id.id,
+ analytic_line.to_invoice.id,
+ analytic_line.account_id,
+ analytic_line.journal_id.type)
+ invoice_lines_grouping.setdefault(key, []).append(analytic_line)
- last_invoice = invoice_obj.create(cr, uid, curr_invoice, context=context2)
- invoices.append(last_invoice)
+ # finally creates the invoice line
+ for (product_id, uom, user_id, factor_id, account, journal_type), lines_to_invoice in invoice_lines_grouping.items():
+ curr_invoice_line = self._prepare_cost_invoice_line(cr, uid, last_invoice,
+ product_id, uom, user_id, factor_id, account, lines_to_invoice,
+ journal_type, data, context=context)
- cr.execute("""SELECT product_id, user_id, to_invoice, sum(amount), sum(unit_amount), product_uom_id
- FROM account_analytic_line as line LEFT JOIN account_analytic_journal journal ON (line.journal_id = journal.id)
- WHERE account_id = %s
- AND line.id IN %s AND journal.type = %s AND to_invoice IS NOT NULL
- GROUP BY product_id, user_id, to_invoice, product_uom_id""", (account.id, tuple(ids), journal_type))
-
- for product_id, user_id, factor_id, total_price, qty, uom in cr.fetchall():
- context2.update({'uom': uom})
-
- if data.get('product'):
- # force product, use its public price
- product_id = data['product'][0]
- unit_price = self._get_invoice_price(cr, uid, account, product_id, user_id, qty, context2)
- elif journal_type == 'general' and product_id:
- # timesheets, use sale price
- unit_price = self._get_invoice_price(cr, uid, account, product_id, user_id, qty, context2)
- else:
- # expenses, using price from amount field
- unit_price = total_price*-1.0 / qty
-
- factor = invoice_factor_obj.browse(cr, uid, factor_id, context=context2)
- # factor_name = factor.customer_name and line_name + ' - ' + factor.customer_name or line_name
- factor_name = factor.customer_name
- curr_line = {
- 'price_unit': unit_price,
- 'quantity': qty,
- 'product_id': product_id or False,
- 'discount': factor.factor,
- 'invoice_id': last_invoice,
- 'name': factor_name,
- 'uos_id': uom,
- 'account_analytic_id': account.id,
- }
- product = product_obj.browse(cr, uid, product_id, context=context2)
- if product:
- factor_name = product_obj.name_get(cr, uid, [product_id], context=context2)[0][1]
- if factor.customer_name:
- factor_name += ' - ' + factor.customer_name
-
- general_account = product.property_account_income or product.categ_id.property_account_income_categ
- if not general_account:
- raise UserError(_("Configuration Error!") + '\n' + _("Please define income account for product '%s'.") % product.name)
- taxes = product.taxes_id or general_account.tax_ids
- tax = fiscal_pos_obj.map_tax(cr, uid, account.partner_id.property_account_position, taxes)
- curr_line.update({
- 'invoice_line_tax_id': [(6,0,tax )],
- 'name': factor_name,
- 'invoice_line_tax_id': [(6,0,tax)],
- 'account_id': general_account.id,
- })
- #
- # Compute for lines
- #
- cr.execute("SELECT * FROM account_analytic_line WHERE account_id = %s and id IN %s AND product_id=%s and to_invoice=%s ORDER BY account_analytic_line.date", (account.id, tuple(ids), product_id, factor_id))
-
- line_ids = cr.dictfetchall()
- note = []
- for line in line_ids:
- # set invoice_line_note
- details = []
- if data.get('date', False):
- details.append(line['date'])
- if data.get('time', False):
- if line['product_uom_id']:
- details.append("%s %s" % (line['unit_amount'], product_uom_obj.browse(cr, uid, [line['product_uom_id']],context2)[0].name))
- else:
- details.append("%s" % (line['unit_amount'], ))
- if data.get('name', False):
- details.append(line['name'])
- if details:
- note.append(u' - '.join(map(lambda x: unicode(x) or '',details)))
- if note:
- curr_line['name'] += "\n" + ("\n".join(map(lambda x: unicode(x) or '',note)))
- invoice_line_obj.create(cr, uid, curr_line, context=context)
- cr.execute("update account_analytic_line set invoice_id=%s WHERE account_id = %s and id IN %s", (last_invoice, account.id, tuple(ids)))
- self.invalidate_cache(cr, uid, ['invoice_id'], ids, context=context)
-
- invoice_obj.button_reset_taxes(cr, uid, [last_invoice], context)
+ invoice_line_obj.create(cr, uid, curr_invoice_line, context=context)
+ self.write(cr, uid, [l.id for l in analytic_lines], {'invoice_id': last_invoice}, context=context)
+ invoice_obj.button_reset_taxes(cr, uid, [last_invoice], context)
return invoices
-
class hr_analytic_timesheet(osv.osv):
_inherit = "hr.analytic.timesheet"
def on_change_account_id(self, cr, uid, ids, account_id, user_id=False):
diff --git a/addons/mrp_repair/mrp_repair_view.xml b/addons/mrp_repair/mrp_repair_view.xml
index 8d97b3aa7dc..fb556af4d59 100644
--- a/addons/mrp_repair/mrp_repair_view.xml
+++ b/addons/mrp_repair/mrp_repair_view.xml
@@ -167,6 +167,7 @@
+
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index de51cc10169..2ab32e322b6 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -183,15 +183,20 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
},
},{
model: 'account.tax',
- fields: ['name','amount', 'price_include', 'include_base_amount', 'type'],
+ fields: ['name','amount', 'price_include', 'include_base_amount', 'type', 'child_ids', 'child_depend', 'include_base_amount'],
domain: null,
- loaded: function(self,taxes){
- self.taxes = taxes;
+ loaded: function(self, taxes){
+ self.taxes = taxes;
self.taxes_by_id = {};
-
- for (var i = 0; i < taxes.length; i++) {
- self.taxes_by_id[taxes[i].id] = taxes[i];
- }
+ _.each(taxes, function(tax){
+ self.taxes_by_id[tax.id] = tax;
+ });
+ _.each(self.taxes_by_id, function(tax) {
+ tax.child_taxes = {};
+ _.each(tax.child_ids, function(child_tax_id) {
+ tax.child_taxes[child_tax_id] = self.taxes_by_id[child_tax_id];
+ });
+ });
},
},{
model: 'pos.session',
@@ -1089,52 +1094,84 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
}
return taxes;
},
- get_all_prices: function(){
+ compute_all: function(taxes, price_unit) {
var self = this;
+ var res = [];
var currency_rounding = this.pos.currency.rounding;
- var base = this.get_base_price();
- var totalTax = base;
- var totalNoTax = base;
-
- var product = this.get_product();
- var taxes = this.get_applicable_taxes();
- var taxtotal = 0;
- var taxdetail = {};
- _.each(taxes, function(tax) {
+ var base = price_unit;
+ _(taxes).each(function(tax) {
if (tax.price_include) {
- var tmp;
if (tax.type === "percent") {
- tmp = base - round_pr(base / (1 + tax.amount),currency_rounding);
+ tmp = round_pr(base - round_pr(base / (1 + tax.amount),currency_rounding),currency_rounding);
+ data = {amount:tmp, price_include:true, id: tax.id};
+ res.push(data);
} else if (tax.type === "fixed") {
tmp = round_pr(tax.amount * self.get_quantity(),currency_rounding);
+ data = {amount:tmp, price_include:true, id: tax.id};
+ res.push(data);
} else {
throw "This type of tax is not supported by the point of sale: " + tax.type;
}
- tmp = round_pr(tmp,currency_rounding);
- taxtotal += tmp;
- totalNoTax -= tmp;
- taxdetail[tax.id] = tmp;
} else {
- var tmp;
if (tax.type === "percent") {
- tmp = tax.amount * base;
+ tmp = round_pr(tax.amount * base, currency_rounding);
+ data = {amount:tmp, price_include:false, id: tax.id};
+ res.push(data);
} else if (tax.type === "fixed") {
- tmp = tax.amount * self.get_quantity();
+ tmp = round_pr(tax.amount * self.get_quantity(), currency_rounding);
+ data = {amount:tmp, price_include:false, id: tax.id};
+ res.push(data);
} else {
throw "This type of tax is not supported by the point of sale: " + tax.type;
}
- tmp = round_pr(tmp,currency_rounding);
-
- if (tax.include_base_amount) {
- base += tmp;
+ var base_amount = data.amount;
+ var child_amount = 0.0;
+ if (tax.child_depend) {
+ res.pop(); // do not use parent tax
+ child_tax = self.compute_all(tax.child_taxes, base_amount);
+ res.push(child_tax);
+ _(child_tax).each(function(child) {
+ child_amount += child.amount;
+ });
+ }
+ if (tax.include_base_amount) {
+ base += base_amount + child_amount;
}
-
- taxtotal += tmp;
- totalTax += tmp;
- taxdetail[tax.id] = tmp;
}
});
+ return res;
+ },
+ get_all_prices: function(){
+ var base = round_pr(this.get_quantity() * this.get_unit_price() * (1.0 - (this.get_discount() / 100.0)), this.pos.currency.rounding);
+ var totalTax = base;
+ var totalNoTax = base;
+ var taxtotal = 0;
+
+ var product = this.get_product();
+ var taxes_ids = product.taxes_id;
+ var taxes = this.pos.taxes;
+ var taxdetail = {};
+ var product_taxes = [];
+
+ _(taxes_ids).each(function(el){
+ product_taxes.push(_.detect(taxes, function(t){
+ return t.id === el;
+ }));
+ });
+
+ var all_taxes = _(this.compute_all(product_taxes, base)).flatten();
+
+ _(all_taxes).each(function(tax) {
+ if (tax.price_include) {
+ totalNoTax -= tax.amount;
+ } else {
+ totalTax += tax.amount;
+ }
+ taxtotal += tax.amount;
+ taxdetail[tax.id] = tax.amount;
+ });
+
return {
"priceWithTax": totalTax,
"priceWithoutTax": totalNoTax,
diff --git a/addons/project/project.py b/addons/project/project.py
index c071cae1bb8..84a6a7e4064 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -1031,7 +1031,7 @@ class task(osv.osv):
def set_remaining_time(self, cr, uid, ids, remaining_time=1.0, context=None):
for task in self.browse(cr, uid, ids, context=context):
if (task.stage_id and task.stage_id.sequence <= 1) or (task.planned_hours == 0.0):
- self.write(cr, uid, [task.id], {'planned_hours': remaining_time}, context=context)
+ self.write(cr, uid, [task.id], {'planned_hours': remaining_time + task.effective_hours}, context=context)
self.write(cr, uid, ids, {'remaining_hours': remaining_time}, context=context)
return True
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 111852f723e..86bb9ed2b83 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -861,7 +861,7 @@ class purchase_order(osv.osv):
Orders will only be merged if:
* Purchase Orders are in draft
* Purchase Orders belong to the same partner
- * Purchase Orders are have same stock location, same pricelist
+ * Purchase Orders are have same stock location, same pricelist, same currency
Lines will only be merged if:
* Order lines are exactly the same except for the quantity and unit
@@ -897,12 +897,13 @@ class purchase_order(osv.osv):
# Compute what the new orders should contain
new_orders = {}
- order_lines_to_move = []
+ order_lines_to_move = {}
for porder in [order for order in self.browse(cr, uid, ids, context=context) if order.state == 'draft']:
- order_key = make_key(porder, ('partner_id', 'location_id', 'pricelist_id'))
+ order_key = make_key(porder, ('partner_id', 'location_id', 'pricelist_id', 'currency_id'))
new_order = new_orders.setdefault(order_key, ({}, []))
new_order[1].append(porder.id)
order_infos = new_order[0]
+ order_lines_to_move.setdefault(order_key, [])
if not order_infos:
order_infos.update({
@@ -913,6 +914,7 @@ class purchase_order(osv.osv):
'picking_type_id': porder.picking_type_id.id,
'location_id': porder.location_id.id,
'pricelist_id': porder.pricelist_id.id,
+ 'currency_id': porder.currency_id.id,
'state': 'draft',
'order_line': {},
'notes': '%s' % (porder.notes or '',),
@@ -926,8 +928,7 @@ class purchase_order(osv.osv):
if porder.origin:
order_infos['origin'] = (order_infos['origin'] or '') + ' ' + porder.origin
- for order_line in porder.order_line:
- order_lines_to_move += [order_line.id]
+ order_lines_to_move[order_key] += [order_line.id for order_line in porder.order_line]
allorders = []
orders_info = {}
@@ -941,7 +942,7 @@ class purchase_order(osv.osv):
for key, value in order_data['order_line'].iteritems():
del value['uom_factor']
value.update(dict(key))
- order_data['order_line'] = [(6, 0, order_lines_to_move)]
+ order_data['order_line'] = [(6, 0, order_lines_to_move[order_key])]
# create the new order
context.update({'mail_create_nolog': True})
diff --git a/addons/purchase/wizard/purchase_order_group_view.xml b/addons/purchase/wizard/purchase_order_group_view.xml
index 83503380c61..56efe485ae5 100644
--- a/addons/purchase/wizard/purchase_order_group_view.xml
+++ b/addons/purchase/wizard/purchase_order_group_view.xml
@@ -13,7 +13,7 @@ Please note that:
Orders will only be merged if:
* Purchase Orders are in draft
* Purchase Orders belong to the same supplier
-* Purchase Orders are have same stock location, same pricelist
+* Purchase Orders are have same stock location, same pricelist, same currency
Lines will only be merged if:
* Order lines are exactly the same except for the product,quantity and unit
" colspan="4"/>
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 8d7084abe5c..b032304faa8 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -330,7 +330,8 @@ class sale_order(osv.osv):
part = self.pool.get('res.partner').browse(cr, uid, part, context=context)
addr = self.pool.get('res.partner').address_get(cr, uid, [part.id], ['delivery', 'invoice', 'contact'])
pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
- payment_term = part.property_payment_term and part.property_payment_term.id or False
+ invoice_part = self.pool.get('res.partner').browse(cr, uid, addr['invoice'], context=context)
+ payment_term = invoice_part.property_payment_term and invoice_part.property_payment_term.id or False
dedicated_salesman = part.user_id and part.user_id.id or uid
val = {
'partner_invoice_id': addr['invoice'],
@@ -393,14 +394,14 @@ class sale_order(osv.osv):
'origin': order.name,
'type': 'out_invoice',
'reference': order.client_order_ref or order.name,
- 'account_id': order.partner_id.property_account_receivable.id,
+ 'account_id': order.partner_invoice_id.property_account_receivable.id,
'partner_id': order.partner_invoice_id.id,
'journal_id': journal_ids[0],
'invoice_line': [(6, 0, lines)],
'currency_id': order.pricelist_id.currency_id.id,
'comment': order.note,
'payment_term': order.payment_term and order.payment_term.id or False,
- 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
+ 'fiscal_position': order.fiscal_position.id or order.partner_invoice_id.property_account_position.id,
'date_invoice': context.get('date_invoice', False),
'company_id': order.company_id.id,
'user_id': order.user_id and order.user_id.id or False,
diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml
index 23633f9a233..c914a8761cc 100644
--- a/addons/stock/stock_view.xml
+++ b/addons/stock/stock_view.xml
@@ -264,12 +264,7 @@
Click to add a serial number.
This is the list of all the production lots you recorded. When
- you select a lot, you can get the
- traceability of the products contained in lot. By default, the
- list is filtered on the serial numbers that are available in
- your warehouse but you can uncheck the 'Available' button to
- get all the lots you produced, received or delivered to
- customers.
+ you select a lot, you can get the traceability of the products contained in lot.
diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js
index 9fd9ff86b02..b716f0f0ef3 100644
--- a/addons/web/static/src/js/formats.js
+++ b/addons/web/static/src/js/formats.js
@@ -227,10 +227,6 @@ instance.web.parse_value = function (value, descriptor, value_if_empty) {
throw new Error(_.str.sprintf(_t("'%s' is not a correct integer"), value));
return tmp;
case 'float':
- tmp = Number(value);
- if (!isNaN(tmp))
- return tmp;
-
var tmp2 = value;
do {
tmp = tmp2;
diff --git a/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js b/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js
index b8c7761d103..f5be348a8e7 100644
--- a/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js
+++ b/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js
@@ -11,6 +11,15 @@ instance.web_kanban.SparklineBarWidget = instance.web_kanban.AbstractField.exten
var self = this;
var title = this.$node.html() || this.field.string;
setTimeout(function () {
+ var tooltipFormatter = function(sp, options, fields) {
+ var format = $.spformat('{{offset:offset}} {{formatted_value}}
');
+ var result = '';
+ $.each(fields, function(i, field) {
+ field.formatted_value = instance.web.format_value(field.value, { type : 'float' });
+ result += format.render(field, options.get('tooltipValueLookups'), options);
+ })
+ return result;
+ }
var field_value = JSON.parse(self.field.value);
var value = _.pluck(field_value, 'value');
var tooltips = _.pluck(field_value, 'tooltip');
@@ -24,6 +33,7 @@ instance.web_kanban.SparklineBarWidget = instance.web_kanban.AbstractField.exten
barSpacing: 1,
barColor: '#96d854',
tooltipFormat: tooltipFormat,
+ tooltipFormatter: self.options.type == 'tristate' ? undefined : tooltipFormatter,
chartRangeMin: 0,
tooltipValueLookups: {
'offset': tooltips
diff --git a/addons/website_quote/controllers/main.py b/addons/website_quote/controllers/main.py
index c15431b9b2b..0c295d20b92 100644
--- a/addons/website_quote/controllers/main.py
+++ b/addons/website_quote/controllers/main.py
@@ -36,7 +36,7 @@ class sale_quote(http.Controller):
def view(self, order_id, pdf=None, token=None, message=False, **post):
# use SUPERUSER_ID allow to access/view order for public user
# only if he knows the private token
- order = request.registry.get('sale.order').browse(request.cr, token and SUPERUSER_ID or request.uid, order_id)
+ order = request.registry.get('sale.order').browse(request.cr, token and SUPERUSER_ID or request.uid, order_id, request.context)
now = time.strftime('%Y-%m-%d')
dummy, action = request.registry.get('ir.model.data').get_object_reference(request.cr, request.uid, 'sale', 'action_quotations')
if token:
diff --git a/doc/business.rst b/doc/business.rst
index 26d4dfdf97a..def762578f6 100644
--- a/doc/business.rst
+++ b/doc/business.rst
@@ -4,4 +4,4 @@ Business Mementoes
.. toctree::
- Accounting Memento
+ Accounting Memento (US GAAP)
diff --git a/doc/cla/corporate/akretion.md b/doc/cla/corporate/akretion.md
new file mode 100644
index 00000000000..61932b3fe8b
--- /dev/null
+++ b/doc/cla/corporate/akretion.md
@@ -0,0 +1,43 @@
+Brazil, France, 2015-04-14
+
+Akretion France 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,
+
+Sébastien BEAU sebastien.beau@akretion.com https://github.com/akretion
+
+
+Akretion 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,
+Raphaël Valyi raphael.valyi@akretion.com https://github.com/akretion
+
+
+List of contributors:
+
+Abdessamad Hilali abdessamad.hilali@akretion.com https://github.com/ahilali
+Adrien Chaussende adrien.chaussende@akretion.com https://github.com/achaussende
+Alexis de Lattre alexis.delattre@akretion.com https://github.com/alexis-via
+Arthur Vuillard arthur.vuillard@akretion.com https://github.com/arthru
+Benoît Guillot benoit.guillot@akretion.com https://github.com/bguillot
+Chafique delli chafique.delli@akretion.com https://github.com/chafique-delli
+Cilene Oliveira cilene.oliveira@akretion.com https://github.com/cileneoliveira
+David Beal david.beal@akretion.com https://github.com/bealdav
+Florian da Costa florian.dacosta@akretion.com https://github.com/florian-dacosta
+Magno Barcelo da Costa magno.costa@akretion.com.br https://github.com/mbcosta
+Raphaël Valyi raphael.valyi@akretion.com https://github.com/rvalyi
+Renato Lima renato.lima@akretion.com https://github.com/renatonlima
+Sebastien Beau sebastien.beau@akretion.com https://github.com/sebastienbeau
+Sylvain Calador sylvain.calador@akretion.com https://github.com/sylvainc
+Sylvain Legal sylvain.legal@akretion.com https://github.com/legalsylvain
+Thiago Moreira de Souza Arrais thiago.moreira@akretion.com.br https://github.com/thiagomds
+Valentin Chemiere valentin.chemiere@akretion.com https://github.com/viggor
+Vianney da Costa vianney.dacosta@akretion.com.br https://launchpad.net/~vianneydc
diff --git a/doc/cla/individual/jnbrunet.md b/doc/cla/individual/jnbrunet.md
new file mode 100644
index 00000000000..9c48a753205
--- /dev/null
+++ b/doc/cla/individual/jnbrunet.md
@@ -0,0 +1,11 @@
+Canada, March 21 2015
+
+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,
+
+Jean-Nicolas Brunet jnbrunet2000@gmail.com https://github.com/jnbrunet