diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 8ac7194e3d0..f114e2e889d 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -486,7 +486,6 @@ class AccountInvoice(models.Model): addr = self.partner_id.address_get(['delivery']) fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id, delivery_id=addr['delivery']) - bank_id = p.bank_ids and p.bank_ids.ids[0] or False # If partner has no warning, check its company if p.invoice_warn == 'no-message' and p.parent_id: @@ -507,8 +506,12 @@ class AccountInvoice(models.Model): self.payment_term_id = payment_term_id self.fiscal_position_id = fiscal_position - if type in ('in_invoice', 'in_refund'): + if type in ('in_invoice', 'out_refund'): + bank_ids = p.commercial_partner_id.bank_ids + bank_id = bank_ids[0].id if bank_ids else False self.partner_bank_id = bank_id + return {'domain': {'partner_bank_id': [('id', 'in', bank_ids.ids)]}} + return {} @api.onchange('journal_id') diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 0d2569593f1..1252aa647bf 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -709,22 +709,37 @@ class AccountMoveLine(models.Model): line_currency = (line.currency_id and line.amount_currency) and line.currency_id or company_currency amount_currency_str = "" total_amount_currency_str = "" - if line_currency != company_currency and target_currency != company_currency: + if line_currency != company_currency and target_currency == line_currency: + # The payment currency is the invoice currency, but they are different than the company currency + # We use the `amount_currency` computed during the invoice validation, at the invoice date + # to avoid exchange gain/loss + # e.g. an invoice of 100€ must be paid with 100€, whatever the company currency and the exchange rates total_amount = line.amount_currency actual_debit = debit > 0 and amount_currency or 0.0 actual_credit = credit > 0 and -amount_currency or 0.0 + currency = line_currency else: + # Either: + # - the invoice, payment, company currencies are all the same, + # - the payment currency is the company currency, but the invoice currency is different, + # - the invoice currency is the company currency, but the payment currency is different, + # - the invoice, payment and company currencies are all different. + # For the two first cases, we can simply use the debit/credit of the invoice move line, which are always in the company currency, + # and this is what the target need. + # For the two last cases, we can use the debit/credit which are in the company currency, and then change them to the target currency total_amount = abs(debit - credit) actual_debit = debit > 0 and amount or 0.0 actual_credit = credit > 0 and -amount or 0.0 - if line_currency != target_currency and target_currency != company_currency: + currency = company_currency + if line_currency != target_currency: amount_currency_str = formatLang(self.env, abs(actual_debit or actual_credit), currency_obj=line_currency) total_amount_currency_str = formatLang(self.env, total_amount, currency_obj=line_currency) + if currency != target_currency: ctx = context.copy() ctx.update({'date': target_date or line.date}) - total_amount = line_currency.with_context(ctx).compute(total_amount, target_currency) - actual_debit = line_currency.with_context(ctx).compute(actual_debit, target_currency) - actual_credit = line_currency.with_context(ctx).compute(actual_credit, target_currency) + total_amount = currency.with_context(ctx).compute(total_amount, target_currency) + actual_debit = currency.with_context(ctx).compute(actual_debit, target_currency) + actual_credit = currency.with_context(ctx).compute(actual_credit, target_currency) amount_str = formatLang(self.env, abs(actual_debit or actual_credit), currency_obj=target_currency) total_amount_str = formatLang(self.env, total_amount, currency_obj=target_currency) diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index 34c7e28cac1..75c09b3ac7e 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -21,7 +21,7 @@ class account_payment_method(models.Model): _name = "account.payment.method" _description = "Payment Methods" - name = fields.Char(required=True) + name = fields.Char(required=True, translate=True) code = fields.Char(required=True) # For internal identification payment_type = fields.Selection([('inbound', 'Inbound'), ('outbound', 'Outbound')], required=True) @@ -400,7 +400,7 @@ class account_payment(models.Model): # the writeoff debit and credit must be computed from the invoice residual in company currency # minus the payment amount in company currency, and not from the payment difference in the payment currency # to avoid loss of precision during the currency rate computations. See revision 20935462a0cabeb45480ce70114ff2f4e91eaf79 for a detailed example. - total_residual_company_signed = self._compute_total_invoices_amount() + total_residual_company_signed = sum(invoice.residual_company_signed for invoice in self.invoice_ids) total_payment_company_signed = self.currency_id.with_context(date=self.payment_date).compute(self.amount, self.company_id.currency_id) if self.invoice_ids[0].type in ['in_invoice', 'out_refund']: amount_wo = total_payment_company_signed - total_residual_company_signed diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index 5f83012ad07..532777a5066 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -1106,7 +1106,6 @@ - @@ -1195,7 +1194,7 @@ - + diff --git a/addons/crm/data/sales_config_settings_data.xml b/addons/crm/data/sales_config_settings_data.xml index 9aef98293b6..be269cafec9 100644 --- a/addons/crm/data/sales_config_settings_data.xml +++ b/addons/crm/data/sales_config_settings_data.xml @@ -1,8 +1,8 @@ - + - + diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml index d90664ca1c5..944212cde3e 100644 --- a/addons/crm/views/crm_lead_views.xml +++ b/addons/crm/views/crm_lead_views.xml @@ -583,6 +583,9 @@ + diff --git a/addons/im_livechat/static/src/less/im_livechat.less b/addons/im_livechat/static/src/less/im_livechat.less index c57bfad230a..c52fc7d2b9e 100644 --- a/addons/im_livechat/static/src/less/im_livechat.less +++ b/addons/im_livechat/static/src/less/im_livechat.less @@ -16,6 +16,7 @@ border-bottom: 0px; border-top-left-radius: 5px; border-top-right-radius: 5px; + z-index: 5; } .o_chat_window { diff --git a/addons/l10n_ro/data/account_fiscal_position_data.xml b/addons/l10n_ro/data/account_fiscal_position_data.xml index 8649103fa8f..9ff61aa3f0e 100644 --- a/addons/l10n_ro/data/account_fiscal_position_data.xml +++ b/addons/l10n_ro/data/account_fiscal_position_data.xml @@ -88,6 +88,11 @@ + + + + + @@ -147,6 +152,11 @@ + + + + + @@ -207,6 +217,11 @@ + + + + + diff --git a/addons/l10n_ro/data/account_tax_data.xml b/addons/l10n_ro/data/account_tax_data.xml index 8bba8727f27..373fea4c16c 100644 --- a/addons/l10n_ro/data/account_tax_data.xml +++ b/addons/l10n_ro/data/account_tax_data.xml @@ -135,6 +135,21 @@ taxes + + TVA Taxare Inversa Colectat 19% + taxes + + + + TVA Taxare Inversa Deductibil 19% + taxes + + + + TVA Taxare Inversa 19% + taxes + + TVA Taxare Inversa Colectat 20% taxes @@ -210,6 +225,21 @@ taxes + + TVA Intracomunitara Achizitii Colectat 19% + taxes + + + + TVA Intracomunitara Achizitii Deductibil 19% + taxes + + + + TVA Intracomunitara Achizitii 19% + taxes + + TVA Intracomunitara Achizitii Colectat 20% taxes @@ -281,7 +311,7 @@ - 11 + 9 TVA colectat 19% TVA colectat 19% 19 @@ -307,7 +337,7 @@ - 9 + 11 TVA colectat 24% TVA colectat 24% 24 @@ -359,7 +389,7 @@ - 23 + 19 TVA deductibil 19% TVA deductibil 19% 19 @@ -385,7 +415,7 @@ - 19 + 23 TVA deductibil 24% TVA deductibil 24% 24 @@ -506,6 +536,38 @@ + + + TVA Taxare Inversa Colectat 19% + -19 + percent + none + + + + + + + TVA Taxare Inversa Deductibil 19% + 19 + percent + none + + + + + + + 32 + TVA Taxare Inversa 19% + TVA Taxare Inversa 19% + 1 + group + purchase + + + + TVA Taxare Inversa Colectat 20% @@ -730,6 +792,38 @@ + + + TVA Intracomunitara Achizitii Colectat 19% + -19 + percent + none + + + + + + + TVA Intracomunitara Achizitii Deductibil 19% + + 19 + percent + none + + + + + + + 62 + TVA Intracomunitara Achizitii 19% + 1 + group + purchase + + + + TVA Intracomunitara Achizitii Colectat 20% diff --git a/addons/maintenance/__manifest__.py b/addons/maintenance/__manifest__.py index b30f3bd2d73..9bdde95f507 100644 --- a/addons/maintenance/__manifest__.py +++ b/addons/maintenance/__manifest__.py @@ -15,6 +15,7 @@ 'data/maintenance_data.xml', 'views/maintenance_views.xml', 'views/maintenance_templates.xml', + 'data/maintenance_cron.xml', ], 'demo': ['data/maintenance_demo.xml'], 'installable': True, diff --git a/addons/maintenance/models/maintenance.py b/addons/maintenance/models/maintenance.py index 4d2efbdeaa0..87d6f6535f3 100644 --- a/addons/maintenance/models/maintenance.py +++ b/addons/maintenance/models/maintenance.py @@ -144,17 +144,48 @@ class MaintenanceEquipment(models.Model): maintenance_team_id = fields.Many2one('maintenance.team', string='Maintenance Team') maintenance_duration = fields.Float(help="Maintenance Duration in minutes and seconds.") - @api.depends('period', 'maintenance_open_count', 'maintenance_ids.request_date') + @api.depends('period', 'maintenance_ids.request_date', 'maintenance_ids.close_date') def _compute_next_maintenance(self): - for equipment in self: - create_date = equipment.create_date and datetime.strptime(equipment.create_date, DEFAULT_SERVER_DATETIME_FORMAT) - if equipment.period: - next_date = create_date - if equipment.maintenance_ids: - maintenance = equipment.maintenance_ids.sorted(lambda x: x.request_date)[0] - next_date = maintenance.request_date and datetime.strptime(maintenance.request_date, DEFAULT_SERVER_DATE_FORMAT) or create_date - equipment.next_action_date = next_date and (next_date + timedelta(days=equipment.period)).strftime(DEFAULT_SERVER_DATE_FORMAT) + date_now = fields.Date.context_today(self) + for equipment in self.filtered(lambda x: x.period > 0): + next_maintenance_todo = self.env['maintenance.request'].search([ + ('equipment_id', '=', equipment.id), + ('maintenance_type', '=', 'preventive'), + ('stage_id.done', '!=', True), + ('close_date', '=', False)], order="request_date asc", limit=1) + last_maintenance_done = self.env['maintenance.request'].search([ + ('equipment_id', '=', equipment.id), + ('maintenance_type', '=', 'preventive'), + ('stage_id.done', '=', True), + ('close_date', '!=', False)], order="close_date desc", limit=1) + if next_maintenance_todo and last_maintenance_done: + next_date = next_maintenance_todo.request_date + date_gap = fields.Date.from_string(next_maintenance_todo.request_date) - fields.Date.from_string(last_maintenance_done.close_date) + # If the gap between the last_maintenance_done and the next_maintenance_todo one is bigger than 2 times the period and next request is in the future + # We use 2 times the period to avoid creation too closed request from a manually one created + if date_gap > timedelta(0) and date_gap > timedelta(days=equipment.period) * 2 and fields.Date.from_string(next_maintenance_todo.request_date) > fields.Date.from_string(date_now): + # If the new date still in the past, we set it for today + if fields.Date.from_string(last_maintenance_done.close_date) + timedelta(days=equipment.period) < fields.Date.from_string(date_now): + next_date = date_now + else: + next_date = fields.Date.to_string(fields.Date.from_string(last_maintenance_done.close_date) + timedelta(days=equipment.period)) + elif next_maintenance_todo: + next_date = next_maintenance_todo.request_date + date_gap = fields.Date.from_string(next_maintenance_todo.request_date) - fields.Date.from_string(date_now) + # If next maintenance to do is in the future, and in more than 2 times the period, we insert an new request + # We use 2 times the period to avoid creation too closed request from a manually one created + if date_gap > timedelta(0) and date_gap > timedelta(days=equipment.period) * 2: + next_date = fields.Date.to_string(fields.Date.from_string(date_now)+timedelta(days=equipment.period)) + elif last_maintenance_done: + next_date = fields.Date.from_string(last_maintenance_done.close_date)+timedelta(days=equipment.period) + # If when we add the period to the last maintenance done and we still in past, we plan it for today + if next_date < fields.Date.from_string(date_now): + next_date = date_now + else: + next_date = fields.Date.to_string(fields.Date.from_string(date_now) + timedelta(days=equipment.period)) + + equipment.next_action_date = next_date @api.one @api.depends('maintenance_ids.stage_id.done') def _compute_maintenance_count(self): @@ -190,18 +221,31 @@ class MaintenanceEquipment(models.Model): category_ids = categories._search([], order=order, access_rights_uid=SUPERUSER_ID) return categories.browse(category_ids) + def _create_new_request(self, date): + self.ensure_one() + self.env['maintenance.request'].create({ + 'name': _('Preventive Maintenance - %s') % self.name, + 'request_date': date, + 'schedule_date': date, + 'category_id': self.category_id.id, + 'equipment_id': self.id, + 'maintenance_type': 'preventive', + 'owner_user_id': self.owner_user_id.id, + 'technician_user_id': self.technician_user_id.id, + }) + @api.model def _cron_generate_requests(self): - for equipment in self.search([]): - if equipment.period and equipment.next_action_date == date.today().strftime(DEFAULT_SERVER_DATE_FORMAT): - self.env['maintenance.request'].create({ - 'name': _('Preventive Maintenance - %s' % equipment.next_action_date), - 'request_date': equipment.next_action_date, - 'category_id': equipment.category_id.id, - 'equipment_id': equipment.id, - 'maintenance_type': 'preventive', - }) - + """ + Generates maintenance request on the next_action_date or today if none exists + """ + for equipment in self.search([('period', '>', 0)]): + next_requests = self.env['maintenance.request'].search([('stage_id.done', '=', False), + ('equipment_id', '=', equipment.id), + ('maintenance_type', '=', 'preventive'), + ('request_date', '=', equipment.next_action_date)]) + if not next_requests: + equipment._create_new_request(equipment.next_action_date) class MaintenanceRequest(models.Model): _name = 'maintenance.request' @@ -227,8 +271,8 @@ class MaintenanceRequest(models.Model): name = fields.Char('Subjects', required=True) description = fields.Text('Description') - request_date = fields.Date('Request Date', track_visibility='onchange', default=fields.Date.context_today) - + request_date = fields.Date('Request Date', track_visibility='onchange', default=fields.Date.context_today, + help="Date requested for the maintenance to happen") owner_user_id = fields.Many2one('res.users', string='Created by', default=lambda s: s.env.uid) category_id = fields.Many2one('maintenance.equipment.category', related='equipment_id.category_id', string='Category', store=True, readonly=True) equipment_id = fields.Many2one('maintenance.equipment', string='Equipment', index=True) @@ -237,13 +281,13 @@ class MaintenanceRequest(models.Model): group_expand='_read_group_stage_ids', default=_default_stage) priority = fields.Selection([('0', 'Very Low'), ('1', 'Low'), ('2', 'Normal'), ('3', 'High')], string='Priority') color = fields.Integer('Color Index') - close_date = fields.Date('Close Date') + close_date = fields.Date('Close Date', help="Date the maintenance was finished. ") kanban_state = fields.Selection([('normal', 'In Progress'), ('blocked', 'Blocked'), ('done', 'Ready for next stage')], string='Kanban State', required=True, default='normal', track_visibility='onchange') # active = fields.Boolean(default=True, help="Set active to false to hide the maintenance request without deleting it.") archive = fields.Boolean(default=False, help="Set archive to true to hide the maintenance request without deleting it.") maintenance_type = fields.Selection([('corrective', 'Corrective'), ('preventive', 'Preventive')], string='Maintenance Type', default="corrective") - schedule_date = fields.Datetime('Scheduled Date') + schedule_date = fields.Datetime('Scheduled Date', help="Date the maintenance team plans the maintenance. It should not differ much from the Request Date. ") maintenance_team_id = fields.Many2one('maintenance.team', string='Team', required=True, default=_get_default_team_id) duration = fields.Float(help="Duration in minutes and seconds.") diff --git a/addons/maintenance/tests/test_maintenance.py b/addons/maintenance/tests/test_maintenance.py index a9c55d95ffc..d73301d7ec2 100644 --- a/addons/maintenance/tests/test_maintenance.py +++ b/addons/maintenance/tests/test_maintenance.py @@ -4,7 +4,8 @@ import time from odoo.tests.common import TransactionCase - +from dateutil import relativedelta +import datetime class TestEquipment(TransactionCase): """ Test used to check that when doing equipment/maintenance_request/equipment_category creation.""" @@ -36,7 +37,7 @@ class TestEquipment(TransactionCase): groups_id=[(6, 0, [res_manager.id])] )) - def test_equipment_request_category(self): + def test_10_equipment_request_category(self): # Create a new equipment equipment_01 = self.equipment.sudo(self.manager).create({ @@ -75,3 +76,32 @@ class TestEquipment(TransactionCase): # I check that maintenance request is in the "In Progress" stage self.assertEquals(maintenance_request_01.stage_id.id, self.ref('maintenance.stage_1')) + + def test_20_cron(self): + """ Check the cron creates the necessary preventive maintenance requests""" + equipment_cron = self.equipment.create({ + 'name': 'High Maintenance Monitor because of Color Calibration', + 'category_id': self.ref('maintenance.equipment_monitor'), + 'technician_user_id': self.ref('base.user_root'), + 'owner_user_id': self.user.id, + 'assign_date': time.strftime('%Y-%m-%d'), + 'period': 7, + 'color': 3, + }) + + maintenance_request_cron = self.maintenance_request.create({ + 'name': 'Need a special calibration', + 'technician_user_id': self.user.id, + 'request_date': (datetime.datetime.now() + relativedelta.relativedelta(days=7)).strftime('%Y-%m-%d'), + 'maintenance_type': 'preventive', + 'owner_user_id': self.user.id, + 'equipment_id': equipment_cron.id, + 'color': 7, + 'stage_id': self.ref('maintenance.stage_0'), + 'maintenance_team_id': self.ref('maintenance.equipment_team_maintenance') + }) + + self.env['maintenance.equipment']._cron_generate_requests() + # As it is generating the requests for one month in advance, we should have 4 requests in total + tot_requests = self.maintenance_request.search([('equipment_id', '=', equipment_cron.id)]) + self.assertEqual(len(tot_requests), 1, 'The cron should have generated just 1 request for the High Maintenance Monitor.') diff --git a/addons/maintenance/views/maintenance_views.xml b/addons/maintenance/views/maintenance_views.xml index 99858bff69f..6be82359009 100644 --- a/addons/maintenance/views/maintenance_views.xml +++ b/addons/maintenance/views/maintenance_views.xml @@ -78,7 +78,7 @@ - + @@ -416,7 +416,7 @@ - diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index c57c079be7a..1f772e445f4 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -323,6 +323,7 @@ class MrpProduction(models.Model): data = { 'name': self.name, 'date': self.date_planned_start, + 'date_expected': self.date_planned_start, 'bom_line_id': bom_line.id, 'product_id': bom_line.product_id.id, 'product_uom_qty': quantity, diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 33ff2293052..42df10850ca 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -81,14 +81,14 @@ class StockMove(models.Model): store=True, help='Technical Field to order moves') # TDE: what ? - @api.multi + @api.depends('state', 'product_uom_qty', 'reserved_availability') def _qty_available(self): for move in self: # For consumables, state is available so availability = qty to do if move.state == 'assigned': move.quantity_available = move.product_uom_qty - else: - move.quantity_available = move.reserved_availability + elif move.product_id.uom_id and move.product_uom: + move.quantity_available = move.product_id.uom_id._compute_quantity(move.reserved_availability, move.product_uom) @api.multi @api.depends('move_lot_ids', 'move_lot_ids.quantity_done', 'quantity_done_store') diff --git a/addons/mrp/wizard/mrp_product_produce_views.xml b/addons/mrp/wizard/mrp_product_produce_views.xml index 9a22d279766..6650449dd16 100644 --- a/addons/mrp/wizard/mrp_product_produce_views.xml +++ b/addons/mrp/wizard/mrp_product_produce_views.xml @@ -17,7 +17,7 @@ - + diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 201ed22e66c..c2f9a0ca076 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -198,6 +198,7 @@ class PosOrder(models.Model): grouped_data = {} have_to_group_by = session and session.config_id.group_by or False + rounding_method = session and session.config_id.company_id.tax_calculation_rounding_method for order in self.filtered(lambda o: not o.account_move or order.state == 'paid'): current_company = order.sale_journal.company_id @@ -296,6 +297,14 @@ class PosOrder(models.Model): 'partner_id': partner_id }) + # round tax lines per order + if rounding_method == 'round_globally': + for group_key, group_value in grouped_data.iteritems(): + if group_key[0] == 'tax': + for line in group_value: + line['credit'] = cur.round(line['credit']) + line['debit'] = cur.round(line['debit']) + # counterpart insert_data('counter_part', { 'name': _("Trade Receivables"), # order.name, diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js index f7bf1f78010..02d1123bcc3 100644 --- a/addons/point_of_sale/static/src/js/chrome.js +++ b/addons/point_of_sale/static/src/js/chrome.js @@ -278,13 +278,19 @@ var DebugWidget = PosBaseWidget.extend({ }); this.$('.button.export_unpaid_orders').click(function(){ - self.gui.download_file(self.pos.export_unpaid_orders(), - "unpaid_orders_" + (new Date()).toUTCString().replace(/\ /g,'_') + '.json'); + self.gui.prepare_download_link( + self.pos.export_unpaid_orders(), + _t("unpaid orders") + ' ' + moment().format('YYYY-MM-DD-HH-mm-ss') + '.json', + ".export_unpaid_orders", ".download_unpaid_orders" + ); }); this.$('.button.export_paid_orders').click(function() { - self.gui.download_file(self.pos.export_paid_orders(), - "paid_orders_" + (new Date()).toUTCString().replace(/\ /g,'_') + '.json'); + self.gui.prepare_download_link( + self.pos.export_paid_orders(), + _t("paid orders") + ' ' + moment().format('YYYY-MM-DD-HH-mm-ss') + '.json', + ".export_paid_orders", ".download_paid_orders" + ); }); this.$('.button.import_orders input').on('change', function(event) { diff --git a/addons/point_of_sale/static/src/js/gui.js b/addons/point_of_sale/static/src/js/gui.js index f0755fa7573..87cb30caf78 100644 --- a/addons/point_of_sale/static/src/js/gui.js +++ b/addons/point_of_sale/static/src/js/gui.js @@ -362,7 +362,34 @@ var Gui = core.Class.extend({ // if 'contents' is not a string, it is converted into // a JSON representation of the contents. + + // TODO: remove me in master: deprecated in favor of prepare_download_link + // this method is kept for backward compatibility but is likely not going + // to work as many browsers do to not accept fake click events on links download_file: function(contents, name) { + href_params = this.prepare_file_blob(contents,name); + var evt = document.createEvent("HTMLEvents"); + evt.initEvent("click"); + + $("",href_params).get(0).dispatchEvent(evt); + + }, + + prepare_download_link: function(contents, filename, src, target) { + var href_params = this.prepare_file_blob(contents, filename); + + $(target).parent().attr(href_params); + $(src).addClass('oe_hidden'); + $(target).removeClass('oe_hidden'); + + // hide again after click + $(target).click(function() { + $(src).removeClass('oe_hidden'); + $(this).addClass('oe_hidden'); + }); + }, + + prepare_file_blob: function(contents, name) { var URL = window.URL || window.webkitURL; if (typeof contents !== 'string') { @@ -371,13 +398,8 @@ var Gui = core.Class.extend({ var blob = new Blob([contents]); - var evt = document.createEvent("HTMLEvents"); - evt.initEvent("click"); - - $("",{ - download: name || 'document.txt', - href: URL.createObjectURL(blob), - }).get(0).dispatchEvent(evt); + return {download: name || 'document.txt', + href: URL.createObjectURL(blob),} }, /* ---- Gui: EMAILS ---- */ diff --git a/addons/point_of_sale/static/src/js/popups.js b/addons/point_of_sale/static/src/js/popups.js index f4429840237..eaeae4d6f83 100644 --- a/addons/point_of_sale/static/src/js/popups.js +++ b/addons/point_of_sale/static/src/js/popups.js @@ -105,7 +105,9 @@ var ErrorTracebackPopupWidget = ErrorPopupWidget.extend({ this._super(opts); this.$('.download').off('click').click(function(){ - self.gui.download_file(self.options.body,'traceback.txt'); + self.gui.prepare_download_link(self.options.body, + _t('error') + ' ' + moment().format('YYYY-MM-DD-HH-mm-ss') + '.txt', + '.download', '.download_error_file'); }); this.$('.email').off('click').click(function(){ diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 3245c9a409b..3821fd212dc 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -336,7 +336,7 @@
Country -
+ +
@@ -1308,7 +1311,9 @@
  • Delete Paid Orders
  • Delete Unpaid Orders
  • Export Paid Orders
  • +
  • Download Paid Orders
  • Export Unpaid Orders
  • +
  • Download Unpaid Orders
  • Import Orders diff --git a/addons/point_of_sale/views/pos_order_report_view.xml b/addons/point_of_sale/views/pos_order_report_view.xml index 9559833a92b..43700f170c2 100644 --- a/addons/point_of_sale/views/pos_order_report_view.xml +++ b/addons/point_of_sale/views/pos_order_report_view.xml @@ -4,7 +4,7 @@ report.pos.order.pivot report.pos.order - + diff --git a/addons/product/views/product_views.xml b/addons/product/views/product_views.xml index 3bbc078c6cf..7c245ff55dd 100644 --- a/addons/product/views/product_views.xml +++ b/addons/product/views/product_views.xml @@ -44,6 +44,7 @@ + @@ -310,6 +311,7 @@ + diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index c46557ee51f..36e428a7730 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -448,6 +448,7 @@ class PurchaseOrder(models.Model): result = action.read()[0] #override the context to get rid of the default filtering on picking type + result.pop('id', None) result['context'] = {} pick_ids = sum([order.picking_ids.ids for order in self], []) #choose the view_mode accordingly diff --git a/addons/report/models/ir_actions_report_xml.py b/addons/report/models/ir_actions_report_xml.py index 667a2b43349..8461815e389 100644 --- a/addons/report/models/ir_actions_report_xml.py +++ b/addons/report/models/ir_actions_report_xml.py @@ -17,7 +17,7 @@ class ir_actions_report(models.Model): """ self.ensure_one() action_ref = self.env.ref('base.action_ui_view') - if not action_ref: + if not action_ref or len(self.report_name.split('.')) < 2: return False action_data = action_ref.read()[0] action_data['domain'] = [('name', 'ilike', self.report_name.split('.')[1]), ('type', '=', 'qweb')] diff --git a/addons/report_intrastat/views/report_intrastat_views.xml b/addons/report_intrastat/views/report_intrastat_views.xml index ca1c5c67c10..f91c5db3745 100644 --- a/addons/report_intrastat/views/report_intrastat_views.xml +++ b/addons/report_intrastat/views/report_intrastat_views.xml @@ -29,9 +29,11 @@ product.template - - - + + + + + diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 13d1e7a740a..a9d5c3dac20 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -506,10 +506,12 @@ class SaleOrder(models.Model): for tax in line.tax_id: group = tax.tax_group_id res.setdefault(group, 0.0) - amount = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty)['taxes'][0]['amount'] + amount = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty, + product=line.product_id, partner=line.order_partner_id)['taxes'][0]['amount'] res[group] += amount if tax.include_base_amount: - base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1)['taxes'][0]['amount'] + base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1, product=line.product_id, + partner=line.order_partner_id)['taxes'][0]['amount'] res = sorted(res.items(), key=lambda l: l[0].sequence) res = map(lambda l: (l[0].name, l[1]), res) return res diff --git a/addons/sales_team/static/src/xml/sales_team_dashboard.xml b/addons/sales_team/static/src/xml/sales_team_dashboard.xml index 93f2f3ca9a9..b1bb4a6ca65 100644 --- a/addons/sales_team/static/src/xml/sales_team_dashboard.xml +++ b/addons/sales_team/static/src/xml/sales_team_dashboard.xml @@ -84,7 +84,7 @@ - + exp. closing diff --git a/addons/stock/models/stock_pack_operation.py b/addons/stock/models/stock_pack_operation.py index b6c46310b94..6e51a573546 100644 --- a/addons/stock/models/stock_pack_operation.py +++ b/addons/stock/models/stock_pack_operation.py @@ -34,8 +34,7 @@ class PackOperation(models.Model): product_qty = fields.Float('To Do', default=0.0, digits=dp.get_precision('Product Unit of Measure'), required=True) ordered_qty = fields.Float('Ordered Quantity', digits=dp.get_precision('Product Unit of Measure')) qty_done = fields.Float('Done', default=0.0, digits=dp.get_precision('Product Unit of Measure')) - # TDE FIXME: what what what what ?? - is_done = fields.Boolean(compute='_compute_is_done', inverse='_set_is_done', string='Done', oldname='processed_boolean') + is_done = fields.Boolean(compute='_compute_is_done', string='Done', readonly=False, oldname='processed_boolean') package_id = fields.Many2one('stock.quant.package', 'Source Package') pack_lot_ids = fields.One2many('stock.pack.operation.lot', 'operation_id', 'Lots/Serial Numbers Used') result_package_id = fields.Many2one( @@ -73,9 +72,8 @@ class PackOperation(models.Model): def _compute_is_done(self): self.is_done = self.qty_done > 0.0 - @api.one - def _set_is_done(self): - # TDE FIXME: whuuuut ??? + @api.onchange('is_done') + def on_change_is_done(self): if not self.product_id: if self.is_done and self.qty_done == 0: self.qty_done = 1.0 diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py index e0ddb97a84e..3ee20467a88 100644 --- a/addons/stock/tests/test_stock_flow.py +++ b/addons/stock/tests/test_stock_flow.py @@ -1366,7 +1366,8 @@ class TestStockFlow(TestStockCommon): # set the pack in pack operation to 'done' for pack in picking_pack.pack_operation_pack_ids: - pack.qty_done = 1.0 + pack.is_done = True + pack.on_change_is_done() # Put in a pack picking_pack.put_in_pack() @@ -1384,7 +1385,8 @@ class TestStockFlow(TestStockCommon): # set the pack in pack operation to 'done' for pack in picking_out.pack_operation_pack_ids: - pack.qty_done = 1.0 + pack.is_done = True + pack.on_change_is_done() # Validate picking picking_out.do_new_transfer() diff --git a/addons/stock/views/product_views.xml b/addons/stock/views/product_views.xml index f3c05e4dcb1..6fe6c489083 100644 --- a/addons/stock/views/product_views.xml +++ b/addons/stock/views/product_views.xml @@ -140,9 +140,6 @@
  • - - - diff --git a/addons/stock/views/stock_production_lot_views.xml b/addons/stock/views/stock_production_lot_views.xml index 31a2c532a13..cfa5d3929ce 100644 --- a/addons/stock/views/stock_production_lot_views.xml +++ b/addons/stock/views/stock_production_lot_views.xml @@ -15,7 +15,6 @@