From 487ed76bc1254c84115a27dce8a8b31624869193 Mon Sep 17 00:00:00 2001 From: flectra-admin Date: Thu, 5 Apr 2018 13:55:40 +0530 Subject: [PATCH] [ODOO] Upstream Changes of Odoo to Flectra till commit ref. https://github.com/odoo/odoo/commit/d636c6c95cb6af5fead1509ce9723e23d5f5f762 --- addons/account/models/account.py | 23 +- .../account/models/account_bank_statement.py | 4 +- addons/account/models/account_invoice.py | 28 +- .../models/account_journal_dashboard.py | 10 +- addons/account/models/account_move.py | 26 +- addons/account/models/account_payment.py | 27 +- addons/account/models/chart_template.py | 5 +- addons/account/models/company.py | 8 +- addons/account/models/product.py | 25 + .../report/account_aged_partner_balance.py | 16 +- .../account/report/account_partner_ledger.py | 6 +- .../js/reconciliation/reconciliation_model.js | 38 +- .../reconciliation/reconciliation_renderer.js | 50 +- .../static/tests/reconciliation_tests.js | 119 +- addons/account/tests/test_payment.py | 54 +- addons/account/tests/test_reconciliation.py | 39 +- addons/account/views/account_invoice_view.xml | 38 +- .../views/account_journal_dashboard_view.xml | 12 +- .../views/account_portal_templates.xml | 10 +- addons/account/views/account_report.xml | 1 + addons/account/views/account_view.xml | 2 +- addons/account/views/report_invoice.xml | 14 +- .../models/account_analytic_default.py | 4 +- .../account_bank_statement_import.py | 5 +- .../models/account_payment.py | 2 +- addons/account_invoicing/__init__.py | 22 +- addons/account_invoicing/__manifest__.py | 2 + addons/account_payment/controllers/payment.py | 10 +- addons/account_payment/models/payment.py | 7 + .../views/account_portal_templates.xml | 41 +- .../account_voucher/models/account_voucher.py | 65 +- .../tests/test_account_voucher_branch.py | 9 +- .../views/account_voucher_views.xml | 37 +- addons/auth_crypt/models/res_users.py | 3 + addons/auth_oauth/models/res_users.py | 3 + addons/auth_signup/data/auth_signup_data.xml | 9 + addons/auth_signup/models/__init__.py | 1 + addons/auth_signup/models/ir_model_fields.py | 20 + addons/barcodes/models/__init__.py | 1 + addons/barcodes/models/ir_http.py | 16 + .../barcodes/static/src/js/barcode_events.js | 113 +- .../static/src/js/barcode_form_view.js | 8 +- .../base_address_city/models/res_partner.py | 8 +- .../wizard/base_gengo_translations.py | 6 +- addons/base_iban/models/res_partner_bank.py | 1 + addons/base_import/models/base_import.py | 8 +- .../static/src/js/import_action.js | 2 + addons/base_import_module/models/ir_module.py | 11 +- .../base_setup/models/res_config_settings.py | 2 + .../models/res_partner.py | 25 +- addons/board/static/tests/dashboard_tests.js | 52 + addons/bus/static/src/js/bus.js | 3 +- addons/calendar/models/calendar.py | 6 +- addons/calendar/views/calendar_views.xml | 8 +- addons/crm/data/web_planner_data.xml | 2 +- addons/crm/models/crm_lead.py | 26 +- addons/crm/models/res_config_settings.py | 2 +- .../report/crm_opportunity_report_views.xml | 8 +- addons/crm/views/crm_lead_views.xml | 16 +- addons/crm/wizard/base_partner_merge.py | 2 +- .../crm_phone_validation/models/crm_lead.py | 4 +- .../models/res_partner.py | 4 +- addons/delivery/models/__init__.py | 1 + addons/delivery/models/delivery_carrier.py | 5 +- addons/delivery/models/res_config_settings.py | 13 + addons/delivery/models/stock_picking.py | 14 +- .../security/delivery_carrier_security.xml | 1 + addons/event/data/email_template_data.xml | 24 +- addons/event/models/event.py | 2 +- addons/event/models/event_config_settings.py | 1 + addons/event/models/event_mail.py | 6 +- .../event/views/res_config_settings_views.xml | 14 + .../wizard/event_edit_registration.xml | 2 +- addons/fleet/models/fleet_vehicle.py | 4 +- addons/fleet/static/description/index.html | 50 +- addons/gamification/__manifest__.py | 2 +- addons/gamification/data/goal_base.xml | 3 +- .../static/description/index.html | 62 +- addons/google_drive/models/google_drive.py | 4 + addons/google_drive/static/src/js/gdrive.js | 141 ++- .../google_drive/static/tests/gdrive_test.js | 112 ++ .../views/google_drive_templates.xml | 5 + addons/hr/models/hr.py | 20 +- addons/hr/static/description/index.html | 36 +- addons/hr/views/hr_views.xml | 11 +- .../hr_expense/static/description/index.html | 40 +- addons/hr_expense/views/hr_expense_views.xml | 10 +- .../hr_holidays/static/description/index.html | 40 +- .../static/src/js/hr_org_chart.js | 1 + addons/hr_payroll/models/hr_payslip.py | 4 +- addons/hr_payroll/models/hr_salary_rule.py | 6 +- .../hr_payroll/models/res_config_settings.py | 1 + .../views/res_config_settings_views.xml | 14 + .../hr_payroll_payslips_by_employees.py | 1 + addons/hr_recruitment/README.md | 4 +- .../data/hr_recruitment_data.xml | 3 + .../static/description/index.html | 50 +- addons/hr_timesheet/models/hr_timesheet.py | 15 +- addons/hr_timesheet/models/project.py | 17 +- .../models/res_config_settings.py | 1 + .../security/hr_timesheet_security.xml | 2 +- addons/hr_timesheet/tests/test_timesheet.py | 48 +- .../views/res_config_settings_views.xml | 33 + addons/http_routing/models/ir_http.py | 2 + addons/hw_scanner/controllers/main.py | 2 +- addons/iap/static/src/xml/iap_templates.xml | 8 +- addons/im_livechat/controllers/main.py | 3 +- .../im_livechat/models/im_livechat_channel.py | 2 +- .../im_livechat/static/description/index.html | 60 +- .../static/src/js/im_livechat_backend.js | 2 +- .../views/im_livechat_channel_views.xml | 9 +- .../data/l10n_be_hr_payroll_data.xml | 12 +- .../models/l10n_be_hr_payroll.py | 37 +- .../views/l10n_be_hr_payroll_view.xml | 8 +- .../models/hr_contract.py | 6 +- .../views/hr_contract_views.xml | 4 +- .../views/res_config_settings_views.xml | 4 +- .../data/mail_template_data.xml | 2 +- addons/l10n_ch/models/mail_compose_message.py | 2 +- addons/l10n_ch/models/res_company.py | 20 + addons/l10n_ch/models/res_config_settings.py | 8 + addons/l10n_ch/report/isr_report.xml | 14 +- .../l10n_ch/static/src/less/report_isr.less | 95 +- .../views/res_config_settings_views.xml | 29 + .../l10n_co/data/account.account.template.csv | 67 + addons/l10n_co/data/account.tax.template.csv | 108 +- addons/l10n_co/models/res_partner.py | 15 +- addons/l10n_co/views/res_partner.xml | 2 +- .../data/account_chart_template_data.xml | 60 +- .../2.0/post-migrate_tags_on_taxes.py | 8 +- addons/l10n_fr_fec/wizard/account_fr_fec.py | 4 +- .../report/report_l10n_fr_fiche_paye.xml | 2 +- addons/l10n_fr_pos_cert/models/pos.py | 8 +- addons/l10n_in/views/report_invoice.xml | 8 +- .../views/report_payslip_details_template.xml | 6 +- .../l10n_multilang/models/l10n_multilang.py | 4 +- addons/l10n_mx/__manifest__.py | 1 + addons/l10n_mx/data/account_tax_data.xml | 4 + addons/l10n_mx/models/__init__.py | 1 + addons/l10n_mx/models/res_config_settings.py | 9 + .../views/res_config_settings_views.xml | 23 + .../l10n_nl/data/account_chart_template.xml | 4 +- ...count_fiscal_position_account_template.xml | 4 +- .../account_fiscal_position_tax_template.xml | 4 +- .../data/account_fiscal_position_template.xml | 4 +- addons/l10n_nl/data/account_tax_template.xml | 4 +- addons/mail/data/mail_shortcode_data.xml | 17 +- addons/mail/models/ir_model.py | 2 +- addons/mail/models/mail_activity.py | 9 + addons/mail/models/mail_channel.py | 4 +- addons/mail/models/mail_message.py | 37 +- addons/mail/models/mail_template.py | 2 +- .../src/img/smiley/smile_open_mouth.png | Bin 0 -> 6262 bytes addons/mail/static/src/js/activity.js | 146 ++- addons/mail/static/src/js/chat_manager.js | 2 +- addons/mail/static/src/js/chatter.js | 2 +- addons/mail/static/src/js/client_action.js | 2 +- addons/mail/static/src/js/composer.js | 56 +- addons/mail/static/src/js/document_viewer.js | 35 +- addons/mail/static/src/js/systray.js | 2 +- addons/mail/static/src/js/thread.js | 6 +- addons/mail/static/src/less/composer.less | 4 + addons/mail/static/src/xml/activity.xml | 2 +- addons/mail/static/src/xml/announcement.xml | 18 + addons/mail/static/src/xml/thread.xml | 9 +- addons/mail/static/tests/chatter_tests.js | 125 +- addons/mail/static/tests/mail_utils_tests.js | 34 + addons/mail/tests/test_mail_message.py | 76 ++ addons/mail/views/mail_templates.xml | 1 + addons/maintenance/models/maintenance.py | 11 +- .../maintenance/views/maintenance_views.xml | 17 +- addons/mass_mailing/models/mass_mailing.py | 9 +- .../static/src/js/mass_mailing_editor.js | 14 +- .../mass_mailing/views/link_tracker_views.xml | 2 +- .../views/snippets_themes_options.xml | 23 +- addons/membership/models/partner.py | 6 +- addons/mrp/models/mrp_bom.py | 5 + addons/mrp/models/mrp_production.py | 11 + addons/mrp/models/mrp_workorder.py | 24 +- addons/mrp/models/product.py | 13 +- addons/mrp/models/res_config_settings.py | 8 + addons/mrp/models/stock_move.py | 12 +- addons/mrp/models/stock_scrap.py | 2 +- addons/mrp/security/mrp_security.xml | 7 + addons/mrp/static/description/index.html | 72 +- addons/mrp/tests/test_procurement.py | 2 +- addons/mrp/tests/test_unbuild.py | 2 +- addons/mrp/views/mrp_bom_views.xml | 8 +- addons/mrp/views/mrp_message_views.xml | 1 + addons/mrp/views/mrp_production_views.xml | 14 +- addons/mrp/views/mrp_routing_views.xml | 1 + addons/mrp/views/mrp_workcenter_views.xml | 4 + .../mrp/views/res_config_settings_views.xml | 46 + addons/mrp/wizard/mrp_product_produce.py | 32 +- .../mrp/wizard/stock_warn_insufficient_qty.py | 2 +- .../wizard/change_production_qty.py | 2 +- addons/mrp_repair/data/mrp_repair_data.xml | 2 +- addons/mrp_repair/views/mrp_repair_views.xml | 4 +- addons/note/static/description/index.html | 8 +- addons/note_pad/models/note.py | 2 +- addons/pad/models/pad.py | 62 +- addons/pad/py_etherpad/__init__.py | 14 + addons/pad_project/models/project.py | 8 +- addons/payment/__manifest__.py | 2 +- addons/payment/controllers/portal.py | 96 +- addons/payment/models/payment_acquirer.py | 39 +- addons/payment/security/ir.model.access.csv | 1 - .../payment/static/src/css/portal_payment.css | 9 + addons/payment/static/src/js/payment_form.js | 157 ++- .../payment/static/src/js/payment_portal.js | 3 + .../src/js/payment_transaction_portal.js | 26 +- .../payment/static/src/less/payment_form.less | 7 +- addons/payment/tests/common.py | 1 + .../views/payment_portal_templates.xml | 68 +- addons/payment/views/payment_templates.xml | 15 +- addons/payment/views/payment_views.xml | 4 +- addons/payment_adyen/__manifest__.py | 2 +- addons/payment_adyen/models/payment.py | 6 +- addons/payment_authorize/__manifest__.py | 2 +- addons/payment_authorize/controllers/main.py | 29 +- .../models/authorize_request.py | 7 +- addons/payment_authorize/models/payment.py | 16 +- .../payment_authorize/tests/test_authorize.py | 1 + .../views/payment_authorize_templates.xml | 3 +- addons/payment_buckaroo/__manifest__.py | 2 +- addons/payment_buckaroo/models/payment.py | 2 - addons/payment_ogone/__manifest__.py | 2 +- addons/payment_ogone/controllers/main.py | 7 +- addons/payment_ogone/models/payment.py | 40 +- .../views/payment_ogone_templates.xml | 2 +- addons/payment_paypal/__manifest__.py | 2 +- addons/payment_paypal/controllers/main.py | 2 +- addons/payment_paypal/models/payment.py | 2 - addons/payment_payumoney/__manifest__.py | 2 +- addons/payment_payumoney/models/payment.py | 2 - addons/payment_sips/models/payment.py | 4 +- addons/payment_stripe/__manifest__.py | 2 +- addons/payment_stripe/controllers/main.py | 4 + addons/payment_stripe/models/payment.py | 8 +- addons/payment_stripe/static/src/js/stripe.js | 37 +- .../views/payment_stripe_templates.xml | 2 +- addons/payment_transfer/__manifest__.py | 2 +- .../models/phone_validation_mixin.py | 8 +- .../tools/phone_validation.py | 2 +- addons/point_of_sale/__init__.py | 1 + .../point_of_sale/data/point_of_sale_demo.xml | 1 + addons/point_of_sale/models/pos_config.py | 6 + addons/point_of_sale/models/pos_order.py | 80 +- .../security/ir.model.access.csv | 2 +- .../static/description/index.html | 18 +- .../point_of_sale/static/src/css/chrome50.css | 2 +- addons/point_of_sale/static/src/js/devices.js | 3 +- addons/point_of_sale/static/src/js/models.js | 30 +- addons/point_of_sale/static/src/js/screens.js | 28 +- addons/point_of_sale/static/src/js/tests.js | 17 +- addons/point_of_sale/tests/__init__.py | 1 + .../point_of_sale/tests/test_anglo_saxon.py | 101 ++ .../views/account_journal_view.xml | 9 + .../point_of_sale/views/pos_config_view.xml | 39 +- addons/portal/controllers/mail.py | 8 +- addons/portal/static/src/js/portal_chatter.js | 3 +- .../static/src/xml/portal_signature.xml | 4 +- addons/portal/views/portal_templates.xml | 10 +- addons/pos_discount/models/pos_config.py | 13 +- addons/pos_discount/static/src/js/discount.js | 7 + .../static/src/css/pos_mercury.css | 2 +- .../pos_mercury/static/src/js/pos_mercury.js | 165 +-- .../pos_mercury_transaction_templates.xml | 8 +- .../static/src/js/multiprint.js | 3 +- .../static/src/xml/printbill.xml | 2 +- addons/product/models/product_attribute.py | 7 +- addons/product/models/product_pricelist.py | 2 +- addons/product/models/product_template.py | 12 +- addons/product/tests/test_variants.py | 235 ++++ addons/product/views/product_views.xml | 8 +- addons/product_extended/models/product.py | 4 +- .../product_extended/views/product_views.xml | 20 + addons/project/controllers/portal.py | 16 +- addons/project/data/project_data.xml | 2 +- addons/project/models/project.py | 25 +- addons/project/models/res_company.py | 1 + addons/project/models/res_config_settings.py | 6 + .../static/src/less/project_dashboard.less | 4 +- addons/project/tests/test_access_rights.py | 9 + .../views/project_portal_templates.xml | 10 +- addons/project/views/project_views.xml | 12 +- .../views/res_config_settings_views.xml | 15 + .../models/hr_holidays.py | 7 +- .../tests/test_timesheet_holidays.py | 5 +- addons/purchase/data/mail_template_data.xml | 8 +- addons/purchase/models/account_invoice.py | 8 +- addons/purchase/models/purchase.py | 61 +- addons/purchase/models/res_config_settings.py | 1 + addons/purchase/models/stock.py | 20 +- .../purchase/security/purchase_security.xml | 4 +- addons/purchase/test/fifo_price.yml | 2 +- addons/purchase/tests/test_create_picking.py | 139 ++ addons/purchase/tests/test_stockvaluation.py | 73 ++ .../purchase/views/account_invoice_views.xml | 4 +- .../views/res_config_settings_views.xml | 11 + addons/purchase/views/stock_views.xml | 9 +- addons/rating_project/data/project_data.xml | 1 + addons/resource/models/resource.py | 16 +- addons/resource/models/resource_mixin.py | 6 +- addons/resource/tests/test_resource.py | 6 + addons/sale/controllers/mail.py | 1 + addons/sale/controllers/portal.py | 2 +- addons/sale/data/mail_template_data.xml | 2 +- addons/sale/models/__init__.py | 1 + addons/sale/models/analytic.py | 3 +- addons/sale/models/ir_model_fields.py | 19 + addons/sale/models/product_product.py | 2 + addons/sale/models/sale.py | 22 +- addons/sale/report/sale_report_templates.xml | 7 +- addons/sale/views/sale_portal_templates.xml | 10 +- addons/sale/views/sale_views.xml | 4 +- addons/sale/wizard/mail_compose_message.py | 2 +- addons/sale_crm/models/crm_lead.py | 3 +- addons/sale_management/__init__.py | 20 + addons/sale_management/__manifest__.py | 2 + .../static/description/index.html | 72 +- addons/sale_mrp/tests/test_sale_mrp_flow.py | 4 +- addons/sale_payment/__manifest__.py | 1 + addons/sale_payment/controllers/payment.py | 10 +- addons/sale_payment/models/payment.py | 6 +- .../views/sale_portal_templates.xml | 47 +- addons/sale_payment/views/settings.xml | 15 + addons/sale_stock/models/account_invoice.py | 4 +- addons/sale_stock/models/sale_order.py | 12 +- addons/sale_stock/models/stock.py | 2 +- addons/sale_stock/tests/test_sale_stock.py | 122 +- addons/sale_stock/views/stock_views.xml | 2 +- addons/sale_timesheet/models/account.py | 110 +- .../sale_timesheet/models/account_invoice.py | 9 +- addons/sale_timesheet/models/product.py | 2 +- addons/sale_timesheet/models/project.py | 15 +- addons/sale_timesheet/models/sale_order.py | 4 +- .../views/hr_timesheet_templates.xml | 16 +- addons/sales_team/models/crm_team.py | 2 +- addons/sms/__manifest__.py | 6 +- addons/sms/static/src/js/sms_widget.js | 120 ++ .../sms/static/src/tests/sms_widget_test.js | 67 + addons/sms/static/src/xml/sms_widget.xml | 6 + addons/sms/views/templates.xml | 16 + addons/sms/wizard/send_sms.py | 11 +- addons/sms/wizard/send_sms_views.xml | 2 +- addons/stock/__init__.py | 14 +- addons/stock/__manifest__.py | 1 + addons/stock/data/stock_demo.yml | 5 - addons/stock/data/web_planner_data.xml | 1 + addons/stock/models/procurement.py | 4 +- addons/stock/models/product.py | 10 +- addons/stock/models/res_company.py | 8 +- addons/stock/models/res_config_settings.py | 12 +- addons/stock/models/stock_inventory.py | 11 +- addons/stock/models/stock_move.py | 77 +- addons/stock/models/stock_move_line.py | 102 +- addons/stock/models/stock_picking.py | 106 +- addons/stock/models/stock_quant.py | 12 +- addons/stock/models/stock_scrap.py | 8 +- addons/stock/models/stock_traceability.py | 12 +- addons/stock/models/stock_warehouse.py | 13 +- addons/stock/report/report_deliveryslip.xml | 19 +- .../report/report_stockpicking_operations.xml | 12 +- addons/stock/static/description/index.html | 54 +- .../js/stock_traceability_report_backend.js | 3 +- addons/stock/tests/test_move.py | 290 ++++- addons/stock/tests/test_move2.py | 108 +- addons/stock/tests/test_warehouse.py | 2 +- .../stock/views/res_config_settings_views.xml | 87 ++ addons/stock/views/stock_picking_views.xml | 18 +- .../stock/wizard/stock_change_product_qty.py | 2 +- .../stock/wizard/stock_immediate_transfer.py | 7 +- .../data/stock_account_data_post_install.yml | 10 +- .../stock_account/models/account_invoice.py | 63 +- addons/stock_account/models/product.py | 63 + addons/stock_account/models/stock.py | 43 +- .../tests/test_stockvaluation.py | 64 +- .../views/stock_account_views.xml | 6 +- addons/stock_dropshipping/__manifest__.py | 2 +- addons/stock_dropshipping/models/purchase.py | 18 +- .../test/cancellation_propagated.yml | 7 +- addons/stock_dropshipping/test/crossdock.yml | 7 +- addons/stock_dropshipping/test/dropship.yml | 2 + addons/stock_dropshipping/test/lifo_price.yml | 2 +- addons/stock_dropshipping/tests/__init__.py | 4 + .../tests/test_stockvaluation.py | 269 ++++ .../models/stock_landed_cost.py | 21 +- .../views/stock_landed_cost_views.xml | 2 +- .../tests/test_batch_picking.py | 2 +- addons/survey/controllers/main.py | 5 +- addons/survey/models/survey.py | 4 +- addons/survey/security/survey_security.xml | 2 +- addons/survey/static/description/index.html | 44 +- .../static/src/less/cosmo/fix.css | 2 +- .../static/src/less/cyborg/bootswatch.less | 2 +- .../static/src/less/cyborg/fix.css | 2 +- .../static/src/less/flatly/fix.css | 2 +- .../static/src/less/journal/fix.css | 2 +- .../static/src/less/readable/fix.css | 2 +- .../static/src/less/simplex/bootswatch.less | 2 +- .../static/src/less/simplex/fix.css | 2 +- .../static/src/less/slate/bootswatch.less | 2 +- .../static/src/less/spacelab/bootswatch.less | 2 +- .../static/src/less/spacelab/fix.css | 2 +- .../static/src/less/united/bootswatch.less | 2 +- .../theme_default/static/src/less/colors.less | 2 +- .../src/less/option_color_amethyst.less | 2 +- addons/utm/models/ir_http.py | 25 +- addons/web/static/lib/clipboard/clipboard.js | 2 +- .../web/static/lib/es5-shim/es5-shim.min.js | 2 +- .../lib/fullcalendar/js/fullcalendar.js | 2 +- .../lib/jquery.hotkeys/jquery.hotkeys.js | 2 +- .../jquery.mjs.nestedSortable.js | 2 +- .../jquery.placeholder/jquery.placeholder.js | 2 +- addons/web/static/lib/jquery.ui/jquery-ui.js | 2 +- addons/web/static/lib/nvd3/d3.v3.js | 2 +- addons/web/static/lib/nvd3/nv.d3.js | 50 +- .../lib/pdfjs/external/cmapscompress/parse.js | 2 +- .../lib/pdfjs/worker_loader_by_flectra.js | 2 +- addons/web/static/lib/qweb/qweb2.js | 7 +- .../web/static/lib/rtl/flectra-rtl-custom.css | 7 +- .../select2-bootstrap.css | 2 +- addons/web/static/lib/select2/select2.css | 2 +- addons/web/static/lib/select2/select2.js | 2 +- .../web/static/lib/underscore/underscore.js | 2 +- addons/web/static/src/js/_deprecated/data.js | 1 + .../static/src/js/chrome/action_manager.js | 3 +- .../web/static/src/js/chrome/control_panel.js | 2 +- addons/web/static/src/js/chrome/menu.js | 6 +- .../web/static/src/js/chrome/search_menus.js | 14 +- .../web/static/src/js/chrome/view_manager.js | 48 +- addons/web/static/src/js/chrome/web_client.js | 15 +- addons/web/static/src/js/core/bus.js | 5 +- addons/web/static/src/js/core/domain.js | 2 +- addons/web/static/src/js/core/session.js | 3 +- addons/web/static/src/js/core/utils.js | 19 +- .../web/static/src/js/fields/basic_fields.js | 7 +- .../static/src/js/fields/relational_fields.js | 35 +- .../static/src/js/fields/special_fields.js | 3 +- addons/web/static/src/js/libs/jquery.js | 3 + .../static/src/js/report/qwebactionmanager.js | 3 +- .../static/src/js/services/crash_manager.js | 2 +- .../web/static/src/js/views/abstract_view.js | 12 +- .../src/js/views/basic/basic_controller.js | 13 +- .../static/src/js/views/basic/basic_model.js | 457 ++++--- .../src/js/views/basic/basic_renderer.js | 67 +- .../static/src/js/views/basic/basic_view.js | 7 +- .../js/views/calendar/calendar_controller.js | 2 +- .../src/js/views/calendar/calendar_model.js | 12 +- .../js/views/calendar/calendar_renderer.js | 5 - .../static/src/js/views/form/form_renderer.js | 4 + .../src/js/views/kanban/kanban_column.js | 9 +- .../src/js/views/kanban/kanban_controller.js | 8 +- .../src/js/views/kanban/kanban_model.js | 17 +- .../js/views/list/list_editable_renderer.js | 14 +- .../static/src/js/views/list/list_renderer.js | 9 +- .../src/js/views/pivot/pivot_controller.js | 1 + .../static/src/js/views/pivot/pivot_model.js | 3 +- .../web/static/src/js/views/view_dialogs.js | 20 + .../web/static/src/js/widgets/data_export.js | 2 +- .../static/src/js/widgets/debug_manager.js | 4 +- .../src/js/widgets/domain_selector_dialog.js | 6 +- .../web/static/src/js/widgets/notification.js | 8 +- addons/web/static/src/less/form_view.less | 4 +- .../web/static/src/less/form_view_extra.less | 7 +- addons/web/static/src/less/kanban_view.less | 8 +- .../static/src/less/kanban_view_mobile.less | 3 +- addons/web/static/src/less/list_view.less | 8 + addons/web/static/src/less/webclient.less | 50 +- addons/web/static/src/xml/base.xml | 29 +- addons/web/static/tests/chrome/pager_tests.js | 2 +- .../static/tests/core/concurrency_tests.js | 2 +- addons/web/static/tests/core/time_tests.js | 2 +- addons/web/static/tests/core/util_tests.js | 13 +- addons/web/static/tests/core/widget_tests.js | 2 +- .../static/tests/fields/basic_fields_tests.js | 80 +- .../tests/fields/relational_fields_tests.js | 1121 ++++++++++++++++- .../web/static/tests/helpers/mock_server.js | 6 +- addons/web/static/tests/helpers/test_utils.js | 87 +- .../static/tests/views/basic_model_tests.js | 36 + .../web/static/tests/views/calendar_tests.js | 67 +- .../web/static/tests/views/form_benchmarks.js | 2 +- addons/web/static/tests/views/form_tests.js | 159 ++- addons/web/static/tests/views/kanban_tests.js | 231 ++++ addons/web/static/tests/views/list_tests.js | 200 ++- addons/web/static/tests/views/pivot_tests.js | 29 +- .../static/tests/views/view_dialogs_tests.js | 159 ++- addons/web_editor/controllers/main.py | 18 +- .../static/lib/nearest/jquery.nearest.min.js | 2 +- .../static/src/js/backend/fields.js | 15 + addons/web_editor/static/src/js/editor/rte.js | 44 +- .../static/src/js/editor/rte.summernote.js | 9 +- .../static/src/js/editor/snippets.options.js | 9 +- .../static/src/js/editor/summernote.js | 112 +- .../static/src/js/editor/transcoder.js | 60 +- addons/web_editor/static/src/js/inline.js | 1 + .../web_editor/static/src/js/widgets/ace.js | 8 +- .../static/src/js/widgets/widgets.js | 180 ++- .../static/src/less/web_editor.backend.less | 3 +- .../src/less/web_editor.ui.components.less | 10 +- .../static/src/less/web_editor.variables.less | 6 + addons/web_editor/static/src/xml/editor.xml | 2 + .../static/tests/web_editor_tests.js | 20 +- addons/web_planner/models/web_planner.py | 1 + addons/website/controllers/main.py | 4 +- addons/website/models/ir_http.py | 28 +- addons/website/models/ir_model_fields.py | 11 +- addons/website/models/ir_qweb.py | 2 +- addons/website/models/res_config_settings.py | 2 + addons/website/models/website.py | 17 +- .../static/src/js/editor/snippets.options.js | 3 +- addons/website/static/src/js/menu/content.js | 36 +- .../views/res_config_settings_views.xml | 11 + .../views/website_navbar_templates.xml | 2 +- addons/website/views/website_templates.xml | 9 +- addons/website_blog/__manifest__.py | 1 - .../website_blog/data/website_blog_demo.xml | 2 +- addons/website_blog/models/website_blog.py | 11 +- .../views/website_blog_templates.xml | 4 +- addons/website_crm/__manifest__.py | 1 - .../models/crm_lead.py | 2 +- .../controllers/website_form.py | 12 +- addons/website_customer/controllers/main.py | 2 +- addons/website_event/models/event.py | 2 +- .../views/event_templates.xml | 3 +- .../website_event_track/controllers/main.py | 4 +- .../views/event_track_templates.xml | 4 +- addons/website_form/controllers/main.py | 16 +- .../static/src/js/website_form.js | 11 +- addons/website_forum/controllers/main.py | 4 +- addons/website_forum/models/forum.py | 6 +- addons/website_forum/views/website_forum.xml | 2 +- .../static/src/less/s_hr_rating.less | 2 +- .../static/lib/zeroclipboard/ZeroClipboard.js | 2 +- .../static/src/css/website_links.css | 2 +- .../views/website_mail_channel_templates.xml | 2 +- addons/website_quote/controllers/main.py | 8 +- .../website_quote/data/website_quote_data.xml | 5 + addons/website_quote/models/sale_order.py | 17 +- .../views/website_quote_templates.xml | 58 +- addons/website_rating/__manifest__.py | 2 +- addons/website_rma/controllers/portal.py | 4 +- addons/website_rma/views/templates.xml | 2 - addons/website_sale/controllers/main.py | 32 +- addons/website_sale/controllers/portal.py | 279 ---- addons/website_sale/data/data.xml | 1 - addons/website_sale/data/demo.xml | 10 + addons/website_sale/models/__init__.py | 1 + addons/website_sale/models/ir_model_fields.py | 2 +- addons/website_sale/models/payment.py | 26 + addons/website_sale/models/website.py | 4 +- .../static/src/css/website_sale.css | 3 + .../static/src/css/website_sale.sass | 2 + .../static/src/js/website_sale.js | 27 +- .../views/res_config_settings_views.xml | 4 +- .../website_sale/views/sale_order_views.xml | 1 + .../website_sale/views/sale_report_views.xml | 2 +- addons/website_sale/views/templates.xml | 61 +- .../static/src/js/website_sale_comparison.js | 14 +- .../website_sale_comparison_template.xml | 2 +- .../website_sale_delivery/controllers/main.py | 8 +- .../models/res_country.py | 2 + .../models/sale_order.py | 5 +- .../static/src/js/website_sale_delivery.js | 2 + .../src/less/website_sale_delivery.less | 2 +- .../views/website_sale_delivery_templates.xml | 6 +- .../static/src/js/website_sale.test.js | 124 ++ .../views/website_sale_options_templates.xml | 2 +- .../static/src/js/website_sale_stock.js | 8 +- .../static/src/js/website_sale_wishlist.js | 31 +- .../src/less/website_sale_wishlist.less | 2 +- addons/website_slides/models/slides.py | 2 +- addons/website_slides/static/src/js/slides.js | 7 +- .../static/src/js/slides_embed.js | 4 +- .../website_slides/views/website_slides.xml | 5 - flectra/addons/base/base_data.xml | 18 +- flectra/addons/base/ir/ir_actions.py | 5 +- flectra/addons/base/ir/ir_attachment.py | 11 +- flectra/addons/base/ir/ir_autovacuum.py | 6 +- flectra/addons/base/ir/ir_cron.py | 33 +- flectra/addons/base/ir/ir_cron_view.xml | 53 - flectra/addons/base/ir/ir_http.py | 7 +- flectra/addons/base/ir/ir_mail_server.py | 4 +- .../addons/base/ir/ir_mail_server_view.xml | 1 - flectra/addons/base/ir/ir_model_view.xml | 4 +- flectra/addons/base/ir/ir_qweb.xml | 4 +- flectra/addons/base/ir/ir_translation.py | 7 +- flectra/addons/base/ir/ir_ui_view.py | 123 +- flectra/addons/base/module/module.py | 2 +- flectra/addons/base/res/res.lang.csv | 1 - flectra/addons/base/res/res_company.py | 17 +- flectra/addons/base/res/res_config.py | 5 +- flectra/addons/base/res/res_country_data.xml | 4 +- flectra/addons/base/res/res_currency.py | 4 +- flectra/addons/base/res/res_currency_data.xml | 15 +- flectra/addons/base/res/res_currency_demo.xml | 6 + flectra/addons/base/res/res_currency_view.xml | 4 +- flectra/addons/base/res/res_lang_view.xml | 2 +- flectra/addons/base/res/res_partner.py | 2 +- flectra/addons/base/res/res_users.py | 40 +- .../addons/base/security/ir.model.access.csv | 201 ++- .../base/static/src/js/res_config_settings.js | 14 +- .../static/src/tests/base_setting_test.js | 2 +- flectra/addons/base/tests/test_float.py | 18 +- flectra/addons/base/tests/test_views.py | 177 +++ .../static/src/js/test_jsfile1.js | 2 +- .../static/src/js/test_jsfile2.js | 2 +- .../static/src/js/test_jsfile3.js | 2 +- .../static/src/js/test_jsfile4.js | 2 +- .../addons/test_impex/tests/test_export.py | 49 +- .../test_main_flows/static/src/js/tour.js | 18 + flectra/addons/test_new_api/models.py | 4 + .../test_new_api/static/tests/x2many.js | 2 +- .../test_new_api/tests/test_new_fields.py | 41 +- .../test_new_api/tests/test_onchange.py | 29 + .../addons/test_pylint/tests/test_pylint.py | 2 +- flectra/api.py | 13 + flectra/cli/shell.py | 1 + flectra/fields.py | 23 +- flectra/http.py | 27 +- flectra/models.py | 139 +- flectra/modules/loading.py | 7 +- flectra/modules/migration.py | 5 +- flectra/modules/registry.py | 8 + flectra/service/security.py | 13 + flectra/tests/common.py | 4 +- flectra/tools/config.py | 8 +- flectra/tools/float_utils.py | 27 +- flectra/tools/translate.py | 4 +- flectra/tools/yaml_import.py | 4 +- 631 files changed, 10472 insertions(+), 3607 deletions(-) create mode 100644 addons/auth_signup/models/ir_model_fields.py create mode 100644 addons/barcodes/models/ir_http.py create mode 100644 addons/delivery/models/res_config_settings.py create mode 100644 addons/google_drive/static/tests/gdrive_test.js create mode 100644 addons/l10n_mx/models/res_config_settings.py create mode 100644 addons/l10n_mx/views/res_config_settings_views.xml create mode 100644 addons/mail/static/src/img/smiley/smile_open_mouth.png create mode 100644 addons/mail/static/src/xml/announcement.xml create mode 100644 addons/mail/static/tests/mail_utils_tests.js create mode 100644 addons/point_of_sale/tests/test_anglo_saxon.py create mode 100644 addons/sale/models/ir_model_fields.py create mode 100644 addons/sale_payment/views/settings.xml create mode 100644 addons/sms/static/src/js/sms_widget.js create mode 100644 addons/sms/static/src/tests/sms_widget_test.js create mode 100644 addons/sms/static/src/xml/sms_widget.xml create mode 100644 addons/sms/views/templates.xml create mode 100644 addons/stock_dropshipping/tests/__init__.py create mode 100644 addons/stock_dropshipping/tests/test_stockvaluation.py delete mode 100644 addons/website_sale/controllers/portal.py create mode 100644 addons/website_sale/models/payment.py diff --git a/addons/account/models/account.py b/addons/account/models/account.py index da2896af..5ec4c3de 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -391,7 +391,7 @@ class AccountJournal(models.Model): belongs_to_company = fields.Boolean('Belong to the user\'s current company', compute="_belong_to_company", search="_search_company_journals",) # Bank journals fields - bank_account_id = fields.Many2one('res.partner.bank', string="Bank Account", ondelete='restrict', copy=False, domain="[('partner_id','=', company_id)]") + bank_account_id = fields.Many2one('res.partner.bank', string="Bank Account", ondelete='restrict', copy=False) bank_statements_source = fields.Selection([('undefined', 'Undefined Yet'),('manual', 'Record Manually')], string='Bank Feeds', default='undefined') bank_acc_number = fields.Char(related='bank_account_id.acc_number') bank_id = fields.Many2one('res.bank', related='bank_account_id.bank_id') @@ -433,7 +433,7 @@ class AccountJournal(models.Model): for journal in self: if journal.sequence_id and journal.sequence_number_next: sequence = journal.sequence_id._get_current_sequence() - sequence.number_next = journal.sequence_number_next + sequence.sudo().number_next = journal.sequence_number_next @api.multi # do not depend on 'refund_sequence_id.date_range_ids', because @@ -512,9 +512,16 @@ class AccountJournal(models.Model): @api.multi def write(self, vals): for journal in self: + company = journal.company_id if ('company_id' in vals and journal.company_id.id != vals['company_id']): if self.env['account.move'].search([('journal_id', 'in', self.ids)], limit=1): raise UserError(_('This journal already contains items, therefore you cannot modify its company.')) + company = self.env['res.company'].browse(vals['company_id']) + if self.bank_account_id.company_id and self.bank_account_id.company_id != company: + self.bank_account_id.write({ + 'company_id': company.id, + 'partner_id': company.partner_id.id, + }) if ('code' in vals and journal.code != vals['code']): if self.env['account.move'].search([('journal_id', 'in', self.ids)], limit=1): raise UserError(_('This journal already contains items, therefore you cannot modify its short name.')) @@ -528,8 +535,16 @@ class AccountJournal(models.Model): self.default_debit_account_id.currency_id = vals['currency_id'] if not 'default_credit_account_id' in vals and self.default_credit_account_id: self.default_credit_account_id.currency_id = vals['currency_id'] - if 'bank_account_id' in vals and not vals.get('bank_account_id'): - raise UserError(_('You cannot empty the bank account once set.')) + if self.bank_account_id: + self.bank_account_id.currency_id = vals['currency_id'] + if 'bank_account_id' in vals: + if not vals.get('bank_account_id'): + raise UserError(_('You cannot empty the bank account once set.')) + else: + bank_account = self.env['res.partner.bank'].browse(vals['bank_account_id']) + if bank_account.partner_id != company.partner_id: + raise UserError(_("The partners of the journal's company and the related bank account mismatch.")) + result = super(AccountJournal, self).write(vals) # Create the bank_account_id if necessary diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index acd22989..a9ab1ab1 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -341,7 +341,7 @@ class AccountBankStatement(models.Model): def link_bank_to_partner(self): for statement in self: for st_line in statement.line_ids: - if st_line.bank_account_id and st_line.partner_id and st_line.bank_account_id.partner_id != st_line.partner_id: + if st_line.bank_account_id and st_line.partner_id and not st_line.bank_account_id.partner_id: st_line.bank_account_id.partner_id = st_line.partner_id @@ -938,7 +938,7 @@ class AccountBankStatementLine(models.Model): 'currency_id': currency.id, 'amount': abs(total), 'communication': self._get_communication(payment_methods[0] if payment_methods else False), - 'name': self.statement_id.name, + 'name': self.statement_id.name or _("Bank Statement %s") % self.date, }) # Complete dicts to create both counterpart move lines and write-offs diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 5da26b99..0d146d5e 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -221,7 +221,7 @@ class AccountInvoice(models.Model): @api.depends('move_id.line_ids.amount_residual') def _compute_payments(self): payment_lines = set() - for line in self.move_id.line_ids: + for line in self.move_id.line_ids.filtered(lambda l: l.account_id.id == self.account_id.id): payment_lines.update(line.mapped('matched_credit_ids.credit_move_id.id')) payment_lines.update(line.mapped('matched_debit_ids.debit_move_id.id')) self.payment_move_line_ids = self.env['account.move.line'].browse(list(payment_lines)) @@ -347,7 +347,7 @@ class AccountInvoice(models.Model): payment_move_line_ids = fields.Many2many('account.move.line', string='Payment Move Lines', compute='_compute_payments', store=True) user_id = fields.Many2one('res.users', string='Salesperson', track_visibility='onchange', readonly=True, states={'draft': [('readonly', False)]}, - default=lambda self: self.env.user) + default=lambda self: self.env.user, copy=False) fiscal_position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position', oldname='fiscal_position', readonly=True, states={'draft': [('readonly', False)]}) commercial_partner_id = fields.Many2one('res.partner', string='Commercial Entity', compute_sudo=True, @@ -1195,25 +1195,7 @@ class AccountInvoice(models.Model): @api.model def line_get_convert(self, line, part): - return { - 'date_maturity': line.get('date_maturity', False), - 'partner_id': part, - 'name': line['name'], - 'debit': line['price'] > 0 and line['price'], - 'credit': line['price'] < 0 and -line['price'], - 'account_id': line['account_id'], - 'analytic_line_ids': line.get('analytic_line_ids', []), - 'amount_currency': line['price'] > 0 and abs(line.get('amount_currency', False)) or -abs(line.get('amount_currency', False)), - 'currency_id': line.get('currency_id', False), - 'quantity': line.get('quantity', 1.00), - 'product_id': line.get('product_id', False), - 'product_uom_id': line.get('uom_id', False), - 'analytic_account_id': line.get('account_analytic_id', False), - 'invoice_id': line.get('invoice_id', False), - 'tax_ids': line.get('tax_ids', False), - 'tax_line_id': line.get('tax_line_id', False), - 'analytic_tag_ids': line.get('analytic_tag_ids', False), - } + return self.env['product.product']._convert_prepared_anglosaxon_line(line, part) @api.multi def action_cancel(self): @@ -1343,6 +1325,7 @@ class AccountInvoice(models.Model): values['state'] = 'draft' values['number'] = False values['origin'] = invoice.number + values['payment_term_id'] = False values['refund_invoice_id'] = invoice.id if date: @@ -1735,6 +1718,7 @@ class AccountPaymentTerm(models.Model): def compute(self, value, date_ref=False): date_ref = date_ref or fields.Date.today() amount = value + sign = value < 0 and -1 or 1 result = [] if self.env.context.get('currency_id'): currency = self.env['res.currency'].browse(self.env.context['currency_id']) @@ -1742,7 +1726,7 @@ class AccountPaymentTerm(models.Model): currency = self.env.user.company_id.currency_id for line in self.line_ids: if line.value == 'fixed': - amt = currency.round(line.value_amount) + amt = sign * currency.round(line.value_amount) elif line.value == 'percent': amt = currency.round(value * (line.value_amount / 100.0)) elif line.value == 'balance': diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index ebad4b50..600c162d 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -224,7 +224,7 @@ class account_journal(models.Model): data as its first element, and the arguments dictionary to use to run it as its second. """ - return ("""SELECT state, amount_total, currency_id AS currency + return ("""SELECT state, amount_total, currency_id AS currency, type FROM account_invoice WHERE journal_id = %(journal_id)s AND state = 'open';""", {'journal_id':self.id}) @@ -234,7 +234,7 @@ class account_journal(models.Model): gather the bills in draft state data, and the arguments dictionary to use to run it as its second. """ - return ("""SELECT state, amount_total, currency_id AS currency + return ("""SELECT state, amount_total, currency_id AS currency, type FROM account_invoice WHERE journal_id = %(journal_id)s AND state = 'draft';""", {'journal_id':self.id}) @@ -247,7 +247,9 @@ class account_journal(models.Model): for result in results_dict: cur = self.env['res.currency'].browse(result.get('currency')) rslt_count += 1 - rslt_sum += cur.compute(result.get('amount_total'), target_currency) + + type_factor = result.get('type') in ('in_refund', 'out_refund') and -1 or 1 + rslt_sum += type_factor * cur.compute(result.get('amount_total'), target_currency) return (rslt_count, rslt_sum) @api.multi @@ -352,6 +354,8 @@ class account_journal(models.Model): }) [action] = self.env.ref('account.%s' % action_name).read() + if not self.env.context.get('use_domain'): + ctx['search_default_journal_id'] = self.id action['context'] = ctx action['domain'] = self._context.get('use_domain', []) account_invoice_filter = self.env.ref('account.view_account_invoice_filter', False) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index f2f618d6..6779ba18 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -107,7 +107,7 @@ class AccountMove(models.Model): default=lambda self: self.env.user.company_id) matched_percentage = fields.Float('Percentage Matched', compute='_compute_matched_percentage', digits=0, store=True, readonly=True, help="Technical field used in cash basis method") # Dummy Account field to search on account.move by account_id - dummy_account_id = fields.Many2one('account.account', related='line_ids.account_id', string='Account', store=False) + dummy_account_id = fields.Many2one('account.account', related='line_ids.account_id', string='Account', store=False, readonly=True) tax_cash_basis_rec_id = fields.Many2one( 'account.partial.reconcile', string='Tax Cash Basis Entry of', @@ -291,7 +291,7 @@ class AccountMoveLine(models.Model): if not cr.fetchone(): cr.execute('CREATE INDEX account_move_line_partner_id_ref_idx ON account_move_line (partner_id, ref)') - @api.depends('debit', 'credit', 'amount_currency', 'currency_id', 'matched_debit_ids', 'matched_credit_ids', 'matched_debit_ids.amount', 'matched_credit_ids.amount', 'account_id.currency_id', 'move_id.state') + @api.depends('debit', 'credit', 'amount_currency', 'currency_id', 'matched_debit_ids', 'matched_credit_ids', 'matched_debit_ids.amount', 'matched_credit_ids.amount', 'move_id.state') def _amount_residual(self): """ Computes the residual amount of a move line from a reconciliable account in the company currency and the line's currency. This amount will be 0 for fully reconciled lines or lines from a non-reconciliable account, the original line amount @@ -525,7 +525,7 @@ class AccountMoveLine(models.Model): raise ValidationError(_("You cannot create journal items with a secondary currency without filling both 'currency' and 'amount currency' field.")) @api.multi - @api.constrains('amount_currency') + @api.constrains('amount_currency', 'debit', 'credit') def _check_currency_amount(self): for line in self: if line.amount_currency: @@ -936,7 +936,9 @@ class AccountMoveLine(models.Model): def _get_pair_to_reconcile(self): #field is either 'amount_residual' or 'amount_residual_currency' (if the reconciled account has a secondary currency set) - field = self[0].account_id.currency_id and 'amount_residual_currency' or 'amount_residual' + company_currency_id = self[0].account_id.company_id.currency_id + account_curreny_id = self[0].account_id.currency_id + field = (account_curreny_id and company_currency_id != account_curreny_id) and 'amount_residual_currency' or 'amount_residual' #reconciliation on bank accounts are special cases as we don't want to set them as reconciliable #but we still want to reconcile entries that are reversed together in order to clear those lines #in the bank reconciliation report. @@ -952,7 +954,7 @@ class AccountMoveLine(models.Model): elif self._context.get('skip_full_reconcile_check') == 'amount_currency_only': field = 'amount_residual_currency' #target the pair of move in self that are the oldest - sorted_moves = sorted(self, key=lambda a: a.date) + sorted_moves = sorted(self, key=lambda a: a.date_maturity or a.date) debit = credit = False for aml in sorted_moves: if credit and debit: @@ -974,8 +976,9 @@ class AccountMoveLine(models.Model): #there is no more pair to reconcile so return what move_line are left if not sm_credit_move or not sm_debit_move: return self - - field = self[0].account_id.currency_id and 'amount_residual_currency' or 'amount_residual' + company_currency_id = self[0].account_id.company_id.currency_id + account_curreny_id = self[0].account_id.currency_id + field = (account_curreny_id and company_currency_id != account_curreny_id) and 'amount_residual_currency' or 'amount_residual' if not sm_debit_move.debit and not sm_debit_move.credit: #both debit and credit field are 0, consider the amount_residual_currency field because it's an exchange difference entry field = 'amount_residual_currency' @@ -1198,6 +1201,11 @@ class AccountMoveLine(models.Model): 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_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() #################################################### @@ -1361,7 +1369,7 @@ class AccountMoveLine(models.Model): record.payment_id.state = 'reconciled' result = super(AccountMoveLine, self).write(vals) - if self._context.get('check_move_validity', True): + if self._context.get('check_move_validity', True) and any(key in vals for key in ('account_id', 'journal_id', 'date', 'move_id', 'debit', 'credit')): move_ids = set() for line in self: if line.move_id.id not in move_ids: @@ -1381,7 +1389,7 @@ class AccountMoveLine(models.Model): raise UserError(_('You cannot do this modification on a reconciled entry. You can just change some non legal fields or you must unreconcile first.\n%s.') % err_msg) if line.move_id.id not in move_ids: move_ids.add(line.move_id.id) - self.env['account.move'].browse(list(move_ids))._check_lock_date() + self.env['account.move'].browse(list(move_ids))._check_lock_date() return True #################################################### diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index 989ee375..ae879cce 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -102,7 +102,7 @@ class account_abstract_payment(models.AbstractModel): class account_register_payments(models.TransientModel): _name = "account.register.payments" - _inherit = ['account.abstract.payment'] + _inherit = 'account.abstract.payment' _description = "Register payments on multiple invoices" invoice_ids = fields.Many2many('account.invoice', string='Invoices', copy=False) @@ -332,7 +332,7 @@ class account_payment(models.Model): def _compute_journal_domain_and_types(self): journal_type = ['bank', 'cash'] domain = [] - if self.currency_id.is_zero(self.amount): + if self.currency_id.is_zero(self.amount) and self.has_invoices: # In case of payment with 0 amount, allow to select a journal of type 'general' like # 'Miscellaneous Operations' and set this journal by default. journal_type = ['general'] @@ -340,7 +340,7 @@ class account_payment(models.Model): else: if self.payment_type == 'inbound': domain.append(('at_least_one_inbound', '=', True)) - else: + elif self.payment_type == 'outbound': domain.append(('at_least_one_outbound', '=', True)) return {'domain': domain, 'journal_types': set(journal_type)} @@ -349,9 +349,16 @@ class account_payment(models.Model): jrnl_filters = self._compute_journal_domain_and_types() journal_types = jrnl_filters['journal_types'] domain_on_types = [('type', 'in', list(journal_types))] - if self.journal_id.type not in journal_types: - self.journal_id = self.env['account.journal'].search(domain_on_types, limit=1) - return {'domain': {'journal_id': jrnl_filters['domain'] + domain_on_types}} + + journal_domain = jrnl_filters['domain'] + domain_on_types + default_journal_id = self.env.context.get('default_journal_id') + if not default_journal_id: + if self.journal_id.type not in journal_types: + self.journal_id = self.env['account.journal'].search(domain_on_types, limit=1) + else: + journal_domain = journal_domain.append(('id', '=', default_journal_id)) + + return {'domain': {'journal_id': journal_domain}} @api.one @api.depends('invoice_ids', 'payment_type', 'partner_type', 'partner_id') @@ -382,6 +389,8 @@ class account_payment(models.Model): self.partner_type = 'customer' elif self.payment_type == 'outbound': self.partner_type = 'supplier' + else: + self.partner_type = False # Set payment method domain res = self._onchange_journal() if not res.get('domain', {}): @@ -522,7 +531,7 @@ class account_payment(models.Model): if any(len(record.invoice_ids) != 1 for record in self): # For multiple invoices, there is account.register.payments wizard raise UserError(_("This method should only be called to process a single invoice's payment.")) - self.post(); + return self.post() def _create_payment_entry(self, amount): """ Create a journal entry corresponding to a payment, if the payment references invoice(s) they are reconciled. @@ -571,9 +580,9 @@ class account_payment(models.Model): writeoff_line['amount_currency'] = amount_currency_wo writeoff_line['currency_id'] = currency_id writeoff_line = aml_obj.create(writeoff_line) - if counterpart_aml['debit'] or writeoff_line['credit']: + if counterpart_aml['debit'] or (writeoff_line['credit'] and not counterpart_aml['credit']): counterpart_aml['debit'] += credit_wo - debit_wo - if counterpart_aml['credit'] or writeoff_line['debit']: + if counterpart_aml['credit'] or (writeoff_line['debit'] and not counterpart_aml['debit']): counterpart_aml['credit'] += debit_wo - credit_wo counterpart_aml['amount_currency'] -= amount_currency_wo diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py index f1e3bc07..94a6cfb3 100644 --- a/addons/account/models/chart_template.py +++ b/addons/account/models/chart_template.py @@ -745,6 +745,9 @@ class WizardMultiChartsAccounts(models.TransientModel): res.setdefault('domain', {}) res['domain']['sale_tax_id'] = repr(sale_tax_domain) res['domain']['purchase_tax_id'] = repr(purchase_tax_domain) + else: + self.sale_tax_id = False + self.purchase_tax_id = False if self.chart_template_id.transfer_account_id: self.transfer_account_id = self.chart_template_id.transfer_account_id.id if self.chart_template_id.code_digits: @@ -775,7 +778,7 @@ class WizardMultiChartsAccounts(models.TransientModel): if company_id: company = self.env['res.company'].browse(company_id) currency_id = company.on_change_country(company.country_id.id)['value']['currency_id'] - res.update({'currency_id': currency_id.id}) + res.update({'currency_id': currency_id}) chart_templates = account_chart_template.search([('visible', '=', True)]) if chart_templates: diff --git a/addons/account/models/company.py b/addons/account/models/company.py index 64be319d..d9b9fe62 100644 --- a/addons/account/models/company.py +++ b/addons/account/models/company.py @@ -136,6 +136,12 @@ Best Regards,''')) company.reflect_code_prefix_change(company.cash_account_code_prefix, new_cash_code, digits) if values.get('accounts_code_digits'): company.reflect_code_digits_change(digits) + + #forbid the change of currency_id if there are already some accounting entries existing + if 'currency_id' in values and values['currency_id'] != company.currency_id.id: + if self.env['account.move.line'].search([('company_id', '=', company.id)]): + raise UserError(_('You cannot change the currency of the company since some journal items already exist')) + return super(ResCompany, self).write(values) @api.model @@ -271,7 +277,7 @@ Best Regards,''')) default_journal = self.env['account.journal'].search([('type', '=', 'general'), ('company_id', '=', self.id)], limit=1) if not default_journal: - raise UserError(_("No miscellaneous journal could be found. Please create one before proceeding.")) + raise UserError(_("Please install a chart of accounts or create a miscellaneous journal before proceeding.")) self.account_opening_move_id = self.env['account.move'].create({ 'name': _('Opening Journal Entry'), diff --git a/addons/account/models/product.py b/addons/account/models/product.py index b5556558..a5a6afb5 100644 --- a/addons/account/models/product.py +++ b/addons/account/models/product.py @@ -71,3 +71,28 @@ class ProductTemplate(models.Model): if not fiscal_pos: fiscal_pos = self.env['account.fiscal.position'] return fiscal_pos.map_accounts(accounts) + +class ProductProduct(models.Model): + _inherit = "product.product" + + @api.model + def _convert_prepared_anglosaxon_line(self, line, partner): + return { + 'date_maturity': line.get('date_maturity', False), + 'partner_id': partner, + 'name': line['name'], + 'debit': line['price'] > 0 and line['price'], + 'credit': line['price'] < 0 and -line['price'], + 'account_id': line['account_id'], + 'analytic_line_ids': line.get('analytic_line_ids', []), + 'amount_currency': line['price'] > 0 and abs(line.get('amount_currency', False)) or -abs(line.get('amount_currency', False)), + 'currency_id': line.get('currency_id', False), + 'quantity': line.get('quantity', 1.00), + 'product_id': line.get('product_id', False), + 'product_uom_id': line.get('uom_id', False), + 'analytic_account_id': line.get('account_analytic_id', False), + 'invoice_id': line.get('invoice_id', False), + 'tax_ids': line.get('tax_ids', False), + 'tax_line_id': line.get('tax_line_id', False), + 'analytic_tag_ids': line.get('analytic_tag_ids', False), + } diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py index 3087c502..d7ce4aac 100644 --- a/addons/account/report/account_aged_partner_balance.py +++ b/addons/account/report/account_aged_partner_balance.py @@ -27,7 +27,7 @@ class ReportAgedPartnerBalance(models.AbstractModel): res = [] total = [] cr = self.env.cr - user_company = self.env.user.company_id.id + company_ids = self.env.context.get('company_ids', (self.env.user.company_id.id,)) move_state = ['draft', 'posted'] branch = '' if branch_id: @@ -44,7 +44,7 @@ class ReportAgedPartnerBalance(models.AbstractModel): if reconciled_after_date: reconciliation_clause = '(l.reconciled IS FALSE OR l.id IN %s)' arg_list += (tuple(reconciled_after_date),) - arg_list += (date_from, user_company) + arg_list += (date_from, tuple(company_ids)) query = ''' SELECT DISTINCT l.partner_id, UPPER(res_partner.name) FROM account_move_line AS l left join res_partner on l.partner_id = res_partner.id, account_account, account_move am @@ -54,7 +54,7 @@ class ReportAgedPartnerBalance(models.AbstractModel): AND (account_account.internal_type IN %s) AND ''' + reconciliation_clause + branch +''' AND (l.date <= %s) - AND l.company_id = %s + AND l.company_id IN %s ORDER BY UPPER(res_partner.name)''' cr.execute(query, arg_list) @@ -79,8 +79,8 @@ class ReportAgedPartnerBalance(models.AbstractModel): AND (COALESCE(l.date_maturity,l.date) > %s)\ AND ((l.partner_id IN %s) OR (l.partner_id IS NULL)) AND (l.date <= %s) ''' + branch + ''' - AND l.company_id = %s''' - cr.execute(query, (tuple(move_state), tuple(account_type), date_from, tuple(partner_ids), date_from, user_company)) + AND l.company_id IN %s''' + cr.execute(query, (tuple(move_state), tuple(account_type), date_from, tuple(partner_ids), date_from, tuple(company_ids))) aml_ids = cr.fetchall() aml_ids = aml_ids and [x[0] for x in aml_ids] or [] for line in self.env['account.move.line'].browse(aml_ids): @@ -120,7 +120,7 @@ class ReportAgedPartnerBalance(models.AbstractModel): else: dates_query += ' <= %s)' args_list += (periods[str(i)]['stop'],) - args_list += (date_from, user_company) + args_list += (date_from, tuple(company_ids)) query = '''SELECT l.id FROM account_move_line AS l, account_account, account_move am @@ -130,7 +130,7 @@ class ReportAgedPartnerBalance(models.AbstractModel): AND ((l.partner_id IN %s) OR (l.partner_id IS NULL)) AND ''' + dates_query + branch +''' AND (l.date <= %s) - AND l.company_id = %s''' + AND l.company_id IN %s''' cr.execute(query, args_list) partners_amount = {} aml_ids = cr.fetchall() @@ -193,7 +193,7 @@ class ReportAgedPartnerBalance(models.AbstractModel): values['name'] = _('Unknown Partner') values['trust'] = False - if at_least_one_amount: + if at_least_one_amount or self._context.get('include_nullified_amount'): res.append(values) return res, total, lines diff --git a/addons/account/report/account_partner_ledger.py b/addons/account/report/account_partner_ledger.py index de33ee50..514dc88e 100644 --- a/addons/account/report/account_partner_ledger.py +++ b/addons/account/report/account_partner_ledger.py @@ -14,7 +14,7 @@ class ReportPartnerLedger(models.AbstractModel): full_account = [] currency = self.env['res.currency'] query_get_data = self.env['account.move.line'].with_context(data['form'].get('used_context', {}))._query_get() - reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".reconciled = false ' + reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".full_reconcile_id IS NULL ' params = [partner.id, tuple(data['computed']['move_state']), tuple(data['computed']['account_ids'])] + query_get_data[2] query = """ SELECT "account_move_line".id, "account_move_line".date, j.code, acc.code as a_code, acc.name as a_name, "account_move_line".ref, m.name as move_name, "account_move_line".name, "account_move_line".debit, "account_move_line".credit, "account_move_line".amount_currency,"account_move_line".currency_id, c.symbol AS currency_code @@ -51,7 +51,7 @@ class ReportPartnerLedger(models.AbstractModel): return result = 0.0 query_get_data = self.env['account.move.line'].with_context(data['form'].get('used_context', {}))._query_get() - reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".reconciled = false ' + reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".full_reconcile_id IS NULL ' params = [partner.id, tuple(data['computed']['move_state']), tuple(data['computed']['account_ids'])] + query_get_data[2] query = """SELECT sum(""" + field + """) @@ -95,7 +95,7 @@ class ReportPartnerLedger(models.AbstractModel): AND NOT a.deprecated""", (tuple(data['computed']['ACCOUNT_TYPE']),)) data['computed']['account_ids'] = [a for (a,) in self.env.cr.fetchall()] params = [tuple(data['computed']['move_state']), tuple(data['computed']['account_ids'])] + query_get_data[2] - reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".reconciled = false ' + reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".full_reconcile_id IS NULL ' query = """ SELECT DISTINCT "account_move_line".partner_id FROM """ + query_get_data[0] + """, account_account AS account, account_move AS am diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 8c6595a4..c7fd2779 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -524,17 +524,37 @@ var StatementModel = BasicModel.extend({ */ togglePartialReconcile: function (handle) { var line = this.getLine(handle); - var props = _.filter(line.reconciliation_proposition, {'invalid': false}); - var prop = props[0]; - if (props.length !== 1 || Math.abs(line.st_line.amount) >= Math.abs(prop.amount)) { + + // Retrieve the toggle proposition + var selected; + _.each(line.reconciliation_proposition, function (prop) { + if (!prop.invalid) { + if (((line.balance.amount < 0 || !line.partial_reconcile) && prop.amount > 0 && line.st_line.amount > 0 && line.st_line.amount < prop.amount) || + ((line.balance.amount > 0 || !line.partial_reconcile) && prop.amount < 0 && line.st_line.amount < 0 && line.st_line.amount > prop.amount)) { + selected = prop; + return false; + } + } + }); + + // If no toggled proposition found, reject it + if (selected == null) return $.Deferred().reject(); - } - prop.partial_reconcile = !prop.partial_reconcile; - if (!prop.partial_reconcile) { + + // Inverse partial_reconcile value + selected.partial_reconcile = !selected.partial_reconcile; + if (!selected.partial_reconcile) { return this._computeLine(line); } + + // Compute the write_off + var format_options = { currency_id: line.st_line.currency_id }; + selected.write_off_amount = selected.amount + line.balance.amount; + selected.write_off_amount_str = field_utils.format.monetary(Math.abs(selected.write_off_amount), {}, format_options); + selected.write_off_amount_str = selected.write_off_amount_str.replace(' ', ' '); + return this._computeLine(line).then(function () { - if (prop.partial_reconcile) { + if (selected.partial_reconcile) { line.balance.amount = 0; line.balance.type = 1; line.mode = 'inactive'; @@ -594,7 +614,7 @@ var StatementModel = BasicModel.extend({ handles = [handle]; } else { _.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); } }); @@ -1051,7 +1071,7 @@ var StatementModel = BasicModel.extend({ // Do not forward port in master. @CSN will change this var amount = prop.computed_with_tax && -prop.base_amount || -prop.amount; if (prop.partial_reconcile === true) { - amount = -line.st_line.amount; + amount = -prop.write_off_amount; } var result = { name : prop.label, diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js index 9b21da9b..58ee890f 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js @@ -354,7 +354,21 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { // reconciliation_proposition var $props = this.$('.accounting_view tbody').empty(); - var props = _.filter(state.reconciliation_proposition, {'display': true}); + + // loop state propositions + var props = []; + var nb_debit_props = 0; + var nb_credit_props = 0; + _.each(state.reconciliation_proposition, function (prop) { + if (prop.display) { + props.push(prop); + if (prop.amount < 0) + nb_debit_props += 1; + else if (prop.amount > 0) + nb_credit_props += 1; + } + }); + _.each(props, function (line) { var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state})); if (!isNaN(line.id)) { @@ -362,18 +376,16 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { .appendTo($line.find('.cell_info_popover')) .attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line})); } - - if ((state.balance.amount_currency !== 0 || line.partial_reconcile) && props.length === 1 && - line.already_paid === false && - ( - (state.st_line.amount > 0 && state.st_line.amount < props[0].amount) || - (state.st_line.amount < 0 && state.st_line.amount > props[0].amount)) - ) { + if (line.already_paid === false && + ((state.balance.amount_currency < 0 || line.partial_reconcile) && nb_credit_props == 1 + && line.amount > 0 && state.st_line.amount > 0 && state.st_line.amount < line.amount) || + ((state.balance.amount_currency > 0 || line.partial_reconcile) && nb_debit_props == 1 + && line.amount < 0 && state.st_line.amount < 0 && state.st_line.amount > line.amount)) { var $cell = $line.find(line.amount > 0 ? '.cell_right' : '.cell_left'); var text; if (line.partial_reconcile) { text = _t("Undo the partial reconciliation."); - $cell.text(state.st_line.amount_str); + $cell.text(line.write_off_amount_str); } else { text = _t("This move's amount is higher than the transaction's amount. Click to register a partial payment and keep the payment balance open."); } @@ -415,14 +427,15 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { this._renderCreate(state); } var data = this.model.get(this.handleCreateRecord).data; - this.model.notifyChanges(this.handleCreateRecord, state.createForm); - var record = this.model.get(this.handleCreateRecord); - _.each(this.fields, function (field, fieldName) { - if (self._avoidFieldUpdate[fieldName]) return; - if (fieldName === "partner_id") return; - if ((data[fieldName] || state.createForm[fieldName]) && !_.isEqual(state.createForm[fieldName], data[fieldName])) { - field.reset(record); - } + this.model.notifyChanges(this.handleCreateRecord, state.createForm).then(function () { + var record = self.model.get(self.handleCreateRecord); + _.each(self.fields, function (field, fieldName) { + if (self._avoidFieldUpdate[fieldName]) return; + if (fieldName === "partner_id") return; + if ((data[fieldName] || state.createForm[fieldName]) && !_.isEqual(state.createForm[fieldName], data[fieldName])) { + field.reset(record); + } + }); }); } this.$('.create .add_line').toggle(!!state.balance.amount_currency); @@ -626,7 +639,8 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { return; } if(event.keyCode === 13) { - if (_.findWhere(this.model.lines, {mode: 'create'}).balance.amount) { + var created_lines = _.findWhere(this.model.lines, {mode: 'create'}); + if (created_lines && created_lines.balance.amount) { this._onCreateProposition(); } return; diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index ddf2c57d..0cbb4f9a 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -34,17 +34,22 @@ var db = { fields: { id: {string: "ID", type: 'integer'}, code: {string: "code", type: 'integer'}, - display_name: {string: "Displayed name", type: 'char'}, + name: {string: "Displayed name", type: 'char'}, }, records: [ - {id: 282, code: 100000, display_name: "100000 Fixed Asset Account"}, - {id: 283, code: 101000, display_name: "101000 Current Assets"}, - {id: 284, code: 101110, display_name: "101110 Stock Valuation Account"}, - {id: 285, code: 101120, display_name: "101120 Stock Interim Account (Received)"}, - {id: 286, code: 101130, display_name: "101130 Stock Interim Account (Delivered)"}, - {id: 287, code: 101200, display_name: "101200 Account Receivable"}, - {id: 288, code: 101300, display_name: "101300 Tax Paid"}, - {id: 308, code: 101401, display_name: "101401 Bank"}, + {id: 282, code: 100000, name: "100000 Fixed Asset Account"}, + {id: 283, code: 101000, name: "101000 Current Assets"}, + {id: 284, code: 101110, name: "101110 Stock Valuation Account"}, + {id: 285, code: 101120, name: "101120 Stock Interim Account (Received)"}, + {id: 286, code: 101130, name: "101130 Stock Interim Account (Delivered)"}, + {id: 287, code: 101200, name: "101200 Account Receivable"}, + {id: 288, code: 101300, name: "101300 Tax Paid"}, + {id: 308, code: 101401, name: "101401 Bank"}, + {id: 500, code: 500, name: "500 Account"}, + {id: 501, code: 501, name: "501 Account"}, + {id: 502, code: 502, name: "502 Account"}, + {id: 503, code: 503, name: "503 Account"}, + {id: 504, code: 504, name: "504 Account"}, ], mark_as_reconciled: function () { return $.when(); @@ -767,13 +772,7 @@ QUnit.module('account', { partner_id: false, counterpart_aml_dicts:[], payment_aml_ids: [392], - new_aml_dicts: [ - { - "credit": 343.42, - "debit": 0, - "name": "Bank fees : Open balance" - } - ], + new_aml_dicts: [], }] ], "should call process_reconciliations with partial reconcile values"); } @@ -802,14 +801,14 @@ QUnit.module('account', { assert.notOk( widget.$('.cell_left .line_info_button').length, "should not display the partial reconciliation alert"); widget.$('.accounting_view thead td:first').trigger('click'); widget.$('.match .cell_account_code:first').trigger('click'); - assert.equal( widget.$('.accounting_view tbody .cell_left .line_info_button').length, 0, "should not display the partial reconciliation alert"); + assert.equal( widget.$('.accounting_view tbody .cell_left .line_info_button').length, 1, "should display the partial reconciliation alert"); assert.ok( widget.$('button.btn-primary:not(hidden)').length, "should not display the reconcile button"); assert.ok( widget.$('.text-danger:not(hidden)').length, "should display counterpart alert"); widget.$('.accounting_view .cell_left .line_info_button').trigger('click'); - assert.strictEqual(widget.$('.accounting_view .cell_left .line_info_button').length, 0, "should not display a partial reconciliation alert"); - assert.notOk(widget.$('.accounting_view .cell_left .line_info_button').hasClass('do_partial_reconcile_false'), "should not display the partial reconciliation information"); + assert.strictEqual(widget.$('.accounting_view .cell_left .line_info_button').length, 1, "should display a partial reconciliation alert"); + assert.notOk(widget.$('.accounting_view .cell_left .line_info_button').hasClass('do_partial_reconcile_true'), "should display the partial reconciliation information"); assert.ok( widget.$('button.btn-default:not(hidden)').length, "should display the validate button"); - assert.strictEqual( widget.$el.data('mode'), "match", "should be inactive mode"); + assert.strictEqual( widget.$el.data('mode'), "inactive", "should be inactive mode"); widget.$('button.btn-default:not(hidden)').trigger('click'); clientAction.destroy(); @@ -1027,13 +1026,77 @@ QUnit.module('account', { clientAction.destroy(); }); + QUnit.test('Reconciliation create line (many2one test)', function (assert) { + assert.expect(5); + + var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); + var def = $.Deferred(); + + testUtils.addMockEnvironment(clientAction, { + data: this.params.data, + session: { + currencies: { + 3: { + digits: [69, 2], + position: "before", + symbol: "$" + } + } + }, + archs: { + "account.account,false,list": '', + "account.account,false,search": '', + }, + mockRPC: function (route, args) { + if (args.method === 'name_get') { + return def.then(this._super.bind(this, route, args)); + } + return this._super(route, args); + }, + }); + + clientAction.prependTo($('#qunit-fixture')); + + var widget = clientAction.widgets[0]; + + // open the first line in write-off mode + widget.$('.accounting_view tfoot td:first').trigger('click'); + + // select an account with the many2one (drop down) + widget.$('.create .create_account_id input').trigger('click'); + $('.ui-autocomplete .ui-menu-item a:contains(101200)').trigger('mouseenter').trigger('click'); + assert.strictEqual(widget.$('.create .create_account_id input').val(), "101200 Account Receivable", "Display the selected account"); + assert.strictEqual(widget.$('tbody:first .cell_account_code').text(), "101200", "Display the code of the selected account"); + + // use the many2one select dialog to change the account + widget.$('.create .create_account_id input').trigger('click'); + $('.ui-autocomplete .ui-menu-item a:contains(Search)').trigger('mouseenter').trigger('click'); + // select the account who does not appear in the drop drown + $('.modal tr.o_data_row:contains(502)').click(); + assert.strictEqual(widget.$('.create .create_account_id input').val(), "101200 Account Receivable", "Selected account does not change"); + // wait the name_get to render the changes + def.resolve(); + assert.strictEqual(widget.$('.create .create_account_id input').val(), "502 Account", "Display the selected account"); + assert.strictEqual(widget.$('tbody:first .cell_account_code').text(), "502", "Display the code of the selected account"); + clientAction.destroy(); + }); + QUnit.test('Reconciliation create line with taxes', function (assert) { assert.expect(13); var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); testUtils.addMockEnvironment(clientAction, { - 'data': this.params.data, + data: this.params.data, + session: { + currencies: { + 3: { + digits: [69, 2], + position: "before", + symbol: "$" + } + } + }, }); clientAction.appendTo($('#qunit-fixture')); @@ -1045,26 +1108,26 @@ QUnit.module('account', { widget.$('.create .create_label input').val('test1').trigger('input'); widget.$('.create .create_amount input').val('1100').trigger('input'); - assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text(), "1100.00", "should display the value 1100.00 in left column"); + assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text(), "$\u00a01100.00", "should display the value 1100.00 in left column"); assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "75.00", "should display 'Open Balance' with 75.00 in right column"); + assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$\u00a075.00", "should display 'Open Balance' with 75.00 in right column"); assert.strictEqual(widget.$('.accounting_view tbody tr').length, 1, "should have 1 created reconcile lines"); widget.$('.create .create_tax_id input').trigger('click'); $('.ui-autocomplete .ui-menu-item a:contains(10.00%)').trigger('mouseenter').trigger('click'); - assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "1000.00100.00", "should have 2 created reconcile lines with right column values"); + assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "$\u00a01000.00$\u00a0100.00", "should have 2 created reconcile lines with right column values"); assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "75.00", "should display 'Open Balance' with 75.00 in right column"); + assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$\u00a075.00", "should display 'Open Balance' with 75.00 in right column"); assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "", "should display 'Open Balance' without any value in left column"); assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "should have 2 created reconcile lines"); widget.$('.create .create_tax_id input').trigger('click'); $('.ui-autocomplete .ui-menu-item a:contains(20.00%)').trigger('mouseenter').trigger('click'); - assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "1100.00220.00", "should have 2 created reconcile lines with right column values"); + assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "$\u00a01100.00$\u00a0220.00", "should have 2 created reconcile lines with right column values"); assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Create Write-off", "should display 'Create Write-off'"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "145.00", "should display 'Create Write-off' with 145.00 in right column"); + assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "$\u00a0145.00", "should display 'Create Write-off' with 145.00 in right column"); assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "should have 2 created reconcile lines"); clientAction.destroy(); @@ -1076,7 +1139,7 @@ QUnit.module('account', { var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); testUtils.addMockEnvironment(clientAction, { - 'data': this.params.data, + data: this.params.data, }); clientAction.appendTo($('#qunit-fixture')); diff --git a/addons/account/tests/test_payment.py b/addons/account/tests/test_payment.py index a7c5a528..6abc23ae 100644 --- a/addons/account/tests/test_payment.py +++ b/addons/account/tests/test_payment.py @@ -17,7 +17,9 @@ class TestPayment(AccountingTestCase): self.currency_chf_id = self.env.ref("base.CHF").id self.currency_usd_id = self.env.ref("base.USD").id self.currency_eur_id = self.env.ref("base.EUR").id - self.env.ref('base.main_company').write({'currency_id': self.currency_eur_id}) + + company = self.env.ref('base.main_company') + self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.currency_eur_id, company.id]) self.product = self.env.ref("product.product_product_4") self.payment_method_manual_in = self.env.ref("account.account_payment_method_manual_in") self.payment_method_manual_out = self.env.ref("account.account_payment_method_manual_out") @@ -318,3 +320,53 @@ class TestPayment(AccountingTestCase): self.assertEqual(payment_id.payment_type, 'outbound') self.assertEqual(payment_id.partner_id, self.partner_china_exp) self.assertEqual(payment_id.partner_type, 'supplier') + + def test_payment_and_writeoff_in_other_currency(self): + # Use case: + # Company is in EUR, create a customer invoice for 25 EUR and register payment of 25 USD. + # Mark invoice as fully paid with a write_off + # Check that all the aml are correctly created. + invoice = self.create_invoice(amount=25, type='out_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id) + # register payment on invoice + payment = self.payment_model.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': 25, + 'currency_id': self.currency_usd_id, + 'payment_date': time.strftime('%Y') + '-07-15', + 'payment_difference_handling': 'reconcile', + 'writeoff_account_id': self.account_payable.id, + 'journal_id': self.bank_journal_euro.id, + 'invoice_ids': [(4, invoice.id, None)] + }) + payment.post() + self.check_journal_items(payment.move_line_ids, [ + {'account_id': self.account_eur.id, 'debit': 16.35, 'credit': 0.0, 'amount_currency': 25.0, 'currency_id': self.currency_usd_id}, + {'account_id': self.account_payable.id, 'debit': 8.65, 'credit': 0.0, 'amount_currency': 13.22, 'currency_id': self.currency_usd_id}, + {'account_id': self.account_receivable.id, 'debit': 0.0, 'credit': 25.0, 'amount_currency': -38.22, 'currency_id': self.currency_usd_id}, + ]) + # Use case: + # Company is in EUR, create a vendor bill for 25 EUR and register payment of 25 USD. + # Mark invoice as fully paid with a write_off + # Check that all the aml are correctly created. + invoice = self.create_invoice(amount=25, type='in_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id) + # register payment on invoice + payment = self.payment_model.create({'payment_type': 'inbound', + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + 'partner_type': 'supplier', + 'partner_id': self.partner_agrolait.id, + 'amount': 25, + 'currency_id': self.currency_usd_id, + 'payment_date': time.strftime('%Y') + '-07-15', + 'payment_difference_handling': 'reconcile', + 'writeoff_account_id': self.account_payable.id, + 'journal_id': self.bank_journal_euro.id, + 'invoice_ids': [(4, invoice.id, None)] + }) + payment.post() + self.check_journal_items(payment.move_line_ids, [ + {'account_id': self.account_eur.id, 'debit': 16.35, 'credit': 0.0, 'amount_currency': 25.0, 'currency_id': self.currency_usd_id}, + {'account_id': self.account_payable.id, 'debit': 0.0, 'credit': 8.65, 'amount_currency': -13.22, 'currency_id': self.currency_usd_id}, + {'account_id': self.account_receivable.id, 'debit': 0.0, 'credit': 7.7, 'amount_currency': -11.78, 'currency_id': self.currency_usd_id}, + ]) diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index 7782f885..aeacd3e6 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -25,7 +25,8 @@ class TestReconciliation(AccountingTestCase): self.currency_swiss_id = self.env.ref("base.CHF").id self.currency_usd_id = self.env.ref("base.USD").id self.currency_euro_id = self.env.ref("base.EUR").id - self.env.ref('base.main_company').write({'currency_id': self.currency_euro_id}) + company = self.env.ref('base.main_company') + self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.currency_euro_id, company.id]) self.account_rcv = partner_agrolait.property_account_receivable_id or self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1) self.account_rsa = partner_agrolait.property_account_payable_id or self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_payable').id)], limit=1) self.product = self.env.ref("product.product_product_4") @@ -620,3 +621,39 @@ class TestReconciliation(AccountingTestCase): # Checking if the direction of the move is correct 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) + + 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) diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml index e1ff2f9b..ffeaf4e7 100644 --- a/addons/account/views/account_invoice_view.xml +++ b/addons/account/views/account_invoice_view.xml @@ -251,7 +251,7 @@ - + @@ -297,7 +297,7 @@ - + @@ -396,7 +396,7 @@ - +