[MERGE] forward port branch saas-11.3 up to 6fd637e131

This commit is contained in:
Denis Ledoux
2019-04-25 15:47:09 +02:00
19 changed files with 682 additions and 51 deletions
+2 -2
View File
@@ -44,7 +44,7 @@ class AuthSignupHome(Home):
lang=user_sudo.lang,
auth_login=werkzeug.url_encode({'auth_login': user_sudo.email}),
).send_mail(user_sudo.id, force_send=True)
return super(AuthSignupHome, self).web_login(*args, **kw)
return self.web_login(*args, **kw)
except UserError as e:
qcontext['error'] = e.name or e.value
except (SignupError, AssertionError) as e:
@@ -69,7 +69,7 @@ class AuthSignupHome(Home):
try:
if qcontext.get('token'):
self.do_signup(qcontext)
return super(AuthSignupHome, self).web_login(*args, **kw)
return self.web_login(*args, **kw)
else:
login = qcontext.get('login')
assert login, _("No login provided.")
@@ -38,6 +38,8 @@ class IrModule(models.Model):
installed_mods = [m.name for m in known_mods if m.state == 'installed']
terp = load_information_from_description_file(module, mod_path=path)
if not terp:
return False
values = self.get_values_from_terp(terp)
if 'version' in terp:
values['latest_version'] = terp['version']
@@ -133,8 +135,8 @@ class IrModule(models.Model):
try:
# assert mod_name.startswith('theme_')
path = opj(module_dir, mod_name)
self._import_module(mod_name, path, force=force)
success.append(mod_name)
if self._import_module(mod_name, path, force=force):
success.append(mod_name)
except Exception as e:
_logger.exception('Error while importing module')
errors[mod_name] = exception_to_unicode(e)
+2 -2
View File
@@ -42,11 +42,11 @@ class SaleOrderLine(models.Model):
registrations linked to this line. This method update existing registrations
and create new one for missing one. """
Registration = self.env['event.registration'].sudo()
registrations = Registration.search([('sale_order_line_id', 'in', self.ids), ('state', '!=', 'cancel')])
registrations = Registration.search([('sale_order_line_id', 'in', self.ids)])
for so_line in self.filtered('event_id'):
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id)
if confirm:
existing_registrations.filtered(lambda self: self.state != 'open').confirm_registration()
existing_registrations.filtered(lambda self: self.state not in ['open', 'cancel']).confirm_registration()
if cancel_to_draft:
existing_registrations.filtered(lambda self: self.state == 'cancel').do_draft()
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_bank_statement
from . import account_fiscal_position
from . import res_company
from . import pos
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from odoo import _, models
from odoo.exceptions import UserError
class AccountFiscalPosition(models.Model):
_inherit = "account.fiscal.position"
def write(self, vals):
if "tax_ids" in vals:
if self.env["pos.order"].sudo().search_count([("fiscal_position_id", "in", self.ids)]):
raise UserError(
_(
"You cannot modify a fiscal position used in a POS order. "
+ "You should archive it and create a new one."
)
)
return super(AccountFiscalPosition, self).write(vals)
@@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<data noupdate="1">
<record id="seq_picking_type_posout" model="ir.sequence">
<field name="name">Picking POS</field>
<field name="prefix">POS</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
</data>
<data noupdate="0">
<record id="picking_type_posout" model="stock.picking.type">
<field name="name">PoS Orders</field>
<field name="sequence_id" ref="seq_picking_type_posout"/>
@@ -5,8 +5,7 @@
<field name="res_model">account.journal</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form</field>
<field name="context" eval="{'default_journal_user': 1, 'default_type': 'cash'}"/>
<field name="domain">[('journal_user','=', 1)]</field>
<field name="context" eval="{'search_default_available_in_pos': True, 'default_journal_user': True, 'default_type': 'cash'}"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Add a new payment method
@@ -75,13 +75,15 @@ class AccountInvoice(models.Model):
for val_stock_move in valuation_stock_move:
# In case val_stock_move is a return move, its valuation entries have been made with the
# currency rate corresponding to the original stock move
valuation_date = val_stock_move.origin_returned_move_id.date or val_stock_move.date_expected
valuation_date = val_stock_move.origin_returned_move_id.date or val_stock_move.date
valuation_price_unit_total += company_currency._convert(
abs(val_stock_move.price_unit) * val_stock_move.product_qty,
inv.currency_id,
company=inv.company_id, date=valuation_date, round=False,
)
valuation_total_qty += val_stock_move.product_qty
# in Stock Move, price unit is in company_currency
valuation_price_unit = valuation_price_unit_total / valuation_total_qty
valuation_price_unit = i_line.product_id.uom_id._compute_price(valuation_price_unit, i_line.uom_id)
line_quantity = valuation_total_qty
@@ -62,8 +62,10 @@ class TestValuationReconciliation(ValuationReconciliationTestCase):
""" Tests the case into which we receive the goods first, and then make the invoice.
"""
test_product = self.test_product_delivery
purchase_order = self._create_purchase(test_product, '2018-01-01')
self._process_pickings(purchase_order.picking_ids)
date_po_and_delivery = '2018-01-01'
purchase_order = self._create_purchase(test_product, date_po_and_delivery)
self._process_pickings(purchase_order.picking_ids, date=date_po_and_delivery)
invoice = self._create_invoice_for_po(purchase_order, '2018-02-02')
self.env['res.currency.rate'].create({
@@ -149,8 +151,9 @@ class TestValuationReconciliation(ValuationReconciliationTestCase):
""" Tests the case into which we receive part of the goods first, then 2 invoices at different rates, and finally the remaining quantities
"""
test_product = self.test_product_delivery
purchase_order = self._create_purchase(test_product, '2017-01-01', quantity=5.0)
self._process_pickings(purchase_order.picking_ids, quantity=2.0)
date_po_and_delivery0 = '2017-01-01'
purchase_order = self._create_purchase(test_product, date_po_and_delivery0, quantity=5.0)
self._process_pickings(purchase_order.picking_ids, quantity=2.0, date=date_po_and_delivery0)
picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)], order="id asc", limit=1)
invoice = self._create_invoice_for_po(purchase_order, '2017-01-15')
@@ -183,6 +186,8 @@ class TestValuationReconciliation(ValuationReconciliationTestCase):
'rate': 12.195747002,
'name': '2017-04-01',
})
# We don't need to make the date of processing explicit since the very last rate
# will be taken
self._process_pickings(purchase_order.picking_ids.filtered(lambda x: x.state != 'done'), quantity=3.0)
picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)], order='id desc', limit=1)
self.check_reconciliation(invoice2, picking)
@@ -3,6 +3,7 @@
import time
from datetime import datetime
from unittest.mock import patch
from odoo import fields
from odoo.tests import Form
@@ -144,6 +145,7 @@ class TestStockValuation(TransactionCase):
the currency rate, validate the receipt and then check that the value of the received goods
is set according to the last currency rate.
"""
self.env['res.currency.rate'].search([]).unlink()
usd_currency = self.env.ref('base.USD')
self.env.user.company_id.currency_id = usd_currency.id
@@ -290,6 +292,9 @@ class TestStockValuationWithCOA(AccountingTestCase):
self.partner_id = self.env.ref('base.res_partner_1')
self.product1 = self.env.ref('product.product_product_8')
Account = self.env['account.account']
self.usd_currency = self.env.ref('base.USD')
self.eur_currency = self.env.ref('base.EUR')
self.stock_input_account = Account.create({
'name': 'Stock Input',
'code': 'StockIn',
@@ -307,6 +312,11 @@ class TestStockValuationWithCOA(AccountingTestCase):
'code': 'Stock Valuation',
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
})
self.price_diff_account = Account.create({
'name': 'price diff account',
'code': 'price diff account',
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
})
self.stock_journal = self.env['account.journal'].create({
'name': 'Stock Journal',
'code': 'STJTEST',
@@ -324,12 +334,7 @@ class TestStockValuationWithCOA(AccountingTestCase):
self.product1.product_tmpl_id.cost_method = 'fifo'
self.product1.product_tmpl_id.valuation = 'real_time'
self.product1.product_tmpl_id.invoice_policy = 'delivery'
price_diff_account = self.env['account.account'].create({
'name': 'price diff account',
'code': 'price diff account',
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
})
self.product1.property_account_creditor_price_difference = price_diff_account
self.product1.property_account_creditor_price_difference = self.price_diff_account
# Receive 10@10 ; create the vendor bill
po1 = self.env['purchase.order'].create({
@@ -416,7 +421,7 @@ class TestStockValuationWithCOA(AccountingTestCase):
creditnote_po2.action_invoice_open()
# check the anglo saxon entries
price_diff_entry = self.env['account.move.line'].search([('account_id', '=', price_diff_account.id)])
price_diff_entry = self.env['account.move.line'].search([('account_id', '=', self.price_diff_account.id)])
self.assertEqual(price_diff_entry.credit, 100)
def test_anglosaxon_valuation(self):
@@ -424,12 +429,7 @@ class TestStockValuationWithCOA(AccountingTestCase):
self.product1.product_tmpl_id.cost_method = 'fifo'
self.product1.product_tmpl_id.valuation = 'real_time'
self.product1.product_tmpl_id.invoice_policy = 'delivery'
price_diff_account = self.env['account.account'].create({
'name': 'price diff account',
'code': 'price diff account',
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
})
self.product1.property_account_creditor_price_difference = price_diff_account
self.product1.property_account_creditor_price_difference = self.price_diff_account
# Create PO
po_form = Form(self.env['purchase.order'])
@@ -458,12 +458,422 @@ class TestStockValuationWithCOA(AccountingTestCase):
invoice.action_invoice_open()
# Check what was posted in the price difference account
price_diff_aml = self.env['account.move.line'].search([('account_id','=',price_diff_account.id)])
price_diff_aml = self.env['account.move.line'].search([('account_id','=', self.price_diff_account.id)])
self.assertEquals(len(price_diff_aml), 1, "Only one line should have been generated in the price difference account.")
self.assertAlmostEquals(price_diff_aml.debit, 5, "Price difference should be equal to 5 (15-10)")
# Check what was posted in stock input account
input_aml = self.env['account.move.line'].search([('account_id','=',self.stock_input_account.id)])
input_aml = self.env['account.move.line'].search([('account_id','=', self.stock_input_account.id)])
self.assertEquals(len(input_aml), 2, "Only two lines should have been generated in stock input account: one when receiving the product, one when making the invoice.")
self.assertAlmostEquals(sum(input_aml.mapped('debit')), 10, "Total debit value on stock input account should be equal to the original PO price of the product.")
self.assertAlmostEquals(sum(input_aml.mapped('credit')), 10, "Total credit value on stock input account should be equal to the original PO price of the product.")
def test_average_realtime_anglo_saxon_valuation_multicurrency_same_date(self):
"""
The PO and invoice are in the same foreign currency.
The PO is invoiced on the same date as its creation.
This shouldn't create a price difference entry.
"""
company = self.env.user.company_id
company.anglo_saxon_accounting = True
date_po = '2019-01-01'
# SetUp product
self.product1.product_tmpl_id.cost_method = 'average'
self.product1.product_tmpl_id.valuation = 'real_time'
self.product1.product_tmpl_id.invoice_policy = 'order'
self.product1.product_tmpl_id.purchase_method = 'purchase'
self.product1.property_account_creditor_price_difference = self.price_diff_account
# SetUp currency and rates
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': date_po,
'rate': 1.0,
'currency_id': self.usd_currency.id,
'company_id': company.id,
})
self.env['res.currency.rate'].create({
'name': date_po,
'rate': 1.5,
'currency_id': self.eur_currency.id,
'company_id': company.id,
})
# Proceed
po = self.env['purchase.order'].create({
'currency_id': self.eur_currency.id,
'partner_id': self.partner_id.id,
'order_line': [
(0, 0, {
'name': self.product1.name,
'product_id': self.product1.id,
'product_qty': 1.0,
'product_uom': self.product1.uom_po_id.id,
'price_unit': 100.0,
'date_planned': date_po,
}),
],
})
po.button_confirm()
inv = self.env['account.invoice'].create({
'type': 'in_invoice',
'date_invoice': date_po,
'currency_id': self.eur_currency.id,
'purchase_id': po.id,
'partner_id': self.partner_id.id,
'invoice_line_ids': [(0, 0, {
'name': 'Test',
'price_subtotal': 100.0,
'price_unit': 100.0,
'product_id': self.product1.id,
'purchase_id': po.id,
'purchase_line_id': po.order_line.id,
'quantity': 1.0,
'account_id': self.stock_input_account.id,
})]
})
inv.action_invoice_open()
move_lines = inv.move_id.line_ids
self.assertEqual(len(move_lines), 2)
payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
self.assertEqual(payable_line.amount_currency, -100.0)
self.assertAlmostEqual(payable_line.balance, -66.67)
stock_line = move_lines.filtered(lambda l: l.account_id == self.stock_input_account)
self.assertEqual(stock_line.amount_currency, 100.0)
self.assertAlmostEqual(stock_line.balance, 66.67)
def test_realtime_anglo_saxon_valuation_multicurrency_different_dates(self):
"""
The PO and invoice are in the same foreign currency.
The PO is invoiced at a later date than its creation.
This should create a price difference entry for standard cost method
Not for average cost method though, since the PO and invoice have the same currency
"""
company = self.env.user.company_id
company.anglo_saxon_accounting = True
date_po = '2019-01-01'
date_invoice = '2019-01-16'
# SetUp product Average
self.product1.product_tmpl_id.write({
'cost_method': 'average',
'valuation': 'real_time',
'purchase_method': 'purchase',
'property_account_creditor_price_difference': self.price_diff_account.id,
})
self.product1.invoice_policy = 'order'
# SetUp product Standard
# should have bought at 60 USD
# actually invoiced at 70 EUR > 35 USD
product_standard = self.product1.product_tmpl_id.copy({
'cost_method': 'standard',
'name': 'Standard Val',
'standard_price': 60,
'property_account_creditor_price_difference': self.price_diff_account.id
}).product_variant_id
# SetUp currency and rates
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': date_po,
'rate': 1.0,
'currency_id': self.usd_currency.id,
'company_id': company.id,
})
self.env['res.currency.rate'].create({
'name': date_po,
'rate': 1.5,
'currency_id': self.eur_currency.id,
'company_id': company.id,
})
self.env['res.currency.rate'].create({
'name': date_invoice,
'rate': 2,
'currency_id': self.eur_currency.id,
'company_id': company.id,
})
# To allow testing validation of PO
def _today(*args, **kwargs):
return date_po
patchers = [
patch('odoo.fields.Date.context_today', _today),
]
for p in patchers:
p.start()
# Proceed
po = self.env['purchase.order'].create({
'currency_id': self.eur_currency.id,
'partner_id': self.partner_id.id,
'order_line': [
(0, 0, {
'name': self.product1.name,
'product_id': self.product1.id,
'product_qty': 1.0,
'product_uom': self.product1.uom_po_id.id,
'price_unit': 100.0,
'date_planned': date_po,
}),
(0, 0, {
'name': product_standard.name,
'product_id': product_standard.id,
'product_qty': 1.0,
'product_uom': product_standard.uom_po_id.id,
'price_unit': 40.0,
'date_planned': date_po,
}),
],
})
po.button_confirm()
line_product_average = po.order_line.filtered(lambda l: l.product_id == self.product1)
line_product_standard = po.order_line.filtered(lambda l: l.product_id == product_standard)
inv = self.env['account.invoice'].create({
'type': 'in_invoice',
'date_invoice': date_invoice,
'currency_id': self.eur_currency.id,
'purchase_id': po.id,
'partner_id': self.partner_id.id,
'invoice_line_ids': [
(0, 0, {
'name': self.product1.name,
'price_subtotal': 100.0,
'price_unit': 100.0,
'product_id': self.product1.id,
'purchase_id': po.id,
'purchase_line_id': line_product_average.id,
'quantity': 1.0,
'account_id': self.stock_input_account.id,
}),
(0, 0, {
'name': product_standard.name,
'price_subtotal': 70.0,
'price_unit': 70.0,
'product_id': product_standard.id,
'purchase_id': po.id,
'purchase_line_id': line_product_standard.id,
'quantity': 1.0,
'account_id': self.stock_input_account.id,
})
]
})
inv.action_invoice_open()
for p in patchers:
p.stop()
move_lines = inv.move_id.line_ids
self.assertEqual(len(move_lines), 4)
# Ensure no exchange difference move has been created
self.assertTrue(all([not l.reconciled for l in move_lines]))
# PAYABLE CHECK
payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
self.assertEqual(payable_line.amount_currency, -170.0)
self.assertAlmostEqual(payable_line.balance, -85.00)
# PRODUCTS CHECKS
# NO EXCHANGE DIFFERENCE (average)
# We ordered for a value of 100 EUR
# But by the time we are invoiced for it
# the foreign currency appreciated from 1.5 to 2.0
# We still have to pay 100 EUR, which now values at 50 USD
product_lines = move_lines.filtered(lambda l: l.product_id == self.product1)
# Stock-wise, we have been invoiced 100 EUR, and we ordered 100 EUR
# there is no price difference
# However, 100 EUR should be converted at the time of the invoice
stock_line = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
self.assertEqual(stock_line.amount_currency, 100.00)
self.assertAlmostEqual(stock_line.balance, 50.00)
# PRICE DIFFERENCE (STANDARD)
# We ordered a product that should have cost 60 USD (120 EUR)
# However, we effectively got invoiced 70 EUR (35 USD)
product_lines = move_lines.filtered(lambda l: l.product_id == product_standard)
stock_line = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
self.assertEqual(stock_line.amount_currency, 120.00)
self.assertAlmostEqual(stock_line.balance, 60.00)
price_diff_line = product_lines.filtered(lambda l: l.account_id == self.price_diff_account)
self.assertEqual(price_diff_line.amount_currency, -50.00)
self.assertAlmostEqual(price_diff_line.balance, -25.00)
def test_average_realtime_with_delivery_anglo_saxon_valuation_multicurrency_different_dates(self):
"""
The PO and invoice are in the same foreign currency.
The delivery occurs in between PO validation and invoicing
The invoice is created at an even different date
This should create a price difference entry.
"""
company = self.env.user.company_id
company.anglo_saxon_accounting = True
date_po = '2019-01-01'
date_delivery = '2019-01-08'
date_invoice = '2019-01-16'
product_avg = self.product1.product_tmpl_id.copy({
'valuation': 'real_time',
'purchase_method': 'purchase',
'cost_method': 'average',
'name': 'AVG',
'standard_price': 60,
'property_account_creditor_price_difference': self.price_diff_account.id
}).product_variant_id
product_avg.invoice_policy = 'order'
# SetUp currency and rates
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': date_po,
'rate': 1.0,
'currency_id': self.usd_currency.id,
'company_id': company.id,
})
self.env['res.currency.rate'].create({
'name': date_po,
'rate': 1.5,
'currency_id': self.eur_currency.id,
'company_id': company.id,
})
self.env['res.currency.rate'].create({
'name': date_delivery,
'rate': 0.7,
'currency_id': self.eur_currency.id,
'company_id': company.id,
})
self.env['res.currency.rate'].create({
'name': date_invoice,
'rate': 2,
'currency_id': self.eur_currency.id,
'company_id': company.id,
})
# To allow testing validation of PO
def _today(*args, **kwargs):
return date_po
# To allow testing validation of Delivery
def _now(*args, **kwargs):
return date_delivery + ' 01:00:00'
patchers = [
patch('odoo.fields.Date.context_today', _today),
patch('odoo.fields.Datetime.now', _now),
]
for p in patchers:
p.start()
# Proceed
po = self.env['purchase.order'].create({
'currency_id': self.eur_currency.id,
'partner_id': self.partner_id.id,
'order_line': [
(0, 0, {
'name': product_avg.name,
'product_id': product_avg.id,
'product_qty': 1.0,
'product_uom': product_avg.uom_po_id.id,
'price_unit': 30.0,
'date_planned': date_po,
})
],
})
po.button_confirm()
line_product_avg = po.order_line.filtered(lambda l: l.product_id == product_avg)
picking = po.picking_ids
(picking.move_lines
.filtered(lambda l: l.purchase_line_id == line_product_avg)
.write({'quantity_done': 1.0}))
picking.button_validate()
inv = self.env['account.invoice'].create({
'type': 'in_invoice',
'date_invoice': date_invoice,
'currency_id': self.eur_currency.id,
'purchase_id': po.id,
'partner_id': self.partner_id.id,
'invoice_line_ids': [
(0, 0, {
'name': product_avg.name,
'price_subtotal': 30.0,
'price_unit': 30.0,
'product_id': product_avg.id,
'purchase_id': po.id,
'purchase_line_id': line_product_avg.id,
'quantity': 1.0,
'account_id': self.stock_input_account.id,
})
]
})
inv.action_invoice_open()
for p in patchers:
p.stop()
move_lines = inv.move_id.line_ids
self.assertEqual(len(move_lines), 2)
# PAYABLE CHECK
payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
self.assertEqual(payable_line.amount_currency, -30.0)
self.assertAlmostEqual(payable_line.balance, -15.00)
# PRODUCTS CHECKS
# DELIVERY DIFFERENCE (AVERAGE)
# We ordered a product at 30 EUR valued at 20 USD
# We received it when the exchange rate has appreciated
# So, the actualized 20 USD are now 20*1.5/0.7 = 42.86 USD
product_lines = move_lines.filtered(lambda l: l.product_id == product_avg)
# Although those 42.86 USD are just due to the exchange difference
stock_line = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
self.assertEqual(stock_line.journal_id, inv.journal_id)
self.assertEqual(stock_line.amount_currency, 30.00)
self.assertAlmostEqual(stock_line.balance, 15.00)
full_reconcile = stock_line.full_reconcile_id
self.assertTrue(full_reconcile.exists())
reconciled_lines = full_reconcile.reconciled_line_ids - stock_line
self.assertEqual(len(reconciled_lines), 2)
stock_journal_line = reconciled_lines.filtered(lambda l: l.journal_id == self.stock_journal)
self.assertEqual(stock_journal_line.amount_currency, -30.00)
self.assertAlmostEqual(stock_journal_line.balance, -42.86)
exhange_diff_journal = company.currency_exchange_journal_id.exists()
exchange_stock_line = reconciled_lines.filtered(lambda l: l.journal_id == exhange_diff_journal)
self.assertEqual(exchange_stock_line.amount_currency, 0.00)
self.assertAlmostEqual(exchange_stock_line.balance, 27.86)
+3 -3
View File
@@ -484,8 +484,8 @@
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_id))"/>
</td>
<td class="text-right">
<span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
<span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
<span class="oe_order_line_price_subtotal" t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
<span class="oe_order_line_price_total" t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
</td>
</t>
<t t-if="line.display_type == 'line_section'">
@@ -524,7 +524,7 @@
<td><strong>Subtotal</strong></td>
<td class="text-right">
<span
data-id="total_amount"
data-id="total_untaxed"
t-field="sale_order.amount_untaxed"
t-options='{"widget": "monetary","display_currency": sale_order.pricelist_id.currency_id}'
/>
+30 -3
View File
@@ -1,7 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from functools import partial
from odoo import http
from odoo.tools import formatLang
from odoo.exceptions import AccessError, MissingError
from odoo.http import request
from odoo.addons.sale.controllers.portal import CustomerPortal
@@ -11,6 +14,13 @@ class CustomerPortal(CustomerPortal):
@http.route(['/my/orders/<int:order_id>/update_line'], type='json', auth="public", website=True)
def update(self, line_id, remove=False, unlink=False, order_id=None, access_token=None, **post):
values = self.update_line_dict(line_id, remove, unlink, order_id, access_token, **post)
if values:
return [values['order_line_product_uom_qty'], values['order_amount_total']]
return values
@http.route(['/my/orders/<int:order_id>/update_line_dict'], type='json', auth="public", website=True)
def update_line_dict(self, line_id, remove=False, unlink=False, order_id=None, access_token=None, input_quantity=False, **kwargs):
try:
order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token)
except (AccessError, MissingError):
@@ -24,10 +34,27 @@ class CustomerPortal(CustomerPortal):
if unlink:
order_line.unlink()
return False # return False to reload the page, the line must move back to options and the JS doesn't handle it
number = -1 if remove else 1
quantity = order_line.product_uom_qty + number
if input_quantity is not False:
quantity = input_quantity
else:
number = -1 if remove else 1
quantity = order_line.product_uom_qty + number
if quantity < 0:
quantity = 0.0
order_line.write({'product_uom_qty': quantity})
return [str(quantity), str(order_sudo.amount_total)]
currency = order_sudo.currency_id
format_price = partial(formatLang, request.env, digits=currency.decimal_places)
return {
'order_line_product_uom_qty': str(quantity),
'order_line_price_total': format_price(order_line.price_total),
'order_line_price_subtotal': format_price(order_line.price_subtotal),
'order_amount_total': format_price(order_sudo.amount_total),
'order_amount_untaxed': format_price(order_sudo.amount_untaxed),
'order_amount_tax': format_price(order_sudo.amount_tax),
}
@http.route(["/my/orders/<int:order_id>/add_option/<int:option_id>"], type='http', auth="public", website=True)
def add(self, order_id, option_id, access_token=None, **post):
@@ -14,31 +14,155 @@ if (!$('.o_portal_sale_sidebar').length) {
events: {
'click' : 'onClick',
},
onClick: function (ev) {
ev.preventDefault();
/**
* @override
*/
start: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
self.elems = self._getUpdatableElements();
self.elems.$lineQuantity.change(function (ev) {
var quantity = parseInt(this.value);
self._onChangeQuantity(quantity);
});
});
},
/**
* Process the change in line quantity
*
* @private
* @param {Int} quantity, the new quantity of the line
* If not present it will increment/decrement the existing quantity
*/
_onChangeQuantity: function (quantity) {
var href = this.$el.attr("href");
var order_id = href.match(/my\/orders\/([0-9]+)/);
var line_id = href.match(/update_line\/([0-9]+)/);
var params = {
'line_id': line_id[1],
'remove': self.$el.is('[href*="remove"]'),
'unlink': self.$el.is('[href*="unlink"]'),
'remove': this.$el.is('[href*="remove"]'),
'unlink': this.$el.is('[href*="unlink"]'),
'input_quantity': quantity >= 0 ? quantity : false,
};
var token = href.match(/token=(.*)/);
var token = href.match(/token=([\w\d-]*)/)[1];
if (token) {
params.access_token = token;
}
var url = "/my/orders/" + parseInt(order_id[1]) + "/update_line";
ajax.jsonRpc(url, 'call', params).then(function (data) {
if (!data) {
window.location.reload();
}
self.$el.parents('.input-group:first').find('.js_quantity').val(data[0]);
$('[data-id="total_amount"]>span').html(data[1]);
});
this._callUpdateLineRoute(order_id, params).then(this._updateOrderValues.bind(this));
return false;
},
/**
* Reacts to the click on the -/+ buttons
*
* @param {Event} ev
*/
onClick: function (ev) {
ev.preventDefault();
return this._onChangeQuantity();
},
/**
* Calls the route to get updated values of the line and order
* when the quantity of a product has changed
*
* @private
* @param {Object} params
* @return {Deferred}
*/
_callUpdateLineRoute: function (order_id, params) {
var def = new $.Deferred();
var url = "/my/orders/" + parseInt(order_id[1]) + "/update_line_dict";
ajax.jsonRpc(url, 'call', params)
.then(def.resolve.bind(def))
.fail(function () {
// Compatibility: the server may not have been restarted
// So the real route may not exist
delete params.input_quantity;
ajax.jsonRpc("/quote/update_line", 'call', params)
.fail(def.reject.bind(def))
.then(function (data) {
// Data is an array, convert it to a dict
var actualData = data;
if (data) {
actualData = {
order_amount_total: data[1],
order_line_product_uom_qty: data[0],
};
}
def.resolve(actualData);
});
});
return def;
},
/**
* Processes data from the server to update the UI
*
* @private
* @param {Object} data: contains order and line updated values
*/
_updateOrderValues: function (data) {
if (!data) {
window.location.reload();
}
var orderAmountTotal = data.order_amount_total;
var orderAmountUntaxed = data.order_amount_untaxed;
var orderAmountTax = data.order_amount_tax;
var lineProductUomQty = data.order_line_product_uom_qty;
var linePriceTotal = data.order_line_price_total;
var linePriceSubTotal = data.order_line_price_subtotal;
this.elems.$lineQuantity.val(lineProductUomQty)
if (this.elems.$linePriceTotal.length && linePriceTotal !== undefined) {
this.elems.$linePriceTotal.text(linePriceTotal);
}
if (this.elems.$linePriceSubTotal.length && linePriceSubTotal !== undefined) {
this.elems.$linePriceSubTotal.text(linePriceSubTotal);
}
if (orderAmountUntaxed !== undefined) {
this.elems.$orderAmountUntaxed.text(orderAmountUntaxed);
}
if (orderAmountTotal !== undefined) {
this.elems.$orderAmountTotal.text(orderAmountTotal);
}
},
/**
* Locate in the DOM the elements to update
* Mostly for compatibility, when the module has not been upgraded
* In that case, we need to fall back to some other elements
*
* @private
* @return {Object}: Jquery elements to update
*/
_getUpdatableElements: function () {
var $parentTr = this.$el.parents('tr:first');
var $linePriceTotal = $parentTr.find('.oe_order_line_price_total .oe_currency_value');
var $linePriceSubTotal = $parentTr.find('.oe_order_line_price_subtotal .oe_currency_value');
if (!$linePriceTotal.length && !$linePriceSubTotal.length) {
$linePriceTotal = $linePriceSubTotal = $parentTr.find('.oe_currency_value').last();
}
var $orderAmountUntaxed = $('[data-id="total_untaxed"]>span');
var $orderAmountTotal = $('[data-id="total_amount"]>span');
if (!$orderAmountUntaxed.length) {
$orderAmountUntaxed = $orderAmountTotal.eq(1);
$orderAmountTotal = $orderAmountTotal.eq(0).add($orderAmountTotal.eq(3));
}
return {
$lineQuantity: this.$el.parents('.input-group:first').find('.js_quantity'),
$linePriceSubTotal: $linePriceSubTotal,
$linePriceTotal: $linePriceTotal,
$orderAmountUntaxed: $orderAmountUntaxed,
$orderAmountTotal: $orderAmountTotal,
}
}
});
var update_button_list = [];
+13
View File
@@ -60,6 +60,19 @@ class SaleOrderLine(models.Model):
return self.env.context['previous_product_uom_qty'].get(self.id, 0.0)
return super(SaleOrderLine, self)._get_qty_procurement()
@api.multi
@api.depends('product_id', 'move_ids.state')
def _compute_qty_delivered_method(self):
lines = self.env['sale.order.line']
for line in self:
bom = self.env['mrp.bom']._bom_find(product=line.product_id, company_id=line.company_id.id)
if bom and bom.type == 'phantom' and line.order_id.state == 'sale':
bom_delivered = all([move.state == 'done' for move in line.move_ids])
if not bom_delivered:
line.qty_delivered_method = 'manual'
lines |= line
super(SaleOrderLine, self - lines)._compute_qty_delivered_method()
class AccountInvoiceLine(models.Model):
# TDE FIXME: what is this code ??
@@ -7,6 +7,13 @@
<field name="inherit_id" ref="sale_management.sale_order_form_quote"/>
<field name="arch" type="xml">
<xpath expr="//page/field[@name='order_line']/tree/field[@name='name']" position="after">
<field name="website_description" invisible="1" readonly="1"/>
</xpath>
<xpath expr="//page/field[@name='order_line']/form/field[@name='name']" position="after">
<field name="website_description" invisible="1" readonly="1"/>
</xpath>
<xpath expr="//button[@name='button_add_to_order']" position="after">
<field name="website_description" invisible="1"/>
</xpath>
+3 -1
View File
@@ -15,7 +15,9 @@ class PurchaseOrderLine(models.Model):
return res
def _merge_in_existing_line(self, product_id, product_qty, product_uom, location_id, name, origin, values):
if values.get('route_ids') and values['route_ids'] == self.env.ref('stock_dropshipping.route_drop_shipping'):
if values.get('sale_line_id') and values['sale_line_id'] != self.sale_line_id.id:
# if this is defined, this is a dropshipping line, so no
# this is to correctly map delivered quantities to the so lines
return False
return super(PurchaseOrderLine, self)._merge_in_existing_line(
product_id=product_id, product_qty=product_qty, product_uom=product_uom,
@@ -48,6 +48,22 @@ class TestDropship(common.TransactionCase):
so.order_line.product_uom_qty = 2.00
self.assertAlmostEqual(po_line.product_qty, 2.00)
# Create a new so line
sol2 = self.env['sale.order.line'].create({
'order_id': so.id,
'name': prod.name,
'product_id': prod.id,
'product_uom_qty': 3.00,
'product_uom': prod.uom_id.id,
'price_unit': 12,
})
# there is a new line
pol2 = po.order_line - po_line
# the first line is unchanged
self.assertAlmostEqual(po_line.product_qty, 2.00)
# the new line matches the new line on the so
self.assertAlmostEqual(pol2.product_qty, sol2.product_uom_qty)
def test_00_dropship(self):
# Create a vendor
@@ -41,7 +41,7 @@ class SaleOrder(models.Model):
values['product_id'] = ticket.product_id.id
values['event_id'] = ticket.event_id.id
values['event_ticket_id'] = ticket.id
values['price_unit'] = ticket.price_reduce or ticket.price
values['price_unit'] = ticket.price_reduce
values['name'] = ticket.get_ticket_multiline_description_sale()
# avoid writing related values that end up locking the product record
+3
View File
@@ -127,7 +127,10 @@ class SaleOrder(models.Model):
pu = currency._convert(pu, order.pricelist_id.currency_id, order.company_id, date)
discount = (pu - price) / pu * 100
if discount < 0:
# In case the discount is negative, we don't want to show it to the customer,
# but we still want to use the price defined on the pricelist
discount = 0
pu = price
else:
pu = product.price
if order.pricelist_id and order.partner_id: