[MERGE] forward port of branch 8.0 up to 9fe0590

This commit is contained in:
Christophe Simonis
2015-05-28 15:53:42 +02:00
28 changed files with 1504 additions and 67 deletions
+1133
View File
File diff suppressed because it is too large Load Diff
+67
View File
@@ -743,6 +743,60 @@ class account_bank_statement_line(osv.osv):
'account_id': account_id
}
def _get_exchange_lines(self, cr, uid, st_line, mv_line, currency_diff, currency_id, move_id, context=None):
'''
Prepare the two lines in company currency due to currency rate difference.
:param line: browse record of the voucher.line for which we want to create currency rate difference accounting
entries
:param move_id: Account move wher the move lines will be.
:param currency_diff: Amount to be posted.
:param company_currency: id of currency of the company to which the voucher belong
:param current_currency: id of currency of the voucher
:return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
:rtype: tuple of dict
'''
if currency_diff > 0:
exchange_account_id = st_line.company_id.expense_currency_exchange_account_id.id
else:
exchange_account_id = st_line.company_id.income_currency_exchange_account_id.id
# Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
# the receivable/payable account may have a secondary currency, which render this field mandatory
if mv_line.account_id.currency_id:
account_currency_id = mv_line.account_id.currency_id.id
else:
account_currency_id = st_line.company_id.currency_id.id != currency_id and currency_id or False
move_line = {
'journal_id': st_line.journal_id.id,
'period_id': st_line.statement_id.period_id.id,
'name': _('change') + ': ' + (st_line.name or '/'),
'account_id': mv_line.account_id.id,
'move_id': move_id,
'partner_id': st_line.partner_id.id,
'currency_id': account_currency_id,
'amount_currency': 0.0,
'quantity': 1,
'credit': currency_diff > 0 and currency_diff or 0.0,
'debit': currency_diff < 0 and -currency_diff or 0.0,
'date': st_line.date,
'counterpart_move_line_id': mv_line.id,
}
move_line_counterpart = {
'journal_id': st_line.journal_id.id,
'period_id': st_line.statement_id.period_id.id,
'name': _('change') + ': ' + (st_line.name or '/'),
'account_id': exchange_account_id,
'move_id': move_id,
'amount_currency': 0.0,
'partner_id': st_line.partner_id.id,
'currency_id': account_currency_id,
'quantity': 1,
'debit': currency_diff > 0 and currency_diff or 0.0,
'credit': currency_diff < 0 and -currency_diff or 0.0,
'date': st_line.date,
}
return (move_line, move_line_counterpart)
def process_reconciliations(self, cr, uid, data, context=None):
for datum in data:
self.process_reconciliation(cr, uid, datum[0], datum[1], context=context)
@@ -857,6 +911,19 @@ class account_bank_statement_line(osv.osv):
if credit_at_old_rate - credit_at_current_rate:
currency_diff = credit_at_current_rate - credit_at_old_rate
to_create.append(self.get_currency_rate_line(cr, uid, st_line, currency_diff, move_id, context=context))
if mv_line.currency_id and mv_line_dict['currency_id'] == mv_line.currency_id.id:
amount_unreconciled = mv_line.amount_residual_currency
else:
amount_unreconciled = currency_obj.compute(cr, uid, company_currency.id, mv_line_dict['currency_id'] , mv_line.amount_residual, context=ctx)
if float_is_zero(mv_line_dict['amount_currency'] + amount_unreconciled, precision_rounding=mv_line.currency_id.rounding):
amount = mv_line_dict['debit'] or mv_line_dict['credit']
sign = -1 if mv_line_dict['debit'] else 1
currency_rate_difference = sign * (mv_line.amount_residual - amount)
if not company_currency.is_zero(currency_rate_difference):
exchange_lines = self._get_exchange_lines(cr, uid, st_line, mv_line, currency_rate_difference, mv_line_dict['currency_id'], move_id, context=context)
for exchange_line in exchange_lines:
to_create.append(exchange_line)
else:
mv_line_dict['debit'] = debit_at_current_rate
mv_line_dict['credit'] = credit_at_current_rate
+110 -11
View File
@@ -14,13 +14,17 @@ class TestReconciliation(TransactionCase):
self.account_invoice_line_model = self.registry('account.invoice.line')
self.acc_bank_stmt_model = self.registry('account.bank.statement')
self.acc_bank_stmt_line_model = self.registry('account.bank.statement.line')
self.res_currency_model = self.registry('res.currency')
self.res_currency_rate_model = self.registry('res.currency.rate')
self.partner_agrolait_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "base", "res_partner_2")[1]
self.currency_swiss_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "base", "CHF")[1]
self.currency_usd_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "base", "USD")[1]
self.currency_euro_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "base", "EUR")[1]
self.account_rcv_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "account", "a_recv")[1]
self.account_rsa_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "account", "rsa")[1]
self.account_fx_income_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "account", "income_fx_income")[1]
self.account_fx_expense_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "account", "income_fx_expense")[1]
self.product_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "product", "product_product_4")[1]
self.bank_journal_usd_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "account", "bank_journal_usd")[1]
@@ -29,8 +33,9 @@ class TestReconciliation(TransactionCase):
self.account_euro_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "account", "bnk")[1]
self.company_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "base", "main_company")[1]
#set expense_currency_exchange_account_id and income_currency_exchange_account_id to a random account
self.registry("res.company").write(self.cr, self.uid, [self.company_id], {'expense_currency_exchange_account_id': self.account_rsa_id, 'income_currency_exchange_account_id':self.account_rsa_id})
#set expense_currency_exchange_account_id and income_currency_exchange_account_id to the according accounts
self.registry("res.company").write(self.cr, self.uid, [self.company_id], {'expense_currency_exchange_account_id': self.account_fx_expense_id, 'income_currency_exchange_account_id':self.account_fx_income_id})
def create_invoice(self, type='out_invoice', currency=None):
cr, uid = self.cr, self.uid
@@ -130,12 +135,12 @@ class TestReconciliation(TransactionCase):
self.check_results(customer_move_lines, {
self.account_usd_id: {'debit': 40.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
self.account_rcv_id: {'debit': 0.0, 'credit': 32.70, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
self.account_rsa_id: {'debit': 0.0, 'credit': 7.30, 'amount_currency': 0.0, 'currency_id': self.currency_usd_id},
self.account_fx_income_id: {'debit': 0.0, 'credit': 7.30, 'amount_currency': 0.0, 'currency_id': self.currency_usd_id},
})
self.check_results(supplier_move_lines, {
self.account_usd_id: {'debit': 0.0, 'credit': 40.0, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
self.account_rcv_id: {'debit': 32.70, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
self.account_rsa_id: {'debit': 7.30, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': self.currency_usd_id},
self.account_fx_income_id: {'debit': 7.30, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': self.currency_usd_id},
})
def test_statement_usd_invoice_chf_transaction_chf(self):
@@ -143,12 +148,12 @@ class TestReconciliation(TransactionCase):
self.check_results(customer_move_lines, {
self.account_usd_id: {'debit': 27.47, 'credit': 0.0, 'amount_currency': 42, 'currency_id': self.currency_usd_id},
self.account_rcv_id: {'debit': 0.0, 'credit': 38.21, 'amount_currency': -50, 'currency_id': self.currency_swiss_id},
self.account_rsa_id: {'debit': 10.74, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': self.currency_usd_id},
self.account_fx_income_id: {'debit': 10.74, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': self.currency_usd_id},
})
self.check_results(supplier_move_lines, {
self.account_usd_id: {'debit': 0.0, 'credit': 27.47, 'amount_currency': -42, 'currency_id': self.currency_usd_id},
self.account_rcv_id: {'debit': 38.21, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_swiss_id},
self.account_rsa_id: {'debit': 0.0, 'credit': 10.74, 'amount_currency': 0.0, 'currency_id': self.currency_usd_id},
self.account_fx_income_id: {'debit': 0.0, 'credit': 10.74, 'amount_currency': 0.0, 'currency_id': self.currency_usd_id},
})
def test_statement_eur_invoice_usd_transaction_usd(self):
@@ -156,12 +161,12 @@ class TestReconciliation(TransactionCase):
self.check_results(customer_move_lines, {
self.account_euro_id: {'debit': 40.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
self.account_rcv_id: {'debit': 0.0, 'credit': 32.7, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
self.account_rsa_id: {'debit': 0.0, 'credit': 7.30, 'amount_currency': 0.0, 'currency_id': False},
self.account_fx_income_id: {'debit': 0.0, 'credit': 7.30, 'amount_currency': 0.0, 'currency_id': False},
})
self.check_results(supplier_move_lines, {
self.account_euro_id: {'debit': 0.0, 'credit': 40.0, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
self.account_rcv_id: {'debit': 32.7, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
self.account_rsa_id: {'debit': 7.30, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
self.account_fx_income_id: {'debit': 7.30, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
})
def test_statement_eur_invoice_usd_transaction_eur(self):
@@ -180,10 +185,104 @@ class TestReconciliation(TransactionCase):
self.check_results(customer_move_lines, {
self.account_euro_id: {'debit': 42.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_swiss_id},
self.account_rcv_id: {'debit': 0.0, 'credit': 38.21, 'amount_currency': -50, 'currency_id': self.currency_swiss_id},
self.account_rsa_id: {'debit': 0.0, 'credit': 3.79, 'amount_currency': 0.0, 'currency_id': False},
self.account_fx_income_id: {'debit': 0.0, 'credit': 3.79, 'amount_currency': 0.0, 'currency_id': False},
})
self.check_results(supplier_move_lines, {
self.account_euro_id: {'debit': 0.0, 'credit': 42.0, 'amount_currency': -50, 'currency_id': self.currency_swiss_id},
self.account_rcv_id: {'debit': 38.21, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_swiss_id},
self.account_rsa_id: {'debit': 3.79, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
self.account_fx_income_id: {'debit': 3.79, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
})
def test_balanced_exchanges_gain_loss(self):
# The point of this test is to show that we handle correctly the gain/loss exchanges during reconciliations in foreign currencies.
# For instance, with a company set in EUR, and a USD rate set to 0.033,
# the reconciliation of an invoice of 2.00 USD (60.61 EUR) and a bank statement of two lines of 1.00 USD (30.30 EUR)
# will lead to an exchange loss, that should be handled correctly within the journal items.
cr, uid = self.cr, self.uid
# We update the currency rate of the currency USD in order to force the gain/loss exchanges in next steps
self.res_currency_rate_model.create(cr, uid, {
'name': time.strftime('%Y-%m-%d') + ' 00:00:00',
'currency_id': self.currency_usd_id,
'rate': 0.033,
})
# We create a customer invoice of 2.00 USD
invoice_id = self.account_invoice_model.create(cr, uid, {
'partner_id': self.partner_agrolait_id,
'currency_id': self.currency_usd_id,
'name': 'Foreign invoice with exchange gain',
'account_id': self.account_rcv_id,
'type': 'out_invoice',
'date_invoice': time.strftime('%Y-%m-%d'),
'journal_id': self.bank_journal_usd_id,
'invoice_line': [
(0, 0, {
'name': 'line that will lead to an exchange gain',
'quantity': 1,
'price_unit': 2,
})
]
})
self.registry('account.invoice').signal_workflow(cr, uid, [invoice_id], 'invoice_open')
invoice = self.account_invoice_model.browse(cr, uid, invoice_id)
# We create a bank statement with two lines of 1.00 USD each.
bank_stmt_id = self.acc_bank_stmt_model.create(cr, uid, {
'journal_id': self.bank_journal_usd_id,
'date': time.strftime('%Y-%m-%d'),
'line_ids': [
(0, 0, {
'name': 'half payment',
'partner_id': self.partner_agrolait_id,
'amount': 1.0,
'date': time.strftime('%Y-%m-%d')
}),
(0, 0, {
'name': 'second half payment',
'partner_id': self.partner_agrolait_id,
'amount': 1.0,
'date': time.strftime('%Y-%m-%d')
})
]
})
statement = self.acc_bank_stmt_model.browse(cr, uid, bank_stmt_id)
# We process the reconciliation of the invoice line with the two bank statement lines
line_id = None
for l in invoice.move_id.line_id:
if l.account_id.id == self.account_rcv_id:
line_id = l
break
for statement_line in statement.line_ids:
self.acc_bank_stmt_line_model.process_reconciliation(cr, uid, statement_line.id, [
{'counterpart_move_line_id': line_id.id, 'credit': 1.0, 'debit': 0.0, 'name': line_id.name}
])
# The invoice should be paid, as the payments totally cover its total
self.assertEquals(invoice.state, 'paid', 'The invoice should be paid by now')
reconcile = None
for payment in invoice.payment_ids:
reconcile = payment.reconcile_id
break
# The invoice should be reconciled (entirely, not a partial reconciliation)
self.assertTrue(reconcile, 'The invoice should be totally reconciled')
result = {}
exchange_loss_line = None
for line in reconcile.line_id:
res_account = result.setdefault(line.account_id, {'debit': 0.0, 'credit': 0.0, 'count': 0})
res_account['debit'] = res_account['debit'] + line.debit
res_account['credit'] = res_account['credit'] + line.credit
res_account['count'] += 1
if line.credit == 0.01:
exchange_loss_line = line
# We should be able to find a move line of 0.01 EUR on the Debtors account, being the cent we lost during the currency exchange
self.assertTrue(exchange_loss_line, 'There should be one move line of 0.01 EUR in credit')
# The journal items of the reconciliation should have their debit and credit total equal
# Besides, the total debit and total credit should be 60.61 EUR (2.00 USD)
self.assertEquals(sum([res['debit'] for res in result.values()]), 60.61)
self.assertEquals(sum([res['credit'] for res in result.values()]), 60.61)
counterpart_exchange_loss_line = None
for line in exchange_loss_line.move_id.line_id:
if line.account_id.id == self.account_fx_expense_id:
counterpart_exchange_loss_line = line
# We should be able to find a move line of 0.01 EUR on the Foreign Exchange Loss account
self.assertTrue(counterpart_exchange_loss_line, 'There should be one move line of 0.01 EUR on account "Foreign Exchange Loss"')
@@ -46,7 +46,7 @@ class account_analytic_invoice_line(osv.osv):
_columns = {
'product_id': fields.many2one('product.product','Product',required=True),
'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account'),
'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete='cascade'),
'name': fields.text('Description', required=True),
'quantity': fields.float('Quantity', required=True),
'uom_id': fields.many2one('product.uom', 'Unit of Measure',required=True),
+2 -2
View File
@@ -355,8 +355,8 @@ class AccountAssetDepreciationLine(models.Model):
current_currency = line.asset_id.currency_id
amount = company_currency.compute(line.amount, current_currency)
sign = (line.asset_id.category_id.journal_id.type == 'purchase' or line.asset_id.category_id.journal_id.type == 'sale' and 1) or -1
asset_name = line.asset_id.name
reference = line.name
asset_name = "/"
reference = line.asset_id.name
move_vals = {
'name': asset_name,
'date': depreciation_date,
@@ -9,7 +9,7 @@
<div class="page">
<div class="row">
<div class="col-xs-5 col-xs-offset-7">
<div t-field="o.partner_id"
<div t-field="o.invoice_partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
<span t-field="o.partner_id.vat"/>
</div>
@@ -31,6 +31,13 @@ class account_followup_stat_by_partner(osv.osv):
_description = "Follow-up Statistics by Partner"
_rec_name = 'partner_id'
_auto = False
def _get_invoice_partner_id(self, cr, uid, ids, field_name, arg, context=None):
result = {}
for rec in self.browse(cr, uid, ids, context=context):
result[rec.id] = rec.partner_id.address_get(adr_pref=['invoice']).get('invoice', rec.partner_id.id)
return result
_columns = {
'partner_id': fields.many2one('res.partner', 'Partner', readonly=True),
'date_move':fields.date('First move', readonly=True),
@@ -40,6 +47,7 @@ class account_followup_stat_by_partner(osv.osv):
'Max Follow Up Level', readonly=True, ondelete="cascade"),
'balance':fields.float('Balance', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'invoice_partner_id': fields.function(_get_invoice_partner_id, type='many2one', relation='res.partner', string='Invoice Address')
}
_depends = {
+1 -1
View File
@@ -32,7 +32,7 @@ class StockMove(osv.osv):
_inherit = 'stock.move'
_columns = {
'production_id': fields.many2one('mrp.production', 'Production Order for Produced Products', select=True, copy=False),
'production_id': fields.many2one('mrp.production', 'Production Order for Produced Products', select=True),
'raw_material_production_id': fields.many2one('mrp.production', 'Production Order for Raw Materials', select=True),
'consumed_for': fields.many2one('stock.move', 'Consumed for', help='Technical field used to make the traceability of produced products'),
}
-1
View File
@@ -474,7 +474,6 @@ class mrp_repair(osv.osv):
'partner_id': repair.address_id and repair.address_id.id or False,
'location_id': move.location_id.id,
'location_dest_id': move.location_dest_id.id,
'state': 'assigned',
})
move_ids.append(move_id)
repair_line_obj.write(cr, uid, [move.id], {'move_id': move_id, 'state': 'done'}, context=context)
+17 -9
View File
@@ -94,10 +94,10 @@ class stock_move(osv.osv):
return invoice_line_id
def _get_master_data(self, cr, uid, move, company, context=None):
if move.purchase_line_id:
if context.get('inv_type') in ('in_invoice', 'in_refund') and move.purchase_line_id:
purchase_order = move.purchase_line_id.order_id
return purchase_order.partner_id, purchase_order.create_uid.id, purchase_order.currency_id.id
elif move.picking_id:
elif context.get('inv_type') in ('in_invoice', 'in_refund') and move.picking_id:
# In case of an extra move, it is better to use the data from the original moves
for purchase_move in move.picking_id.move_lines:
if purchase_move.purchase_line_id:
@@ -114,18 +114,26 @@ class stock_move(osv.osv):
def _get_invoice_line_vals(self, cr, uid, move, partner, inv_type, context=None):
res = super(stock_move, self)._get_invoice_line_vals(cr, uid, move, partner, inv_type, context=context)
if move.purchase_line_id:
if inv_type in ('in_invoice', 'in_refund') and move.purchase_line_id:
purchase_line = move.purchase_line_id
res['invoice_line_tax_id'] = [(6, 0, [x.id for x in purchase_line.taxes_id])]
res['price_unit'] = purchase_line.price_unit
return res
def _get_moves_taxes(self, cr, uid, moves, context=None):
is_extra_move, extra_move_tax = super(stock_move, self)._get_moves_taxes(cr, uid, moves, context=context)
for move in moves:
if move.purchase_line_id:
is_extra_move[move.id] = False
extra_move_tax[move.picking_id, move.product_id] = [(6, 0, [x.id for x in move.purchase_line_id.taxes_id])]
def _get_moves_taxes(self, cr, uid, moves, inv_type, context=None):
is_extra_move, extra_move_tax = super(stock_move, self)._get_moves_taxes(cr, uid, moves, inv_type, context=context)
if inv_type == 'in_invoice':
for move in moves:
if move.purchase_line_id:
is_extra_move[move.id] = False
extra_move_tax[move.picking_id, move.product_id] = [(6, 0, [x.id for x in move.purchase_line_id.taxes_id])]
elif move.product_id.product_tmpl_id.supplier_taxes_id:
mov_id = self.search(cr, uid, [('purchase_line_id', '!=', False), ('picking_id', '=', move.picking_id.id)], limit=1, context=context)
if mov_id:
mov = self.browse(cr, uid, mov_id[0], context=context)
fp = mov.purchase_line_id.order_id.fiscal_position
res = self.pool.get("account.invoice.line").product_id_change(cr, uid, [], move.product_id.id, None, partner_id=move.picking_id.partner_id.id, fposition_id=(fp and fp.id), type='in_invoice', context=context)
extra_move_tax[0, move.product_id] = [(6, 0, res['value']['invoice_line_tax_id'])]
return (is_extra_move, extra_move_tax)
+13 -8
View File
@@ -376,7 +376,7 @@ class stock_move(osv.osv):
def _create_invoice_line_from_vals(self, cr, uid, move, invoice_line_vals, context=None):
invoice_line_id = super(stock_move, self)._create_invoice_line_from_vals(cr, uid, move, invoice_line_vals, context=context)
if move.procurement_id and move.procurement_id.sale_line_id:
if context.get('inv_type') in ('out_invoice', 'out_refund') and move.procurement_id and move.procurement_id.sale_line_id:
sale_line = move.procurement_id.sale_line_id
self.pool.get('sale.order.line').write(cr, uid, [sale_line.id], {
'invoice_lines': [(4, invoice_line_id)]
@@ -394,7 +394,7 @@ class stock_move(osv.osv):
return invoice_line_id
def _get_master_data(self, cr, uid, move, company, context=None):
if move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.order_policy == 'picking':
if context.get('inv_type') in ('out_invoice', 'out_refund') and move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.order_policy == 'picking':
sale_order = move.procurement_id.sale_line_id.order_id
return sale_order.partner_invoice_id, sale_order.user_id.id, sale_order.pricelist_id.currency_id.id
elif move.picking_id.sale_id and context.get('inv_type') in ('out_invoice', 'out_refund'):
@@ -421,12 +421,17 @@ class stock_move(osv.osv):
res['price_unit'] = res['price_unit'] / uos_coeff
return res
def _get_moves_taxes(self, cr, uid, moves, context=None):
is_extra_move, extra_move_tax = super(stock_move, self)._get_moves_taxes(cr, uid, moves, context=context)
for move in moves:
if move.procurement_id and move.procurement_id.sale_line_id:
is_extra_move[move.id] = False
extra_move_tax[move.picking_id, move.product_id] = [(6, 0, [x.id for x in move.procurement_id.sale_line_id.tax_id])]
def _get_moves_taxes(self, cr, uid, moves, inv_type, context=None):
is_extra_move, extra_move_tax = super(stock_move, self)._get_moves_taxes(cr, uid, moves, inv_type, context=context)
if inv_type == 'out_invoice':
for move in moves:
if move.procurement_id and move.procurement_id.sale_line_id:
is_extra_move[move.id] = False
extra_move_tax[move.picking_id, move.product_id] = [(6, 0, [x.id for x in move.procurement_id.sale_line_id.tax_id])]
elif move.picking_id.sale_id and move.product_id.product_tmpl_id.taxes_id:
fp = move.picking_id.sale_id.fiscal_position
res = self.pool.get("account.invoice.line").product_id_change(cr, uid, [], move.product_id.id, None, partner_id=move.picking_id.partner_id.id, fposition_id=(fp and fp.id), context=context)
extra_move_tax[0, move.product_id] = [(6, 0, res['value']['invoice_line_tax_id'])]
return (is_extra_move, extra_move_tax)
def _get_taxes(self, cr, uid, move, context=None):
+21 -2
View File
@@ -1059,11 +1059,20 @@ class stock_picking(osv.osv):
# If we encounter an UoM that is smaller than the default UoM or the one already chosen, use the new one instead.
product_uom = {} # Determines UoM used in pack operations
location_dest_id = None
location_id = None
for move in [x for x in picking.move_lines if x.state not in ('done', 'cancel')]:
if not product_uom.get(move.product_id.id):
product_uom[move.product_id.id] = move.product_id.uom_id
if move.product_uom.id != move.product_id.uom_id.id and move.product_uom.factor > product_uom[move.product_id.id].factor:
product_uom[move.product_id.id] = move.product_uom
if not move.scrapped:
if location_dest_id and move.location_dest_id.id != location_dest_id:
raise UserError(_('The destination location must be the same for all the moves of the picking.'))
location_dest_id = move.location_dest_id.id
if location_id and move.location_id.id != location_id:
raise UserError(_('The source location must be the same for all the moves of the picking.'))
location_id = move.location_id.id
pack_obj = self.pool.get("stock.quant.package")
quant_obj = self.pool.get("stock.quant")
@@ -1175,7 +1184,7 @@ class stock_picking(osv.osv):
picking_quants = []
#Calculate packages, reserved quants, qtys of this picking's moves
for move in picking.move_lines:
if move.state not in ('assigned', 'confirmed'):
if move.state not in ('assigned', 'confirmed', 'waiting'):
continue
move_quants = move.reserved_quant_ids
picking_quants += move_quants
@@ -1345,6 +1354,8 @@ class stock_picking(osv.osv):
#HALF-UP rounding as only rounding errors will be because of propagation of error from default UoM
qty = uom_obj._compute_qty_obj(cr, uid, product.uom_id, remaining_qty, op.product_uom_id, rounding_method='HALF-UP')
picking = op.picking_id
ref = product.default_code
name = '[' + ref + ']' + ' ' + product.name if ref else product.name
res = {
'picking_id': picking.id,
'location_id': picking.location_id.id,
@@ -1352,7 +1363,7 @@ class stock_picking(osv.osv):
'product_id': product.id,
'product_uom': uom_id,
'product_uom_qty': qty,
'name': _('Extra Move: ') + product.name,
'name': _('Extra Move: ') + name,
'state': 'draft',
}
return res
@@ -1856,6 +1867,13 @@ class stock_move(osv.osv):
def _default_destination_address(self, cr, uid, context=None):
return False
def _default_group_id(self, cr, uid, context=None):
context = context or {}
if context.get('default_picking_id', False):
picking = self.pool.get('stock.picking').browse(cr, uid, context['default_picking_id'], context=context)
return picking.group_id.id
return False
_defaults = {
'location_id': _default_location_source,
'location_dest_id': _default_location_destination,
@@ -1871,6 +1889,7 @@ class stock_move(osv.osv):
'procure_method': 'make_to_stock',
'propagate': True,
'partially_available': False,
'group_id': _default_group_id,
}
def _check_uom(self, cr, uid, ids, context=None):
+1
View File
@@ -1090,6 +1090,7 @@
<field name="product_id" on_change="onchange_product_id(product_id,location_id,location_dest_id, parent.partner_id)"/>
<field name="procure_method" attrs="{'readonly': [('state', '!=', 'draft')]}" groups="stock.group_adv_location"/>
<field name="picking_type_id" invisible="1"/>
<field name="group_id" invisible="1"/>
<label for="product_uom_qty"/>
<div>
<field name="product_uom_qty" on_change="onchange_quantity(product_id, product_uom_qty, product_uom, product_uos)" class="oe_inline"/>
@@ -58,6 +58,9 @@ class stock_return_picking(osv.osv_memory):
result1 = []
if context is None:
context = {}
if context and context.get('active_ids', False):
if len(context.get('active_ids')) > 1:
raise osv.except_osv(_('Warning!'), _("You may only return one picking at a time!"))
res = super(stock_return_picking, self).default_get(cr, uid, fields, context=context)
record_id = context and context.get('active_id', False) or False
uom_obj = self.pool.get('product.uom')
@@ -148,6 +151,8 @@ class stock_return_picking(osv.osv_memory):
'state': 'draft',
'location_id': move.location_dest_id.id,
'location_dest_id': move.location_id.id,
'picking_type_id': pick_type_id,
'warehouse_id': pick.picking_type_id.warehouse_id.id,
'origin_returned_move_id': move.id,
'procure_method': 'make_to_stock',
'restrict_lot_id': data_get.lot_id.id,
+11 -4
View File
@@ -166,7 +166,7 @@ class stock_move(osv.osv):
'account_analytic_id': False,
}
def _get_moves_taxes(self, cr, uid, moves, context=None):
def _get_moves_taxes(self, cr, uid, moves, inv_type, context=None):
#extra moves with the same picking_id and product_id of a move have the same taxes
extra_move_tax = {}
is_extra_move = {}
@@ -295,7 +295,7 @@ class stock_picking(osv.osv):
invoice_obj = self.pool.get('account.invoice')
move_obj = self.pool.get('stock.move')
invoices = {}
is_extra_move, extra_move_tax = move_obj._get_moves_taxes(cr, uid, moves, context=context)
is_extra_move, extra_move_tax = move_obj._get_moves_taxes(cr, uid, moves, inv_type, context=context)
product_price_unit = {}
for move in moves:
company = move.company_id
@@ -326,8 +326,15 @@ class stock_picking(osv.osv):
product_price_unit[invoice_line_vals['product_id']] = invoice_line_vals['price_unit']
if is_extra_move[move.id] and invoice_line_vals['product_id'] in product_price_unit:
invoice_line_vals['price_unit'] = product_price_unit[invoice_line_vals['product_id']]
if is_extra_move[move.id] and extra_move_tax[move.picking_id, move.product_id]:
invoice_line_vals['invoice_line_tax_id'] = extra_move_tax[move.picking_id, move.product_id]
if is_extra_move[move.id]:
desc = (inv_type == 'out_invoice' and move.product_id.product_tmpl_id.description_sale) or \
(inv_type == 'in_invoice' and move.product_id.product_tmpl_id.description_purchase)
invoice_line_vals['name'] += ' ' + desc if desc else ''
if extra_move_tax[move.picking_id, move.product_id]:
invoice_line_vals['invoice_line_tax_id'] = extra_move_tax[move.picking_id, move.product_id]
#the default product taxes
elif (0, move.product_id) in extra_move_tax:
invoice_line_vals['invoice_line_tax_id'] = extra_move_tax[0, move.product_id]
move_obj._create_invoice_line_from_vals(cr, uid, move, invoice_line_vals, context=context)
move_obj.write(cr, uid, move.id, {'invoice_state': 'invoiced'}, context=context)
@@ -23,6 +23,16 @@ from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.exceptions import UserError
JOURNAL_TYPE_MAP = {
('outgoing', 'customer'): ['sale'],
('outgoing', 'supplier'): ['purchase_refund'],
('outgoing', 'transit'): ['sale', 'purchase_refund'],
('incoming', 'supplier'): ['purchase'],
('incoming', 'customer'): ['sale_refund'],
('incoming', 'transit'): ['purchase', 'sale_refund'],
}
class stock_invoice_onshipping(osv.osv_memory):
def _get_journal(self, cr, uid, context=None):
journal_obj = self.pool.get('account.journal')
@@ -36,24 +46,13 @@ class stock_invoice_onshipping(osv.osv_memory):
res_ids = context and context.get('active_ids', [])
pick_obj = self.pool.get('stock.picking')
pickings = pick_obj.browse(cr, uid, res_ids, context=context)
vals = []
pick = pickings and pickings[0]
if not pick or not pick.move_lines:
return 'sale'
src_usage = pick.move_lines[0].location_id.usage
dest_usage = pick.move_lines[0].location_dest_id.usage
type = pick.picking_type_id.code
if type == 'outgoing' and dest_usage == 'supplier':
journal_type = 'purchase_refund'
elif type == 'outgoing' and dest_usage == 'customer':
journal_type = 'sale'
elif type == 'incoming' and src_usage == 'supplier':
journal_type = 'purchase'
elif type == 'incoming' and src_usage == 'customer':
journal_type = 'sale_refund'
else:
journal_type = 'sale'
return journal_type
usage = pick.move_lines[0].location_id.usage if type == 'incoming' else pick.move_lines[0].location_dest_id.usage
return JOURNAL_TYPE_MAP.get((type, usage), ['sale'])[0]
_name = "stock.invoice.onshipping"
_description = "Stock Invoice Onshipping"
@@ -69,6 +68,23 @@ class stock_invoice_onshipping(osv.osv_memory):
'journal_id' : _get_journal,
}
def onchange_journal_id(self, cr, uid, ids, journal_id, context=None):
if context is None:
context = {}
domain = {}
value = {}
active_id = context.get('active_id')
if active_id:
picking = self.pool['stock.picking'].browse(cr, uid, active_id, context=context)
type = picking.picking_type_id.code
usage = picking.move_lines[0].location_id.usage if type == 'incoming' else picking.move_lines[0].location_dest_id.usage
journal_types = JOURNAL_TYPE_MAP.get((type, usage), ['sale', 'purchase', 'sale_refund', 'purchase_refund'])
domain['journal_id'] = [('type', 'in', journal_types)]
if journal_id:
journal = self.pool['account.journal'].browse(cr, uid, journal_id, context=context)
value['journal_type'] = journal.type
return {'value': value, 'domain': domain}
def view_init(self, cr, uid, fields_list, context=None):
if context is None:
context = {}
@@ -10,7 +10,7 @@
<field name="journal_type" readonly="1"/>
</h1>
<group>
<field name="journal_id" domain="[('type','=',journal_type)]"/>
<field name="journal_id" domain="[('type','=',journal_type)]" on_change="onchange_journal_id(journal_id)"/>
<field name="group"/>
<field name="invoice_date" />
</group>
@@ -1,6 +1,7 @@
# coding: utf-8
from openerp import models, api
from openerp import models, api, _
from openerp.exceptions import UserError
class sale_order_line(models.Model):
@@ -20,3 +21,16 @@ class sale_order_line(models.Model):
res = True
break
return res
class purchase_order(models.Model):
_inherit = 'purchase.order'
@api.multi
def wkf_confirm_order(self):
for po in self:
if po.invoice_method == 'picking' and po.location_id.usage == 'customer':
for proc in po.order_line.mapped('procurement_ids'):
if proc.sale_line_id.order_id.order_policy == 'picking':
raise UserError(_('In the case of a dropship route, it is not possible to have an invoicing control set on "Based on incoming shipments" and a sale order with an invoice creation on "On Delivery Order"'))
super(purchase_order, self).wkf_confirm_order()
+10
View File
@@ -21,5 +21,15 @@
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="website_group_system_edit_all_views" model="ir.rule">
<field name="name">Administration Settings: Manage all views</field>
<field name="model_id" ref="base.model_ir_ui_view"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('base.group_system'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
</data>
</openerp>
+7 -3
View File
@@ -6,6 +6,10 @@
website.seo = {};
// This replaces \b, because accents(e.g. à, é) are not seen as word boundaries.
// Javascript \b is not unicode aware, and words beginning or ending by accents won't match \b
var WORD_SEPARATORS_REGEX = '([\\u2000-\\u206F\\u2E00-\\u2E7F\'!"#\\$%&\\(\\)\\*\\+,\\-\\.\\/:;<=>\\?¿¡@\\[\\]\\^_`\\{\\|\\}~\\s]+|^|$)';
function analyzeKeyword(htmlPage, keyword) {
return htmlPage.isInTitle(keyword) ? {
title: 'label label-primary',
@@ -293,13 +297,13 @@
return $('body').children().not('.js_seo_configuration').text();
},
isInBody: function (text) {
return new RegExp("\\b"+text+"\\b", "gi").test(this.bodyText());
return new RegExp(WORD_SEPARATORS_REGEX+text+WORD_SEPARATORS_REGEX, "gi").test(this.bodyText());
},
isInTitle: function (text) {
return new RegExp("\\b"+text+"\\b", "gi").test(this.title());
return new RegExp(WORD_SEPARATORS_REGEX+text+WORD_SEPARATORS_REGEX, "gi").test(this.title());
},
isInDescription: function (text) {
return new RegExp("\\b"+text+"\\b", "gi").test(this.description());
return new RegExp(WORD_SEPARATORS_REGEX+text+WORD_SEPARATORS_REGEX, "gi").test(this.description());
},
});
@@ -211,7 +211,7 @@
<div t-foreach="track.speaker_ids" t-as="speaker" class="well mt32">
<div class="row">
<div class="col-sm-2">
<span t-field="track.image"
<span t-field="speaker.sudo().image_medium"
t-field-options='{"widget": "image", "class": "img-circle"}'/>
<!-- <span t-field="speaker.self" t-field-options='{"widget": "contact", "fields": ["name", "country_id"]}'
style="display: inline-block;"/> -->
@@ -51,12 +51,15 @@ class WebsiteMembership(http.Controller):
# group by country, based on all customers (base domain)
if membership_id != 'free':
membership_line_ids = membership_line_obj.search(cr, SUPERUSER_ID, base_line_domain, context=context)
country_domain = ('member_lines', 'in', membership_line_ids)
country_domain = [('member_lines', 'in', membership_line_ids)]
else:
membership_line_ids = []
country_domain = ('free_member', '=', True)
country_domain = [('membership_state', '=', 'free')]
if post_name:
country_domain += ['|', ('name', 'ilike', post_name),
('website_description', 'ilike', post_name)]
countries = partner_obj.read_group(
cr, SUPERUSER_ID, [country_domain, ("website_published", "=", True)], ["id", "country_id"],
cr, SUPERUSER_ID, country_domain + [("website_published", "=", True)], ["id", "country_id"],
groupby="country_id", orderby="country_id", context=request.context)
countries_total = sum(country_dict['country_id_count'] for country_dict in countries)
@@ -103,7 +106,7 @@ class WebsiteMembership(http.Controller):
membership_lines_ids = membership_line_obj.search(cr, uid, line_domain, context=context)
google_map_partner_ids = membership_line_obj.get_published_companies(cr, uid, membership_line_ids, limit=2000, context=context)
search_domain = [('free_member', '=', True), ('website_published', '=', True)]
search_domain = [('membership_state', '=', 'free'), ('website_published', '=', True)]
if post_name:
search_domain += ['|', ('name', 'ilike', post_name), ('website_description', 'ilike', post_name)]
if country_id:
@@ -91,7 +91,7 @@
<li class="nav-header"><h3>Location</h3></li>
<t t-foreach="countries" t-as="country">
<li t-if="country['country_id']" t-att-class="country['country_id'] and country['country_id'][0] == current_country_id and 'active' or ''">
<a t-attf-href="/members#{ membership and '/association/%s' % membership.id or '' }#{ country['country_id'][0] and '/country/%s' % slug(country['country_id']) or '' }#{ search }"><t t-esc="country['country_id'][1]"/>
<a t-attf-href="/members#{ membership_id and '/association/%s' % membership_id or '' }#{ country['country_id'][0] and '/country/%s' % slug(country['country_id']) or '' }#{ search }"><t t-esc="country['country_id'][1]"/>
<span class="badge pull-right"><t t-esc="country['country_id_count'] or '0'"/></span>
</a>
</li>
+6 -1
View File
@@ -116,7 +116,12 @@
<template id="products_add_to_cart" inherit_id="website_sale.products_item" active="False" customize_show="True" name="Add to Cart">
<xpath expr="//div[@class='product_price']" position="inside">
<input name="product_id" t-att-value="product.product_variant_ids[0].id" type="hidden"/>
<a class="btn btn-default btn-xs a-submit"><span class="fa fa-shopping-cart"/></a>
<t t-if="len(product.product_variant_ids) == 1">
<a class="btn btn-default btn-xs a-submit"><span class="fa fa-shopping-cart"/></a>
</t>
<t t-if="len(product.product_variant_ids) &gt; 1">
<a class="btn btn-default btn-xs" t-att-href="keep('/shop/product/%s' % slug(product), page=(pager['page']['num'] if pager['page']['num']>1 else None))"><span class="fa fa-shopping-cart"/></a>
</t>
</xpath>
</template>
@@ -1,4 +1,7 @@
$(document).ready(function () {
if(! $("ul.js_add_cart_variants[data-attribute_value_ids]").length){
return;
}
$('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
.off('click')
.removeClass('a-submit')
@@ -71,7 +71,7 @@
},
{
title: "click on shop",
element: "a:contains(Shop)",
element: "a:contains(Continue Shopping)",
waitNot: '#products_grid_before .js_attributes',
},
{
+20
View File
@@ -0,0 +1,20 @@
United Kingdom, 2015-05-22
OpusVL 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,
Nuria Arranz-Velazquez nuria@opusvl.com https://github.com/nuria-opusvl
List of contributors:
JJ Allen jj@opusvl.com https://github.com/jj-opusvl
Colin Newell colin.newell@opusvl.com https://github.com/colinnewell
Peter Alabaster peter.alabaster@opusvl.com https://github.com/petera-opusvl
Nuria Arranz-Velazquez nuria@opusvl.com https://github.com/nuria-opusvl
Stuart J Mackintosh sjm@opusvl.com https://github.com/StuartJMackintosh
Edward Bagulay edward.bagulay@opusvl.com https://github.com/edwardbagulay
Nick Booker nick.booker@opusvl.com https://github.com/Nick-OpusVL
Barnie Giltrap barnie.giltrap@opusvl.com https://github.com/bjgiltrap
+11
View File
@@ -0,0 +1,11 @@
Austria, 2015-04-29
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,
Wolfgang Taferner wtaferner@users.noreply.github.com https://github.com/wtaferner