[MERGE] forward port branch saas-11.3 up to 2c68df0ab4
This commit is contained in:
@@ -994,7 +994,7 @@ class AccountMoveLine(models.Model):
|
||||
|
||||
partner_id = self.env['res.partner']._find_accounting_partner(self[0].partner_id).id
|
||||
company_currency = self[0].account_id.company_id.currency_id
|
||||
writeoff_currency = self[0].currency_id or company_currency
|
||||
writeoff_currency = self[0].account_id.currency_id or company_currency
|
||||
line_to_reconcile = self.env['account.move.line']
|
||||
# Iterate and create one writeoff by journal
|
||||
writeoff_moves = self.env['account.move']
|
||||
|
||||
@@ -831,3 +831,17 @@ class account_payment(models.Model):
|
||||
})
|
||||
|
||||
return vals
|
||||
|
||||
def _get_invoice_payment_amount(self, inv):
|
||||
"""
|
||||
Computes the amount covered by the current payment in the given invoice.
|
||||
|
||||
:param inv: an invoice object
|
||||
:returns: the amount covered by the payment in the invoice
|
||||
"""
|
||||
self.ensure_one()
|
||||
return sum([
|
||||
data['amount']
|
||||
for data in inv._get_payments_vals()
|
||||
if data['account_payment_id'] == self.id
|
||||
])
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<td><span t-field="inv.date_invoice"/></td>
|
||||
<td><span t-field="inv.number"/></td>
|
||||
<td class="text-right"><span t-field="inv.amount_total"/></td>
|
||||
<td class="text-right"><span t-esc="inv.amount_total - inv.residual" t-options="{'widget': 'monetary', 'display_currency': inv.currency_id}"/></td>
|
||||
<td class="text-right"><span t-esc="o._get_invoice_payment_amount(inv)" t-options="{'widget': 'monetary', 'display_currency': inv.currency_id}"/></td>
|
||||
<td class="text-right"><span t-field="inv.residual"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<record id="hr_contract_form_in_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.contract.form.in.inherit</field>
|
||||
<field name="model">hr.contract</field>
|
||||
<field name="inherit_id" ref="hr_payroll.hr_contract_form_inherit"/>
|
||||
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='struct_id']" position="after">
|
||||
<xpath expr="//group[@name='duration_group']" position="after">
|
||||
<group col="2" colspan="2" name="right_column">
|
||||
<separator colspan="2" string="Allowance"/>
|
||||
<field name="driver_salay"/>
|
||||
|
||||
@@ -745,7 +745,7 @@ class PosOrder(models.Model):
|
||||
|
||||
if to_invoice:
|
||||
pos_order.action_pos_order_invoice()
|
||||
pos_order.invoice_id.sudo().action_invoice_open()
|
||||
pos_order.invoice_id.sudo().with_context(force_company=self.env.user.company_id.id).action_invoice_open()
|
||||
pos_order.account_move = pos_order.invoice_id.move_id
|
||||
return order_ids
|
||||
|
||||
|
||||
@@ -178,6 +178,61 @@ var ScreenWidget = PosBaseWidget.extend({
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Handles the error response from the server when we push
|
||||
* an invoiceable order
|
||||
* Displays appropriates warnings and errors and
|
||||
* proposes subsequent actions
|
||||
*
|
||||
* @private
|
||||
* @param {PosModel} order: the order to consider, defaults to current order
|
||||
* @param {Boolean} refresh_screens: whether or not displayed screens should refresh
|
||||
* @param {Object} error: the error provided by Ajax
|
||||
*/
|
||||
_handleFailedPushForInvoice: function (order, refresh_screen, error) {
|
||||
var self = this;
|
||||
order = order || this.pos.get_order();
|
||||
this.invoicing = false;
|
||||
order.finalized = false;
|
||||
if (error.message === 'Missing Customer') {
|
||||
this.gui.show_popup('confirm',{
|
||||
'title': _t('Please select the Customer'),
|
||||
'body': _t('You need to select the customer before you can invoice an order.'),
|
||||
confirm: function(){
|
||||
self.gui.show_screen('clientlist', null, refresh_screen);
|
||||
},
|
||||
});
|
||||
} else if (error.message === 'Backend Invoice') {
|
||||
this.gui.show_popup('confirm',{
|
||||
'title': _t('Please print the invoice from the backend'),
|
||||
'body': _t('The order has been synchronized earlier. Please make the invoice from the backend for the order: ') + error.data.order.name,
|
||||
confirm: function () {
|
||||
this.gui.show_screen('receipt', null, refresh_screen);
|
||||
},
|
||||
cancel: function () {
|
||||
this.gui.show_screen('receipt', null, refresh_screen);
|
||||
},
|
||||
});
|
||||
} else if (error.code < 0) { // XmlHttpRequest Errors
|
||||
this.gui.show_popup('error',{
|
||||
'title': _t('The order could not be sent'),
|
||||
'body': _t('Check your internet connection and try again.'),
|
||||
cancel: function () {
|
||||
this.gui.show_screen('receipt', {button_print_invoice: true}, refresh_screen); // refresh if necessary
|
||||
},
|
||||
});
|
||||
} else if (error.code === 200) { // OpenERP Server Errors
|
||||
this.gui.show_popup('error-traceback',{
|
||||
'title': error.data.message || _t("Server Error"),
|
||||
'body': error.data.debug || _t('The server encountered an error while receiving your order.'),
|
||||
});
|
||||
} else { // ???
|
||||
this.gui.show_popup('error',{
|
||||
'title': _t("Unknown Error"),
|
||||
'body': _t("The order could not be sent to the server due to an unknown error"),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/*--------------------------------------*\
|
||||
@@ -1547,7 +1602,9 @@ var ReceiptScreenWidget = ScreenWidget.extend({
|
||||
return this.pos.config.iface_print_auto && !this.pos.get_order()._printed;
|
||||
},
|
||||
should_close_immediately: function() {
|
||||
return this.pos.config.iface_print_via_proxy && this.pos.config.iface_print_skip_screen;
|
||||
var order = this.pos.get_order();
|
||||
var invoiced_finalized = order.is_to_invoice() ? order.finalized : true;
|
||||
return this.pos.config.iface_print_via_proxy && this.pos.config.iface_print_skip_screen && invoiced_finalized;
|
||||
},
|
||||
lock_screen: function(locked) {
|
||||
this._locked = locked;
|
||||
@@ -1651,9 +1708,32 @@ var ReceiptScreenWidget = ScreenWidget.extend({
|
||||
self.print();
|
||||
}
|
||||
});
|
||||
var button_print_invoice = this.$('.button.print_invoice');
|
||||
button_print_invoice.click(function () {
|
||||
var order = self.pos.get_order();
|
||||
var invoiced = self.pos.push_and_invoice_order(order);
|
||||
self.invoicing = true;
|
||||
|
||||
invoiced.fail(self._handleFailedPushForInvoice.bind(self, order, true)); // refresh
|
||||
|
||||
invoiced.done(function(){
|
||||
self.invoicing = false;
|
||||
self.gui.show_screen('receipt', {button_print_invoice: false}, true); // refresh
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
render_change: function() {
|
||||
var self = this;
|
||||
this.$('.change-value').html(this.format_currency(this.pos.get_order().get_change()));
|
||||
var order = this.pos.get_order();
|
||||
var order_screen_params = order.get_screen_data('params');
|
||||
var button_print_invoice = this.$('.button.print_invoice');
|
||||
if (order_screen_params && order_screen_params.button_print_invoice) {
|
||||
button_print_invoice.show();
|
||||
} else {
|
||||
button_print_invoice.hide();
|
||||
}
|
||||
},
|
||||
render_receipt: function() {
|
||||
this.$('.pos-receipt-container').html(QWeb.render('PosTicket', this.get_receipt_render_env()));
|
||||
@@ -2078,45 +2158,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
|
||||
var invoiced = this.pos.push_and_invoice_order(order);
|
||||
this.invoicing = true;
|
||||
|
||||
invoiced.fail(function(error){
|
||||
self.invoicing = false;
|
||||
order.finalized = false;
|
||||
if (error.message === 'Missing Customer') {
|
||||
self.gui.show_popup('confirm',{
|
||||
'title': _t('Please select the Customer'),
|
||||
'body': _t('You need to select the customer before you can invoice an order.'),
|
||||
confirm: function(){
|
||||
self.gui.show_screen('clientlist');
|
||||
},
|
||||
});
|
||||
} else if (error.message === 'Backend Invoice') {
|
||||
self.gui.show_popup('confirm',{
|
||||
'title': _t('Please print the invoice from the backend'),
|
||||
'body': _t('The order has been synchronized earlier. Please make the invoice from the backend for the order: ') + error.data.order.name,
|
||||
confirm: function () {
|
||||
this.gui.show_screen('receipt');
|
||||
},
|
||||
cancel: function () {
|
||||
this.gui.show_screen('receipt');
|
||||
},
|
||||
});
|
||||
} else if (error.code < 0) { // XmlHttpRequest Errors
|
||||
self.gui.show_popup('error',{
|
||||
'title': _t('The order could not be sent'),
|
||||
'body': _t('Check your internet connection and try again.'),
|
||||
});
|
||||
} else if (error.code === 200) { // OpenERP Server Errors
|
||||
self.gui.show_popup('error-traceback',{
|
||||
'title': error.data.message || _t("Server Error"),
|
||||
'body': error.data.debug || _t('The server encountered an error while receiving your order.'),
|
||||
});
|
||||
} else { // ???
|
||||
self.gui.show_popup('error',{
|
||||
'title': _t("Unknown Error"),
|
||||
'body': _t("The order could not be sent to the server due to an unknown error"),
|
||||
});
|
||||
}
|
||||
});
|
||||
invoiced.fail(this._handleFailedPushForInvoice.bind(this, order, false));
|
||||
|
||||
invoiced.done(function(){
|
||||
self.invoicing = false;
|
||||
|
||||
@@ -712,6 +712,9 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="centered-content touch-scrollable">
|
||||
<div class="button print_invoice">
|
||||
<i class='fa fa-print'></i> Print Invoice
|
||||
</div>
|
||||
<div class="button print">
|
||||
<i class='fa fa-print'></i> Print Receipt
|
||||
</div>
|
||||
|
||||
@@ -360,6 +360,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)
|
||||
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1095,3 +1095,12 @@ class Picking(models.Model):
|
||||
action['domain'] = [('id', 'in', packages.ids)]
|
||||
action['context'] = {'picking_id': self.id}
|
||||
return action
|
||||
|
||||
def action_picking_move_tree(self):
|
||||
action = self.env.ref('stock.stock_move_action').read()[0]
|
||||
action['views'] = [
|
||||
(self.env.ref('stock.view_picking_move_tree').id, 'tree'),
|
||||
]
|
||||
action['context'] = self.env.context
|
||||
action['domain'] = [('picking_id', 'in', self.ids)]
|
||||
return action
|
||||
|
||||
@@ -66,6 +66,38 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_picking_move_tree" model="ir.ui.view">
|
||||
<field name="name">stock.picking.move.tree</field>
|
||||
<field name="model">stock.move</field>
|
||||
<field eval="50" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree decoration-danger="state != 'done' and quantity_done > reserved_availability and show_reserved_availability" decoration-muted="scrapped == True or state == 'cancel' or (state == 'done' and is_locked == True)" string="Stock Moves" editable="bottom">
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="date_expected" invisible="1"/>
|
||||
<field name="state" invisible="1" readonly="0"/>
|
||||
<field name="picking_type_id" invisible="1"/>
|
||||
<field name="location_id" invisible="1"/>
|
||||
<field name="location_dest_id" invisible="1"/>
|
||||
<field name="scrapped" invisible="1"/>
|
||||
<field name="picking_code" invisible="1"/>
|
||||
<field name="product_type" invisible="1"/>
|
||||
<field name="show_details_visible" invisible="1"/>
|
||||
<field name="show_reserved_availability" invisible="1"/>
|
||||
<field name="show_operations" invisible="1" readonly="1"/>
|
||||
<field name="additional" invisible="1"/>
|
||||
<field name="has_move_lines" invisible="1"/>
|
||||
<field name="is_locked" invisible="1"/>
|
||||
<field name="product_id" required="1" attrs="{'readonly': ['|', '&', ('state', '!=', 'draft'), ('additional', '=', False), ('has_move_lines', '=', True)]}"/>
|
||||
<field name="is_initial_demand_editable" invisible="1"/>
|
||||
<field name="is_quantity_done_editable" invisible="1"/>
|
||||
<field name="product_uom_qty" string="Initial Demand" attrs="{'readonly': [('is_initial_demand_editable', '=', False)]}"/>
|
||||
<field name="reserved_availability" string="Reserved"/>
|
||||
<field name="quantity_done" string="Done" attrs="{'readonly': [('is_quantity_done_editable', '=', False)]}"/>
|
||||
<field name="product_uom" attrs="{'readonly': [('state', '!=', 'draft'), ('additional', '=', False)]}" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="product.group_uom"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_move_kandan" model="ir.ui.view">
|
||||
<field name="name">stock.move.kanban</field>
|
||||
<field name="model">stock.move</field>
|
||||
|
||||
@@ -240,6 +240,19 @@
|
||||
class="oe_stat_button" icon="fa-cubes"
|
||||
attrs="{'invisible': [('has_packages', '=', False)]}"/>
|
||||
<button name="%(action_stock_report)d" icon="fa-arrow-up" class="oe_stat_button" string="Traceability Report" type="action" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('has_tracking', '=', False)]}" groups="stock.group_production_lot"/>
|
||||
<!-- Use the following button to avoid onchange on one2many -->
|
||||
<button name="action_picking_move_tree"
|
||||
class="oe_stat_button"
|
||||
icon="fa-arrows-v"
|
||||
type="object"
|
||||
help="List view of operations"
|
||||
groups="base.group_no_one"
|
||||
attrs="{'invisible': ['|', '&', ('show_operations', '=', True), '|', ('is_locked', '=', True), ('state', '=', 'done'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}"
|
||||
context="{'picking_type_code': picking_type_code, 'default_picking_id': id, 'form_view_ref':'stock.view_move_picking_form', 'address_in_id': partner_id, 'default_picking_type_id': picking_type_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id}">
|
||||
<div class="o_form_field o_stat_info">
|
||||
<span class="o_stat_text">Operations</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="d-none d-md-block">
|
||||
<field name="name" attrs="{'invisible': [('name','=','/')]}" readonly="1"/>
|
||||
|
||||
@@ -60,10 +60,11 @@ return AbstractModel.extend({
|
||||
end.add(-1, 'days');
|
||||
}
|
||||
|
||||
var isDateEvent = this.fields[this.mapping.date_start].type === 'date';
|
||||
// An "allDay" event without the "all_day" option is not considered
|
||||
// as a 24h day. It's just a part of the day (by default: 7h-19h).
|
||||
if (event.allDay) {
|
||||
if (!this.mapping.all_day) {
|
||||
if (!this.mapping.all_day && !isDateEvent) {
|
||||
if (event.r_start) {
|
||||
start.hours(event.r_start.hours())
|
||||
.minutes(event.r_start.minutes())
|
||||
|
||||
@@ -2332,6 +2332,67 @@ QUnit.module('Views', {
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('timzeone does not affect calendar with date field', function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
var calendar = createView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar date_start="start_date" mode="month">'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="start_date"/>'+
|
||||
'</calendar>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === "create") {
|
||||
assert.strictEqual(args.args[0].start_date, "2016-12-20 00:00:00");
|
||||
}
|
||||
if (args.method === "write") {
|
||||
assert.step(args.args[1].start_date);
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
session: {
|
||||
getTZOffset: function () {
|
||||
return 120; // 2 hours timezone
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Create event (on 20 december)
|
||||
var $cell = calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day:eq(2)');
|
||||
testUtils.triggerMouseEvent($cell, "mousedown");
|
||||
testUtils.triggerMouseEvent($cell, "mouseup");
|
||||
var $input = $('.modal-body input:first');
|
||||
$input.val("An event").trigger('input');
|
||||
$('.modal button.btn:contains(Create)').trigger('click');
|
||||
|
||||
assert.strictEqual(calendar.$('.o_field_start_date').text().trim(), "12/20/2016")
|
||||
|
||||
// Move event to another day (on 27 november)
|
||||
testUtils.dragAndDrop(
|
||||
calendar.$('.fc-event').first(),
|
||||
calendar.$('.fc-day-top').first()
|
||||
);
|
||||
assert.verifySteps(["2016-11-27 00:00:00"]);
|
||||
assert.strictEqual(calendar.$('.o_field_start_date').text().trim(), "11/27/2016")
|
||||
|
||||
// Move event to last day (on 7 january)
|
||||
testUtils.dragAndDrop(
|
||||
calendar.$('.fc-event').first(),
|
||||
calendar.$('.fc-day-top').last()
|
||||
);
|
||||
assert.verifySteps(["2016-11-27 00:00:00", "2017-01-07 00:00:00"]);
|
||||
assert.strictEqual(calendar.$('.o_field_start_date').text().trim(), "01/07/2017")
|
||||
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('form_view_id attribute works (for creating events)', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
|
||||
@@ -22,12 +22,8 @@ class WebsiteForum(http.Controller):
|
||||
_user_per_page = 30
|
||||
|
||||
def _get_notifications(self):
|
||||
badge_subtype = request.env.ref('gamification.mt_badge_granted')
|
||||
if badge_subtype:
|
||||
msg = request.env['mail.message'].search([('subtype_id', '=', badge_subtype.id), ('needaction', '=', True)])
|
||||
else:
|
||||
msg = list()
|
||||
return msg
|
||||
"""Deprecated"""
|
||||
return list()
|
||||
|
||||
def _prepare_forum_values(self, forum=None, **kwargs):
|
||||
values = {
|
||||
|
||||
@@ -45,7 +45,7 @@ class Job(models.Model):
|
||||
default_description = self.env["ir.model.data"].xmlid_to_object("website_hr_recruitment.default_website_description")
|
||||
return (default_description.render() if default_description else "")
|
||||
|
||||
website_description = fields.Html('Website description', translate=html_translate, sanitize_attributes=False, default=_get_default_website_description)
|
||||
website_description = fields.Html('Website description', translate=html_translate, sanitize_attributes=False, default=_get_default_website_description, prefetch=False)
|
||||
|
||||
@api.multi
|
||||
def _compute_website_url(self):
|
||||
|
||||
@@ -479,6 +479,10 @@ class FloatTimeConverter(models.AbstractModel):
|
||||
def value_to_html(self, value, options):
|
||||
sign = math.copysign(1.0, value)
|
||||
hours, minutes = divmod(abs(value) * 60, 60)
|
||||
minutes = round(minutes)
|
||||
if minutes == 60:
|
||||
minutes = 0
|
||||
hours += 1
|
||||
return '%02d:%02d' % (sign * hours, minutes)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp.tests.common import TransactionCase
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class One2manyCase(TransactionCase):
|
||||
@@ -51,6 +51,8 @@ class One2manyCase(TransactionCase):
|
||||
self.multi.lines = self.multi.lines[:-1]
|
||||
self.assertEqual(len(self.multi.lines), 9)
|
||||
self.assertIn("hello", self.multi.lines.mapped('name'))
|
||||
if not self.multi.id:
|
||||
return
|
||||
# Invalidate the cache and check again; this crashes if the value
|
||||
# of self.multi.lines in cache contains new records
|
||||
self.multi.invalidate_cache()
|
||||
@@ -89,11 +91,26 @@ class One2manyCase(TransactionCase):
|
||||
self.multi.lines = [(0, 0, {"name": str(name)})]
|
||||
self.operations()
|
||||
|
||||
def test_rpcstyle_one_by_one_on_new(self):
|
||||
self.multi = self.env["test_new_api.multi"].new({
|
||||
"name": "What is up?"
|
||||
})
|
||||
for name in range(10):
|
||||
self.multi.lines = [(0, 0, {"name": str(name)})]
|
||||
self.operations()
|
||||
|
||||
def test_rpcstyle_single(self):
|
||||
"""Check lines created with RPC style and added in one step"""
|
||||
self.multi.lines = [(0, 0, {'name': str(name)}) for name in range(10)]
|
||||
self.operations()
|
||||
|
||||
def test_rpcstyle_single_on_new(self):
|
||||
self.multi = self.env["test_new_api.multi"].new({
|
||||
"name": "What is up?"
|
||||
})
|
||||
self.multi.lines = [(0, 0, {'name': str(name)}) for name in range(10)]
|
||||
self.operations()
|
||||
|
||||
def test_many2one_integer(self):
|
||||
"""Test several models one2many with same inverse Integer field"""
|
||||
# utility function to convert records to tuples with id,name
|
||||
|
||||
+2
-2
@@ -2235,8 +2235,8 @@ class _RelationalMulti(_Relational):
|
||||
elif isinstance(value, (list, tuple)):
|
||||
# value is a list/tuple of commands, dicts or record ids
|
||||
comodel = record.env[self.comodel_name]
|
||||
# determine the value ids; by convention empty on new records
|
||||
ids = OrderedSet(record[self.name].ids if record.id else ())
|
||||
# determine the value ids
|
||||
ids = OrderedSet(record[self.name]._ids)
|
||||
# modify ids with the commands
|
||||
for command in value:
|
||||
if isinstance(command, (tuple, list)):
|
||||
|
||||
Reference in New Issue
Block a user