+
+ Print Invoice
+
Print Receipt
diff --git a/addons/portal/controllers/mail.py b/addons/portal/controllers/mail.py
index dcccbb61fa1..dc83f05a3d2 100644
--- a/addons/portal/controllers/mail.py
+++ b/addons/portal/controllers/mail.py
@@ -112,7 +112,7 @@ class PortalChatter(http.Controller):
raise Forbidden()
# Non-employee see only messages with not internal subtype (aka, no internal logs)
if not request.env['res.users'].has_group('base.group_user'):
- domain = expression.AND([['&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)], domain])
+ domain = expression.AND([['&', '&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False), ('website_published', '=', True)], domain])
Message = request.env['mail.message'].sudo()
return {
'messages': Message.search(domain, limit=limit, offset=offset).portal_message_format(),
diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py
index 08b1aa26a8c..3c842b793ba 100644
--- a/addons/product/models/product_template.py
+++ b/addons/product/models/product_template.py
@@ -380,6 +380,9 @@ class ProductTemplate(models.Model):
related_vals['volume'] = vals['volume']
if vals.get('weight'):
related_vals['weight'] = vals['weight']
+ # Please do forward port
+ if vals.get('packaging_ids'):
+ related_vals['packaging_ids'] = vals['packaging_ids']
if related_vals:
template.write(related_vals)
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index 27b6d4b21f9..0ed941374b5 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -461,7 +461,6 @@ class Task(models.Model):
date_assign = fields.Datetime(string='Assigning Date', index=True, copy=False, readonly=True)
date_deadline = fields.Date(string='Deadline', index=True, copy=False, track_visibility='onchange')
date_last_stage_update = fields.Datetime(string='Last Stage Update',
- default=fields.Datetime.now,
index=True,
copy=False,
readonly=True)
@@ -679,9 +678,10 @@ class Task(models.Model):
# user_id change: update date_assign
if vals.get('user_id'):
vals['date_assign'] = fields.Datetime.now()
- # Stage change: Update date_end if folded stage
+ # Stage change: Update date_end if folded stage and date_last_stage_update
if vals.get('stage_id'):
vals.update(self.update_date_end(vals['stage_id']))
+ vals['date_last_stage_update'] = fields.Datetime.now()
task = super(Task, self.with_context(context)).create(vals)
return task
diff --git a/addons/purchase_stock/models/account_invoice.py b/addons/purchase_stock/models/account_invoice.py
index 048853d4c64..78cd9ba2707 100644
--- a/addons/purchase_stock/models/account_invoice.py
+++ b/addons/purchase_stock/models/account_invoice.py
@@ -48,7 +48,6 @@ class AccountInvoice(models.Model):
inv.currency_id,
company=inv.company_id, date=fields.Date.today(), round=False,
)
- line_quantity = line['quantity']
if i_line.product_id.cost_method != 'standard' and i_line.purchase_line_id:
po_currency = i_line.purchase_id.currency_id
@@ -86,7 +85,6 @@ class AccountInvoice(models.Model):
# 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
elif i_line.product_id.cost_method == 'fifo':
# In this condition, we have a real price-valuated product which has not yet been received
@@ -95,7 +93,7 @@ class AccountInvoice(models.Model):
company=po_company, date=inv.date or inv.date_invoice, round=False,
)
- interim_account_price = valuation_price_unit * line_quantity
+ interim_account_price = valuation_price_unit * line['quantity']
invoice_cur_prec = inv.currency_id.decimal_places
if float_compare(valuation_price_unit, i_line.price_unit, precision_digits=invoice_cur_prec) != 0 and float_compare(line['price_unit'], i_line.price_unit, precision_digits=invoice_cur_prec) == 0:
@@ -126,7 +124,7 @@ class AccountInvoice(models.Model):
'type': 'src',
'name': i_line.name[:64],
'price_unit': price_unit_val_dif,
- 'quantity': line_quantity,
+ 'quantity': line['quantity'],
'price': inv.currency_id.round(price_val_dif),
'account_id': acc,
'product_id': line['product_id'],
diff --git a/addons/purchase_stock/tests/test_stockvaluation.py b/addons/purchase_stock/tests/test_stockvaluation.py
index e15b8a2063b..708fb05aa17 100644
--- a/addons/purchase_stock/tests/test_stockvaluation.py
+++ b/addons/purchase_stock/tests/test_stockvaluation.py
@@ -877,3 +877,244 @@ class TestStockValuationWithCOA(AccountingTestCase):
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)
+
+ def test_average_realtime_with_two_delivery_anglo_saxon_valuation_multicurrency_different_dates(self):
+ """
+ The PO and invoice are in the same foreign currency.
+ The deliveries occur at different times and rates
+ 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_delivery1 = '2019-01-10'
+ date_invoice = '2019-01-16'
+ date_invoice1 = '2019-01-20'
+
+ 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_delivery1,
+ 'rate': 0.8,
+ '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,
+ })
+ self.env['res.currency.rate'].create({
+ 'name': date_invoice1,
+ 'rate': 2.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
+ delivery_now = date_delivery
+ def _now(*args, **kwargs):
+ return delivery_now + ' 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': 10.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': 5.0}))
+
+ picking.button_validate()
+ picking.action_done() # Create Backorder
+
+ 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': 100.0,
+ 'price_unit': 20.0,
+ 'product_id': product_avg.id,
+ 'purchase_id': po.id,
+ 'purchase_line_id': line_product_avg.id,
+ 'quantity': 5.0,
+ 'account_id': self.stock_input_account.id,
+ })
+ ]
+ })
+
+ inv.action_invoice_open()
+
+ backorder_picking = self.env['stock.picking'].search([('backorder_id', '=', picking.id)])
+ delivery_now = date_delivery1
+ (backorder_picking.move_lines
+ .filtered(lambda l: l.purchase_line_id == line_product_avg)
+ .write({'quantity_done': 5.0}))
+ backorder_picking.button_validate()
+
+ inv1 = self.env['account.invoice'].create({
+ 'type': 'in_invoice',
+ 'date_invoice': date_invoice1,
+ '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': 200.0,
+ 'price_unit': 40.0,
+ 'product_id': product_avg.id,
+ 'purchase_id': po.id,
+ 'purchase_line_id': line_product_avg.id,
+ 'quantity': 5.0,
+ 'account_id': self.stock_input_account.id,
+ })
+ ]
+ })
+
+ inv1.action_invoice_open()
+
+ for p in patchers:
+ p.stop()
+
+ ##########################
+ # Invoice 0 #
+ ##########################
+ move_lines = inv.move_id.line_ids
+ self.assertEqual(len(move_lines), 3)
+
+ # PAYABLE CHECK
+ 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, -50.00)
+
+ # # PRODUCTS CHECKS
+
+ # DELIVERY DIFFERENCE (AVERAGE)
+ stock_line = move_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, 150.00)
+ self.assertAlmostEqual(stock_line.balance, 75.00)
+
+ price_diff_line = move_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)
+
+ 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, -150)
+ self.assertAlmostEqual(stock_journal_line.balance, -214.29)
+
+ 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, 139.29)
+
+ ##########################
+ # Invoice 1 #
+ ##########################
+ move_lines = inv1.move_id.line_ids
+ self.assertEqual(len(move_lines), 3)
+
+ # PAYABLE CHECK
+ payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
+ self.assertEqual(payable_line.amount_currency, -200.0)
+ self.assertAlmostEqual(payable_line.balance, -90.91)
+
+ # # PRODUCTS CHECKS
+
+ # DELIVERY DIFFERENCE (AVERAGE)
+ stock_line = move_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, 150.00)
+ self.assertAlmostEqual(stock_line.balance, 68.18)
+
+ price_diff_line = move_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, 22.73)
+
+ 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, -150)
+ self.assertAlmostEqual(stock_journal_line.balance, -187.5)
+
+ 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, 119.32)
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 44bfa04dcc8..d2d3b8b1e8d 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -1099,7 +1099,7 @@ class SaleOrderLine(models.Model):
result = super(SaleOrderLine, self).write(values)
return result
- order_id = fields.Many2one('sale.order', string='Order Reference', required=True, ondelete='cascade', index=True, copy=False, readonly=True)
+ order_id = fields.Many2one('sale.order', string='Order Reference', required=True, ondelete='cascade', index=True, copy=False)
name = fields.Text(string='Description', required=True)
sequence = fields.Integer(string='Sequence', default=10)
diff --git a/addons/sale/views/sale_portal_templates.xml b/addons/sale/views/sale_portal_templates.xml
index d6afd43f68a..192a22fc918 100644
--- a/addons/sale/views/sale_portal_templates.xml
+++ b/addons/sale/views/sale_portal_templates.xml
@@ -127,7 +127,7 @@
-
+
@@ -167,14 +167,15 @@
+ class="d-block mt-1"
+ data-id="amount_undiscounted" />
-
+
-
- ( Incl. tax)
+
+ ( Incl. tax)
@@ -374,7 +375,7 @@