[MERGE] forward port branch 11.0 up to e8f630e44d

This commit is contained in:
Christophe Simonis
2018-03-21 19:36:58 +01:00
31 changed files with 283 additions and 70 deletions
+5
View File
@@ -1212,6 +1212,11 @@ class AccountMoveLine(models.Model):
account_move_line.payment_id.write({'invoice_ids': [(3, invoice.id, None)]}) account_move_line.payment_id.write({'invoice_ids': [(3, invoice.id, None)]})
rec_move_ids += account_move_line.matched_debit_ids rec_move_ids += account_move_line.matched_debit_ids
rec_move_ids += account_move_line.matched_credit_ids rec_move_ids += account_move_line.matched_credit_ids
if self.env.context.get('invoice_id'):
current_invoice = self.env['account.invoice'].browse(self.env.context['invoice_id'])
rec_move_ids = rec_move_ids.filtered(
lambda r: (r.debit_move_id + r.credit_move_id) & current_invoice.move_id.line_ids
)
return rec_move_ids.unlink() return rec_move_ids.unlink()
#################################################### ####################################################
@@ -633,7 +633,7 @@ var StatementModel = BasicModel.extend({
handles = [handle]; handles = [handle];
} else { } else {
_.each(this.lines, function (line, handle) { _.each(this.lines, function (line, handle) {
if (!line.reconciled && !line.balance.amount && line.reconciliation_proposition.length) { if (!line.reconciled && line.balance && !line.balance.amount && line.reconciliation_proposition.length) {
handles.push(handle); handles.push(handle);
} }
}); });
@@ -625,6 +625,42 @@ class TestReconciliation(AccountingTestCase):
full_rec_payable = full_rec_move.line_ids.filtered(lambda l: l.account_id == self.account_rsa) full_rec_payable = full_rec_move.line_ids.filtered(lambda l: l.account_id == self.account_rsa)
self.assertEqual(full_rec_payable.balance, 18.75) self.assertEqual(full_rec_payable.balance, 18.75)
def test_unreconcile(self):
# Use case:
# 2 invoices paid with a single payment. Unreconcile the payment with one invoice, the
# other invoice should remain reconciled.
inv1 = self.create_invoice(invoice_amount=10, currency_id=self.currency_usd_id)
inv2 = self.create_invoice(invoice_amount=20, currency_id=self.currency_usd_id)
payment = self.env['account.payment'].create({
'payment_type': 'inbound',
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
'partner_type': 'customer',
'partner_id': self.partner_agrolait_id,
'amount': 100,
'currency_id': self.currency_usd_id,
'journal_id': self.bank_journal_usd.id,
})
payment.post()
credit_aml = payment.move_line_ids.filtered('credit')
# Check residual before assignation
self.assertAlmostEquals(inv1.residual, 10)
self.assertAlmostEquals(inv2.residual, 20)
# Assign credit and residual
inv1.assign_outstanding_credit(credit_aml.id)
inv2.assign_outstanding_credit(credit_aml.id)
self.assertAlmostEquals(inv1.residual, 0)
self.assertAlmostEquals(inv2.residual, 0)
# Unreconcile one invoice at a time and check residual
credit_aml.with_context(invoice_id=inv1.id).remove_move_reconcile()
self.assertAlmostEquals(inv1.residual, 10)
self.assertAlmostEquals(inv2.residual, 0)
credit_aml.with_context(invoice_id=inv2.id).remove_move_reconcile()
self.assertAlmostEquals(inv1.residual, 10)
self.assertAlmostEquals(inv2.residual, 20)
def test_partial_reconcile_currencies_02(self): def test_partial_reconcile_currencies_02(self):
#### ####
# Day 1: Invoice Cust/001 to customer (expressed in USD) # Day 1: Invoice Cust/001 to customer (expressed in USD)
+7
View File
@@ -175,3 +175,10 @@ class PaymentTransaction(models.Model):
}) })
return tx return tx
def _post_process_after_done(self, **kwargs):
# set invoice id in payment transaction when payment being done from sale order
res = super(PaymentTransaction, self)._post_process_after_done()
if kwargs.get('invoice_id'):
self.account_invoice_id = kwargs['invoice_id']
return res
+108 -2
View File
@@ -28,6 +28,12 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
// Keys from a barcode scanner are usually processed as quick as possible, // Keys from a barcode scanner are usually processed as quick as possible,
// but some scanners can use an intercharacter delay (we support <= 50 ms) // but some scanners can use an intercharacter delay (we support <= 50 ms)
max_time_between_keys_in_ms: session.max_time_between_keys_in_ms || 55, max_time_between_keys_in_ms: session.max_time_between_keys_in_ms || 55,
// To be able to receive the barcode value, an input must be focused.
// On mobile devices, this causes the virtual keyboard to open.
// Unfortunately it is not possible to avoid this behavior...
// To avoid keyboard flickering at each detection of a barcode value,
// we want to keep it open for a while (800 ms).
inputTimeOut: 800,
init: function() { init: function() {
mixins.PropertiesMixin.init.call(this); mixins.PropertiesMixin.init.call(this);
@@ -38,6 +44,30 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
// Bind event handler once the DOM is loaded // Bind event handler once the DOM is loaded
// TODO: find a way to be active only when there are listeners on the bus // TODO: find a way to be active only when there are listeners on the bus
$(_.bind(this.start, this, false)); $(_.bind(this.start, this, false));
// Mobile device detection
var isMobile = navigator.userAgent.match(/Android/i) ||
navigator.userAgent.match(/webOS/i) ||
navigator.userAgent.match(/iPhone/i) ||
navigator.userAgent.match(/iPad/i) ||
navigator.userAgent.match(/iPod/i) ||
navigator.userAgent.match(/BlackBerry/i) ||
navigator.userAgent.match(/Windows Phone/i);
this.isChromeMobile = isMobile && window.chrome;
// Creates an input who will receive the barcode scanner value.
if (this.isChromeMobile) {
this.$barcodeInput = $('<input/>', {
name: 'barcode',
type: 'text',
css: {
'position': 'absolute',
'opacity': 0,
},
});
}
this.__removeBarcodeField = _.debounce(this._removeBarcodeField, this.inputTimeOut);
}, },
handle_buffered_keys: function() { handle_buffered_keys: function() {
@@ -109,7 +139,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "ArrowDown" ||
e.key === "Escape" || e.key === "Tab" || e.key === "Escape" || e.key === "Tab" ||
e.key === "Backspace" || e.key === "Delete" || e.key === "Backspace" || e.key === "Delete" ||
/F\d\d?/.test(e.key)) { e.key === "Unidentified" || /F\d\d?/.test(e.key)) {
return true; return true;
} else { } else {
return false; return false;
@@ -167,8 +197,84 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
} }
}, },
/**
* Try to detect the barcode value by listening all keydown events:
* Checks if a dom element who may contains text value has the focus.
* If not, it's probably because these events are triggered by a barcode scanner.
* To be able to handle this value, a focused input will be created.
*
* This function also has the responsibility to detect the end of the barcode value.
* (1) In most of cases, an optional key (tab or enter) is sent to mark the end of the value.
* So, we direclty handle the value.
* (2) If no end key is configured, we have to calculate the delay between each keydowns.
* 'max_time_between_keys_in_ms' depends of the device and may be configured.
* Exceeded this timeout, we consider that the barcode value is entirely sent.
*
* @private
* @param {jQuery.Event} e keydown event
*/
_listenBarcodeScanner: function (e) {
if (!$('input:text:focus, textarea:focus, [contenteditable]:focus').length) {
$('body').append(this.$barcodeInput);
this.$barcodeInput.focus();
}
if (this.$barcodeInput.is(":focus")) {
// Handle buffered keys immediately if the keypress marks the end
// of a barcode or after x milliseconds without a new keypress.
clearTimeout(this.timeout);
// On chrome mobile, e.which only works for some special characters like ENTER or TAB.
if (String.fromCharCode(e.which).match(this.suffix)) {
this._handleBarcodeValue(e);
} else {
this.timeout = setTimeout(this._handleBarcodeValue.bind(this, e),
this.max_time_between_keys_in_ms);
}
// if the barcode input doesn't receive keydown for a while, remove it.
this.__removeBarcodeField();
}
},
/**
* Retrieves the barcode value from the temporary input element.
* This checks this value and trigger it on the bus.
*
* @private
* @param {jQuery.Event} keydown event
*/
_handleBarcodeValue: function (e) {
var barcodeValue = this.$barcodeInput.val();
if (barcodeValue.match(this.regexp)) {
core.bus.trigger('barcode_scanned', barcodeValue, $(e.target).parent()[0]);
this.$barcodeInput.val('');
}
},
/**
* Remove the temporary input created to store the barcode value.
* If nothing happens, this input will be removed, so the focus will be lost
* and the virtual keyboard on mobile devices will be closed.
*
* @private
*/
_removeBarcodeField: function () {
if (this.$barcodeInput) {
// Reset the value and remove from the DOM.
this.$barcodeInput.val('').remove();
}
},
start: function(prevent_key_repeat){ start: function(prevent_key_repeat){
$('body').bind("keypress", this.__handler); // Chrome Mobile isn't triggering keypress event.
// This is marked as Legacy in the DOM-Level-3 Standard.
// See: https://www.w3.org/TR/uievents/#legacy-keyboardevent-event-types
// This fix is only applied for Google Chrome Mobile but it should work for
// all other cases.
// In master, we could remove the behavior with keypress and only use keydown.
if (this.isChromeMobile) {
$('body').on("keydown", this._listenBarcodeScanner.bind(this));
} else {
$('body').bind("keypress", this.__handler);
}
if (prevent_key_repeat === true) { if (prevent_key_repeat === true) {
$('body').bind("keydown", this.__keydown_handler); $('body').bind("keydown", this.__keydown_handler);
$('body').bind('keyup', this.__keyup_handler); $('body').bind('keyup', this.__keyup_handler);
+3 -1
View File
@@ -622,9 +622,11 @@ class Import(models.TransientModel):
# versions, for both data and pattern # versions, for both data and pattern
user_format = pycompat.to_native(options.get('%s_format' % field['type'])) user_format = pycompat.to_native(options.get('%s_format' % field['type']))
for num, line in enumerate(data): for num, line in enumerate(data):
if line[index]:
line[index] = line[index].strip()
if line[index]: if line[index]:
try: try:
line[index] = dt.strftime(dt.strptime(pycompat.to_native(line[index].strip()), user_format), server_format) line[index] = dt.strftime(dt.strptime(pycompat.to_native(line[index]), user_format), server_format)
except ValueError as e: except ValueError as e:
raise ValueError(_("Column %s contains incorrect values. Error in line %d: %s") % (name, num + 1, e)) raise ValueError(_("Column %s contains incorrect values. Error in line %d: %s") % (name, num + 1, e))
except Exception as e: except Exception as e:
+1 -1
View File
@@ -14,7 +14,7 @@ class MailAlias(models.AbstractModel):
_inherit = 'mail.alias.mixin' _inherit = 'mail.alias.mixin'
def _alias_check_contact_on_record(self, record, message, message_dict, alias): def _alias_check_contact_on_record(self, record, message, message_dict, alias):
if alias.alias_contact == 'employees' and record.ids: if alias.alias_contact == 'employees':
email_from = tools.decode_message_header(message, 'From') email_from = tools.decode_message_header(message, 'From')
email_address = tools.email_split(email_from)[0] email_address = tools.email_split(email_from)[0]
employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1) employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1)
+24 -24
View File
@@ -604,7 +604,7 @@ msgstr "Bankverbindlichkeiten"
#: model:account.account,name:l10n_ch.1_ch_coa_2160 #: model:account.account,name:l10n_ch.1_ch_coa_2160
#: model:account.account.template,name:l10n_ch.ch_coa_2160 #: model:account.account.template,name:l10n_ch.ch_coa_2160
msgid "Dettes envers l'actionnaire" msgid "Dettes envers l'actionnaire"
msgstr "" msgstr "Gesellschafterverbindlichkeiten"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_3806 #: model:account.account,name:l10n_ch.1_ch_coa_3806
@@ -612,7 +612,7 @@ msgstr ""
#: model:account.account.template,name:l10n_ch.ch_coa_3806 #: model:account.account.template,name:l10n_ch.ch_coa_3806
#: model:account.account.template,name:l10n_ch.ch_coa_4906 #: model:account.account.template,name:l10n_ch.ch_coa_4906
msgid "Différences de change" msgid "Différences de change"
msgstr "" msgstr "Währungsdifferenzen"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_2261 #: model:account.account,name:l10n_ch.1_ch_coa_2261
@@ -624,7 +624,7 @@ msgstr "Beschlossene Ausschüttungen"
#: model:account.account,name:l10n_ch.1_ch_coa_4071 #: model:account.account,name:l10n_ch.1_ch_coa_4071
#: model:account.account.template,name:l10n_ch.ch_coa_4071 #: model:account.account.template,name:l10n_ch.ch_coa_4071
msgid "Droits de douanes à l'importation" msgid "Droits de douanes à l'importation"
msgstr "" msgstr "Einfuhrzölle"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_1109 #: model:account.account,name:l10n_ch.1_ch_coa_1109
@@ -648,7 +648,7 @@ msgstr "Abrechnungskonto MWST"
#: model:account.account,name:l10n_ch.1_ch_coa_4009 #: model:account.account,name:l10n_ch.1_ch_coa_4009
#: model:account.account.template,name:l10n_ch.ch_coa_4009 #: model:account.account.template,name:l10n_ch.ch_coa_4009
msgid "Déductions obtenues sur achats" msgid "Déductions obtenues sur achats"
msgstr "" msgstr "Einkaufsrabatte"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_3009 #: model:account.account,name:l10n_ch.1_ch_coa_3009
@@ -714,7 +714,7 @@ msgstr "Unterhalt, Reparaturen, Ersatz mobile Sachanlagen"
#: model:account.account,name:l10n_ch.1_ch_coa_1570 #: model:account.account,name:l10n_ch.1_ch_coa_1570
#: model:account.account.template,name:l10n_ch.ch_coa_1570 #: model:account.account.template,name:l10n_ch.ch_coa_1570
msgid "Equipements et Installations" msgid "Equipements et Installations"
msgstr "" msgstr "Feste Einrichtungen und Installationen"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_3800 #: model:account.account,name:l10n_ch.1_ch_coa_3800
@@ -722,7 +722,7 @@ msgstr ""
#: model:account.account.template,name:l10n_ch.ch_coa_3800 #: model:account.account.template,name:l10n_ch.ch_coa_3800
#: model:account.account.template,name:l10n_ch.ch_coa_4900 #: model:account.account.template,name:l10n_ch.ch_coa_4900
msgid "Escomptes" msgid "Escomptes"
msgstr "" msgstr "Erlösminderungen"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_4530 #: model:account.account,name:l10n_ch.1_ch_coa_4530
@@ -734,31 +734,31 @@ msgstr "Benzin"
#: model:account.account,name:l10n_ch.1_ch_coa_3804 #: model:account.account,name:l10n_ch.1_ch_coa_3804
#: model:account.account.template,name:l10n_ch.ch_coa_3804 #: model:account.account.template,name:l10n_ch.ch_coa_3804
msgid "Frais d'encaissement" msgid "Frais d'encaissement"
msgstr "Frais d'encaissement" msgstr "Inkassoaufwand"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_3807 #: model:account.account,name:l10n_ch.1_ch_coa_3807
#: model:account.account.template,name:l10n_ch.ch_coa_3807 #: model:account.account.template,name:l10n_ch.ch_coa_3807
msgid "Frais d'expédition" msgid "Frais d'expédition"
msgstr "Frais d'expédition" msgstr "Versandkosten"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_4072 #: model:account.account,name:l10n_ch.1_ch_coa_4072
#: model:account.account.template,name:l10n_ch.ch_coa_4072 #: model:account.account.template,name:l10n_ch.ch_coa_4072
msgid "Frais de transport à l'achat" msgid "Frais de transport à l'achat"
msgstr "Frais de transport à l'achat" msgstr "Transportkosten"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_4070 #: model:account.account,name:l10n_ch.1_ch_coa_4070
#: model:account.account.template,name:l10n_ch.ch_coa_4070 #: model:account.account.template,name:l10n_ch.ch_coa_4070
msgid "Frêts à l'achat" msgid "Frêts à l'achat"
msgstr "" msgstr "Frachtkosten"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_4510 #: model:account.account,name:l10n_ch.1_ch_coa_4510
#: model:account.account.template,name:l10n_ch.ch_coa_4510 #: model:account.account.template,name:l10n_ch.ch_coa_4510
msgid "Gaz" msgid "Gaz"
msgstr "Gaz" msgstr "Gas"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_1770 #: model:account.account,name:l10n_ch.1_ch_coa_1770
@@ -992,7 +992,7 @@ msgstr "Beteiligungen"
#: model:account.account,name:l10n_ch.1_ch_coa_4086 #: model:account.account,name:l10n_ch.1_ch_coa_4086
#: model:account.account.template,name:l10n_ch.ch_coa_4086 #: model:account.account.template,name:l10n_ch.ch_coa_4086
msgid "Pertes de matières" msgid "Pertes de matières"
msgstr "" msgstr "Warenschwund"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_3805 #: model:account.account,name:l10n_ch.1_ch_coa_3805
@@ -1125,13 +1125,13 @@ msgstr "Werbeaufwand"
#: model:account.account,name:l10n_ch.1_ch_coa_3801 #: model:account.account,name:l10n_ch.1_ch_coa_3801
#: model:account.account.template,name:l10n_ch.ch_coa_3801 #: model:account.account.template,name:l10n_ch.ch_coa_3801
msgid "Rabais et réduction de prix" msgid "Rabais et réduction de prix"
msgstr "" msgstr "Rabatte und Preisreduktionen"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_4901 #: model:account.account,name:l10n_ch.1_ch_coa_4901
#: model:account.account.template,name:l10n_ch.ch_coa_4901 #: model:account.account.template,name:l10n_ch.ch_coa_4901
msgid "Rabais et réductions de prix" msgid "Rabais et réductions de prix"
msgstr "" msgstr "Rabatte und Preisreduktionen"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_3802 #: model:account.account,name:l10n_ch.1_ch_coa_3802
@@ -1139,7 +1139,7 @@ msgstr ""
#: model:account.account.template,name:l10n_ch.ch_coa_3802 #: model:account.account.template,name:l10n_ch.ch_coa_3802
#: model:account.account.template,name:l10n_ch.ch_coa_4092 #: model:account.account.template,name:l10n_ch.ch_coa_4092
msgid "Ristournes" msgid "Ristournes"
msgstr "" msgstr "Rückvergütungen"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_2940 #: model:account.account,name:l10n_ch.1_ch_coa_2940
@@ -1473,13 +1473,13 @@ msgstr "Wertschriften"
#: model:account.account,name:l10n_ch.1_transfer_account_id #: model:account.account,name:l10n_ch.1_transfer_account_id
#: model:account.account.template,name:l10n_ch.transfer_account_id #: model:account.account.template,name:l10n_ch.transfer_account_id
msgid "Transferts internes" msgid "Transferts internes"
msgstr "" msgstr "Interne Verrechnungen"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_1280 #: model:account.account,name:l10n_ch.1_ch_coa_1280
#: model:account.account.template,name:l10n_ch.ch_coa_1280 #: model:account.account.template,name:l10n_ch.ch_coa_1280
msgid "Travaux en cours" msgid "Travaux en cours"
msgstr "Unfertige Erzeugnisse" msgstr "Nicht fakturierte Dienstleistungen"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_3940 #: model:account.account,name:l10n_ch.1_ch_coa_3940
@@ -1491,19 +1491,19 @@ msgstr "Bestandesänderungen nicht fakturierte Dienstleistungen"
#: model:account.account,name:l10n_ch.1_ch_coa_1287 #: model:account.account,name:l10n_ch.1_ch_coa_1287
#: model:account.account.template,name:l10n_ch.ch_coa_1287 #: model:account.account.template,name:l10n_ch.ch_coa_1287
msgid "Variation de la valeur des travaux en cours" msgid "Variation de la valeur des travaux en cours"
msgstr "" msgstr "Bestandesänderungen nicht fakturierte Dienstleistungen"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_1277 #: model:account.account,name:l10n_ch.1_ch_coa_1277
#: model:account.account.template,name:l10n_ch.ch_coa_1277 #: model:account.account.template,name:l10n_ch.ch_coa_1277
msgid "Variation de stock produits semi-ouvrés" msgid "Variation de stock produits semi-ouvrés"
msgstr "" msgstr "Bestandesänderungen unfertige Erzeugnisse"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_1267 #: model:account.account,name:l10n_ch.1_ch_coa_1267
#: model:account.account.template,name:l10n_ch.ch_coa_1267 #: model:account.account.template,name:l10n_ch.ch_coa_1267
msgid "Variation de stocks de produits finis" msgid "Variation de stocks de produits finis"
msgstr "" msgstr "Bestandesänderungen fertige Erzeugnisse"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_1207 #: model:account.account,name:l10n_ch.1_ch_coa_1207
@@ -1511,13 +1511,13 @@ msgstr ""
#: model:account.account.template,name:l10n_ch.ch_coa_1207 #: model:account.account.template,name:l10n_ch.ch_coa_1207
#: model:account.account.template,name:l10n_ch.ch_coa_4800 #: model:account.account.template,name:l10n_ch.ch_coa_4800
msgid "Variation des stocks de marchandises" msgid "Variation des stocks de marchandises"
msgstr "" msgstr "Bestandesänderungen Handelsware"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_4801 #: model:account.account,name:l10n_ch.1_ch_coa_4801
#: model:account.account.template,name:l10n_ch.ch_coa_4801 #: model:account.account.template,name:l10n_ch.ch_coa_4801
msgid "Variation des stocks de matières premières" msgid "Variation des stocks de matières premières"
msgstr "" msgstr "Bestandesänderungen Rohstoffe"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_3901 #: model:account.account,name:l10n_ch.1_ch_coa_3901
@@ -1535,7 +1535,7 @@ msgstr "Bestandesänderungen unfertige Erzeugnisse"
#: model:account.account,name:l10n_ch.1_ch_coa_1217 #: model:account.account,name:l10n_ch.1_ch_coa_1217
#: model:account.account.template,name:l10n_ch.ch_coa_1217 #: model:account.account.template,name:l10n_ch.ch_coa_1217
msgid "Variation des stocks des matières premières" msgid "Variation des stocks des matières premières"
msgstr "" msgstr "Bestandesänderungen Rohstoffe"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_4008 #: model:account.account,name:l10n_ch.1_ch_coa_4008
@@ -1543,7 +1543,7 @@ msgstr ""
#: model:account.account.template,name:l10n_ch.ch_coa_4008 #: model:account.account.template,name:l10n_ch.ch_coa_4008
#: model:account.account.template,name:l10n_ch.ch_coa_4080 #: model:account.account.template,name:l10n_ch.ch_coa_4080
msgid "Variations de stocks" msgid "Variations de stocks"
msgstr "" msgstr "Inventurdifferenzen"
#. module: l10n_ch #. module: l10n_ch
#: model:account.account,name:l10n_ch.1_ch_coa_3200 #: model:account.account,name:l10n_ch.1_ch_coa_3200
+11 -5
View File
@@ -19,12 +19,18 @@ body.l10n_ch_isr {
} }
} }
} }
}
/* content outside isr needs margins to not overlap header */ /* content outside isr needs margins to not overlap header */
body.l10n_ch_isr #content_outside_isr { #content_outside_isr {
padding: 15px; padding: 15px;
padding-top: 150px; padding-top: 150px;
}
/* ISR is intended for pre-printed paper, we don't want stylistic background */
.o_report_layout_background {
background: none;
min-height: 0;
}
} }
body.l10n_ch_isr #isr { body.l10n_ch_isr #isr {
-4
View File
@@ -144,10 +144,6 @@ msgid ""
"object.parent_id.subject) or (object.parent_id and " "object.parent_id.subject) or (object.parent_id and "
"object.parent_id.record_name and 'Re: %s' % object.parent_id.record_name)}" "object.parent_id.record_name and 'Re: %s' % object.parent_id.record_name)}"
msgstr "" msgstr ""
"${object.subject 或 (object.record_name 和 'Re: %s' % object.record_name) 或 "
"(object.parent_id and object.parent_id.subject 和 'Re: %s' % "
"object.parent_id.subject) 或 (object.parent_id 和 object.parent_id.record_name"
" 和 'Re: %s' % object.parent_id.record_name)}"
#. module: mail #. module: mail
#. openerp-web #. openerp-web
+7 -10
View File
@@ -508,22 +508,19 @@
<filter string="My Equipments" name="my" domain="[('owner_user_id', '=', uid)]"/> <filter string="My Equipments" name="my" domain="[('owner_user_id', '=', uid)]"/>
<filter string="Archived" domain="[('active', '=', False)]" name="inactive"/> <filter string="Archived" domain="[('active', '=', False)]" name="inactive"/>
<separator/> <separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<separator/>
<filter string="My Activities" name="activities_my" <filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/> domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today" <separator/>
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/> <filter string="Late Activities" name="activities_overdue"
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]" domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/> help="Show all records which has next action date is before today"/>
<filter string="Upcoming Activities (All)" name="activities_upcoming_all" <filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Future Activities" name="activities_upcoming_all"
domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d')) domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))
]"/> ]"/>
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<!-- <filter string="Archived" name="inactive" domain="[('active','=',False)]"/> --> <!-- <filter string="Archived" name="inactive" domain="[('active','=',False)]"/> -->
<group expand='0' string='Group by...'> <group expand='0' string='Group by...'>
<filter string='Technicians' name="technicians" domain="[]" context="{'group_by': 'technician_user_id'}"/> <filter string='Technicians' name="technicians" domain="[]" context="{'group_by': 'technician_user_id'}"/>
@@ -794,6 +794,8 @@ class MassMailing(models.Model):
def _process_mass_mailing_queue(self): def _process_mass_mailing_queue(self):
mass_mailings = self.search([('state', 'in', ('in_queue', 'sending')), '|', ('schedule_date', '<', fields.Datetime.now()), ('schedule_date', '=', False)]) mass_mailings = self.search([('state', 'in', ('in_queue', 'sending')), '|', ('schedule_date', '<', fields.Datetime.now()), ('schedule_date', '=', False)])
for mass_mailing in mass_mailings: for mass_mailing in mass_mailings:
user = mass_mailing.write_uid or self.env.user
mass_mailing = mass_mailing.with_context(**user.sudo(user=user).context_get())
if len(mass_mailing.get_remaining_recipients()) > 0: if len(mass_mailing.get_remaining_recipients()) > 0:
mass_mailing.state = 'sending' mass_mailing.state = 'sending'
mass_mailing.send_mail() mass_mailing.send_mail()
@@ -686,6 +686,10 @@ class PaymentTransaction(models.Model):
return True return True
@api.multi
def _post_process_after_done(self, **kwargs):
return True
# -------------------------------------------------- # --------------------------------------------------
# SERVER2SERVER RELATED METHODS # SERVER2SERVER RELATED METHODS
# -------------------------------------------------- # --------------------------------------------------
+1 -1
View File
@@ -281,7 +281,7 @@
<select name="country_id" class="form-control"> <select name="country_id" class="form-control">
<option value="">Country...</option> <option value="">Country...</option>
<t t-foreach="countries or []" t-as="country"> <t t-foreach="countries or []" t-as="country">
<option t-att-value="country.id" t-att-selected="country.id == partner.country_id.id"> <option t-att-value="country.id" t-att-selected="country.id == int(country_id) if country_id else country.id == partner.country_id.id">
<t t-esc="country.name" /> <t t-esc="country.name" />
</option> </option>
</t> </t>
+1 -1
View File
@@ -356,7 +356,7 @@ class PricelistItem(models.Model):
'product.category', 'Product Category', ondelete='cascade', 'product.category', 'Product Category', ondelete='cascade',
help="Specify a product category if this rule only applies to products belonging to this category or its children categories. Keep empty otherwise.") help="Specify a product category if this rule only applies to products belonging to this category or its children categories. Keep empty otherwise.")
min_quantity = fields.Integer( min_quantity = fields.Integer(
'Min. Quantity', default=1, 'Min. Quantity', default=0,
help="For the rule to apply, bought/sold quantity must be greater " help="For the rule to apply, bought/sold quantity must be greater "
"than or equal to the minimum quantity specified in this field.\n" "than or equal to the minimum quantity specified in this field.\n"
"Expressed in the default unit of measure of the product.") "Expressed in the default unit of measure of the product.")
+5 -9
View File
@@ -588,20 +588,16 @@ class SaleOrder(models.Model):
self.ensure_one() self.ensure_one()
res = {} res = {}
for line in self.order_line: for line in self.order_line:
base_tax = 0 price_reduce = line.price_unit * (1.0 - line.discount / 100.0)
taxes = line.tax_id.compute_all(price_reduce, quantity=line.product_uom_qty, product=line.product_id, partner=self.partner_shipping_id)['taxes']
for tax in line.tax_id: for tax in line.tax_id:
group = tax.tax_group_id group = tax.tax_group_id
res.setdefault(group, {'amount': 0.0, 'base': 0.0}) res.setdefault(group, {'amount': 0.0, 'base': 0.0})
# FORWARD-PORT UP TO SAAS-17 # FORWARD-PORT UP TO SAAS-17
price_reduce = line.price_unit * (1.0 - line.discount / 100.0)
taxes = tax.compute_all(price_reduce + base_tax, quantity=line.product_uom_qty,
product=line.product_id, partner=self.partner_shipping_id)['taxes']
for t in taxes: for t in taxes:
res[group]['amount'] += t['amount'] if t['id'] == tax.id:
res[group]['base'] += t['base'] res[group]['amount'] += t['amount']
if tax.include_base_amount: res[group]['base'] += t['base']
base_tax += tax.compute_all(price_reduce + base_tax, quantity=1, product=line.product_id,
partner=self.partner_shipping_id)['taxes'][0]['amount']
res = sorted(res.items(), key=lambda l: l[0].sequence) res = sorted(res.items(), key=lambda l: l[0].sequence)
res = [(l[0].name, l[1]['amount'], l[1]['base'], len(res)) for l in res] res = [(l[0].name, l[1]['amount'], l[1]['base'], len(res)) for l in res]
return res return res
@@ -169,9 +169,6 @@
<field name="portal_confirmation_options" class="o_light_label" widget="radio" <field name="portal_confirmation_options" class="o_light_label" widget="radio"
attrs="{'required': [('portal_confirmation', '=', True)]}"/> attrs="{'required': [('portal_confirmation', '=', True)]}"/>
</div> </div>
<div attrs="{'invisible': [('portal_confirmation_options', '!=', 'pay')]}">
<button name='%(payment.action_payment_acquirer)d' icon="fa-arrow-right" type="action" string="Payment Acquirers" class="btn-link"/>
</div>
</div> </div>
</div> </div>
</div> </div>
+1
View File
@@ -20,6 +20,7 @@ Provide tools for sale-related payment
'views/crm_team_views.xml', 'views/crm_team_views.xml',
'views/sale_order_views.xml', 'views/sale_order_views.xml',
'views/sale_portal_templates.xml', 'views/sale_portal_templates.xml',
'views/settings.xml',
], ],
'installable': True, 'installable': True,
'auto_install': False, 'auto_install': False,
+1
View File
@@ -103,6 +103,7 @@ class PaymentTransaction(models.Model):
created_invoice.with_context(default_currency_id=self.currency_id.id).pay_and_reconcile(self.acquirer_id.journal_id, pay_amount=created_invoice.amount_total) created_invoice.with_context(default_currency_id=self.currency_id.id).pay_and_reconcile(self.acquirer_id.journal_id, pay_amount=created_invoice.amount_total)
if created_invoice.payment_ids: if created_invoice.payment_ids:
created_invoice.payment_ids[0].payment_transaction_id = self created_invoice.payment_ids[0].payment_transaction_id = self
self._post_process_after_done(invoice_id=created_invoice)
else: else:
_logger.warning('<%s> transaction completed, could not auto-generate invoice for %s (ID %s)', _logger.warning('<%s> transaction completed, could not auto-generate invoice for %s (ID %s)',
self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.sale_payment</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="sale.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='portal_confirmation_options']/.." position="after">
<div attrs="{'invisible': [('portal_confirmation_options', '!=', 'pay')]}">
<button name='%(payment.action_payment_acquirer)d' icon="fa-arrow-right" type="action" string="Payment Acquirers" class="btn-link"/>
</div>
</xpath>
</field>
</record>
</odoo>
+14 -1
View File
@@ -56,7 +56,16 @@ class Project(models.Model):
class ProjectTask(models.Model): class ProjectTask(models.Model):
_inherit = "project.task" _inherit = "project.task"
sale_line_id = fields.Many2one('sale.order.line', 'Sales Order Item', domain="[('is_service', '=', True), ('order_partner_id', '=', partner_id), ('is_expense', '=', False)]") @api.model
def _default_sale_line_id(self):
sale_line_id = False
if self._context.get('default_parent_id'):
sale_line_id = self.env['project.task'].browse(self._context['default_parent_id']).sale_line_id.id
if not sale_line_id and self._context.get('default_project_id'):
sale_line_id = self.env['project.project'].browse(self._context['default_project_id']).sale_line_id.id
return sale_line_id
sale_line_id = fields.Many2one('sale.order.line', 'Sales Order Item', default=_default_sale_line_id, domain="[('is_service', '=', True), ('order_partner_id', '=', partner_id), ('is_expense', '=', False)]")
@api.multi @api.multi
@api.constrains('sale_line_id') @api.constrains('sale_line_id')
@@ -82,6 +91,10 @@ class ProjectTask(models.Model):
result = super(ProjectTask, self).write(values) result = super(ProjectTask, self).write(values)
# reassign SO line on related timesheet lines # reassign SO line on related timesheet lines
if 'sale_line_id' in values: if 'sale_line_id' in values:
# subtasks should have the same SO line than their mother
self.sudo().mapped('child_ids').write({
'so_line': values['sale_line_id']
})
self.sudo().mapped('timesheet_ids').write({ self.sudo().mapped('timesheet_ids').write({
'so_line': values['sale_line_id'] 'so_line': values['sale_line_id']
}) })
+1
View File
@@ -829,5 +829,6 @@ class Orderpoint(models.Model):
'date_planned': date or self._get_date_planned(product_qty, datetime.today()), 'date_planned': date or self._get_date_planned(product_qty, datetime.today()),
'warehouse_id': self.warehouse_id, 'warehouse_id': self.warehouse_id,
'orderpoint_id': self, 'orderpoint_id': self,
'company_id': self.company_id,
'group_id': group or self.group_id, 'group_id': group or self.group_id,
} }
@@ -44,6 +44,8 @@ class AccountInvoiceLine(models.Model):
def _get_anglo_saxon_price_unit(self): def _get_anglo_saxon_price_unit(self):
self.ensure_one() self.ensure_one()
if not self.product_id:
return self.price_unit
return self.product_id._get_anglo_saxon_price_unit(uom=self.uom_id) return self.product_id._get_anglo_saxon_price_unit(uom=self.uom_id)
def _get_price(self, company_currency, price_unit): def _get_price(self, company_currency, price_unit):
+1 -1
View File
@@ -46,7 +46,7 @@
<field name="name">Public access to user_input</field> <field name="name">Public access to user_input</field>
<field name="model_id" ref="survey.model_survey_user_input"/> <field name="model_id" ref="survey.model_survey_user_input"/>
<field name="domain_force">[('create_uid', '=', user.id)]</field> <field name="domain_force">[('create_uid', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_public'))]"/> <field name="groups" eval="[(4, ref('base.group_user')), (4, ref('base.group_portal')), (4, ref('base.group_public'))]"/>
<field eval="0" name="perm_unlink"/> <field eval="0" name="perm_unlink"/>
<field eval="0" name="perm_write"/> <field eval="0" name="perm_write"/>
<field eval="1" name="perm_read"/> <field eval="1" name="perm_read"/>
+1 -1
View File
@@ -993,7 +993,7 @@ class Binary(http.Controller):
'/web/content/<string:model>/<int:id>/<string:field>/<string:filename>'], type='http', auth="public") '/web/content/<string:model>/<int:id>/<string:field>/<string:filename>'], type='http', auth="public")
def content_common(self, xmlid=None, model='ir.attachment', id=None, field='datas', def content_common(self, xmlid=None, model='ir.attachment', id=None, field='datas',
filename=None, filename_field='datas_fname', unique=None, mimetype=None, filename=None, filename_field='datas_fname', unique=None, mimetype=None,
download=None, data=None, token=None, access_token=None): download=None, data=None, token=None, access_token=None, **kw):
status, headers, content = binary_content( status, headers, content = binary_content(
xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename,
filename_field=filename_field, download=download, mimetype=mimetype, filename_field=filename_field, download=download, mimetype=mimetype,
+2 -2
View File
@@ -125,8 +125,8 @@
and main_object.website_meta_description or website_meta_description"/> and main_object.website_meta_description or website_meta_description"/>
<t t-set="meta_keywords" t-value="main_object and 'website_meta_keywords' in main_object <t t-set="meta_keywords" t-value="main_object and 'website_meta_keywords' in main_object
and main_object.website_meta_keywords or website_meta_keywords"/> and main_object.website_meta_keywords or website_meta_keywords"/>
<meta t-if="meta_description" name="description" t-att-content="meta_description"/> <meta t-if="meta_description or editable" name="description" t-att-content="meta_description"/>
<meta t-if="meta_keywords" name="keywords" t-att-content="meta_keywords"/> <meta t-if="meta_keywords or editable" name="keywords" t-att-content="meta_keywords"/>
<!-- OpenGraph tags for Facebook sharing --> <!-- OpenGraph tags for Facebook sharing -->
<meta property="og:title" t-att-content="additional_title" /> <meta property="og:title" t-att-content="additional_title" />
+2 -1
View File
@@ -1301,7 +1301,8 @@
<div t-esc="int(line.product_uom_qty) == line.product_uom_qty and int(line.product_uom_qty) or line.product_uom_qty" /> <div t-esc="int(line.product_uom_qty) == line.product_uom_qty and int(line.product_uom_qty) or line.product_uom_qty" />
</td> </td>
<td class="text-center td-price"> <td class="text-center td-price">
<span t-field="line.price_unit" style="white-space: nowrap;" t-options="{'widget': 'monetary','from_currency': website_sale_order.pricelist_id.currency_id,'display_currency': website.currency_id}" /> <span t-field="line.price_reduce_taxexcl" style="white-space: nowrap;" t-options="{'widget': 'monetary', 'from_currency': website_sale_order.pricelist_id.currency_id, 'display_currency': website.currency_id}" groups="sale.group_show_price_subtotal" />
<span t-field="line.price_reduce_taxinc" style="white-space: nowrap;" t-options="{'widget': 'monetary', 'from_currency': website_sale_order.pricelist_id.currency_id, 'display_currency': website.currency_id}" groups="sale.group_show_price_total" />
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -26,7 +26,10 @@ class SaleOrder(models.Model):
@api.depends('order_line.price_unit', 'order_line.tax_id', 'order_line.discount', 'order_line.product_uom_qty') @api.depends('order_line.price_unit', 'order_line.tax_id', 'order_line.discount', 'order_line.product_uom_qty')
def _compute_amount_delivery(self): def _compute_amount_delivery(self):
for order in self: for order in self:
order.amount_delivery = sum(order.order_line.filtered('is_delivery').mapped('price_subtotal')) if self.env.user.has_group('sale.group_show_price_subtotal'):
order.amount_delivery = sum(order.order_line.filtered('is_delivery').mapped('price_subtotal'))
else:
order.amount_delivery = sum(order.order_line.filtered('is_delivery').mapped('price_total'))
@api.depends('order_line.is_delivery') @api.depends('order_line.is_delivery')
def _compute_has_delivery(self): def _compute_has_delivery(self):
+11
View File
@@ -0,0 +1,11 @@
Austria, 18th of March 2018
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,
Andreas Perhab bigbear.ap@gmx.at https://github.com/bigbear3001
+11
View File
@@ -0,0 +1,11 @@
Switzerland, 2018-03-20
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,
Andreas Hochuli a.hochuli@hispeed.ch https://github.com/Highcooley
+1 -1
View File
@@ -406,7 +406,7 @@ Source installation requires manually installing dependencies:
$ sudo npm install -g less $ sudo npm install -g less
- on Windows, `install nodejs <http://nodejs.org/download/>`_, reboot (to - on Windows, `install nodejs <https://nodejs.org/en/download/>`_, reboot (to
update the :envvar:`PATH`) and install less: update the :envvar:`PATH`) and install less:
.. code-block:: doscon .. code-block:: doscon