19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:00 +01:00
parent a1137a1456
commit e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions

View file

@ -26,37 +26,16 @@ pip install odoo-bringout-oca-ocb-hr_expense
## Dependencies
This addon depends on:
- hr_contract
- account
- web_tour
## Manifest Information
- **Name**: Expenses
- **Version**: 2.0
- **Category**: Human Resources/Expenses
- **License**: LGPL-3
- **Installable**: True
- hr
## Source
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `hr_expense`.
- Repository: https://github.com/OCA/OCB
- Branch: 19.0
- Path: addons/hr_expense
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md
This package preserves the original LGPL-3 license.

View file

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
from . import wizard

View file

@ -4,7 +4,7 @@
{
'name': 'Expenses',
'version': '2.0',
'version': '2.1',
'category': 'Human Resources/Expenses',
'sequence': 70,
'summary': 'Submit, validate and reinvoice employee expenses',
@ -26,7 +26,7 @@ The whole flow is implemented as:
This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
""",
'website': 'https://www.odoo.com/app/expenses',
'depends': ['hr_contract', 'account', 'web_tour'],
'depends': ['account', 'web_tour', 'hr'],
'data': [
'security/hr_expense_security.xml',
'security/ir.model.access.csv',
@ -37,9 +37,13 @@ This module also uses analytic accounting and is compatible with the invoice on
'data/mail_templates.xml',
'data/hr_expense_sequence.xml',
'data/hr_expense_data.xml',
'data/hr_expense_tour.xml',
'data/hr_expense_cron.xml',
'wizard/hr_expense_refuse_reason_views.xml',
'wizard/hr_expense_approve_duplicate_views.xml',
'wizard/hr_expense_split_wizard_views.xml',
'wizard/hr_expense_post_wizard_views.xml',
'views/product_product_views.xml',
'views/hr_expense_views.xml',
'views/mail_activity_views.xml',
'security/ir_rule.xml',
@ -48,7 +52,7 @@ This module also uses analytic accounting and is compatible with the invoice on
'views/account_payment_views.xml',
'views/hr_department_views.xml',
'views/res_config_settings_views.xml',
'views/account_journal_dashboard.xml',
'views/hr_employee_views.xml',
],
'demo': ['data/hr_expense_demo.xml'],
'installable': True,
@ -61,15 +65,17 @@ This module also uses analytic accounting and is compatible with the invoice on
'hr_expense/static/src/views/*.js',
'hr_expense/static/src/views/*.xml',
'hr_expense/static/src/scss/hr_expense.scss',
'hr_expense/static/src/xml/**/*',
'hr_expense/static/src/js/tours/*.js',
'hr_expense/static/src/js/web/*.js',
],
'web.assets_tests': [
'hr_expense/static/tests/tours/expense_upload_tours.js',
'hr_expense/static/tests/tours/expense_form_tours.js',
],
'web.qunit_mobile_suite_tests': [
'hr_expense/static/tests/expense_mobile_tests.js',
'web.report_assets_common': [
'hr_expense/static/src/scss/hr_expense.scss',
],
},
'author': 'Odoo S.A.',
'license': 'LGPL-3',
}

View file

@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import webmanifest

View file

@ -0,0 +1,22 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.web.controllers import webmanifest
class WebManifest(webmanifest.WebManifest):
def _get_webmanifest(self):
manifest = super()._get_webmanifest()
if not manifest.get('share_target'):
manifest['share_target'] = {
'action': '/odoo?share_target=trigger',
'method': 'POST',
'enctype': 'multipart/form-data',
'params': {
'files': [{
'name': 'externalMedia',
'accept': ['image/*', 'application/pdf'],
}]
}
}
return manifest

View file

@ -9,7 +9,7 @@
<div>
<p class="tip_title">Tip: Snap pictures of your receipts with the remote app</p>
<p class="tip_content">Do not keep your expense tickets in your pockets any longer. Just snap a picture of your receipt and let Odoo digitalizes it for you. The OCR and Artificial Intelligence will fill the data automatically.</p>
<img src="https://download.odoocdn.com/digests/hr_expense/static/src/img/expense.png" class="illustration_border" />
<img src="https://download.odoocdn.com/digests/hr_expense/static/src/img/milk-expense.png" width="540" class="illustration_border" />
</div>
</field>
</record>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="ir_cron_send_submitted_expenses_mail" model="ir.cron">
<field name="name">HR Expense: Send Submitted Expenses Mail</field>
<field name="model_id" ref="model_hr_expense"/>
<field name="state">code</field>
<field name="code">model._cron_send_submitted_expenses_mail()</field>
<field name="interval_number">1</field>
<field name="interval_type">weeks</field>
</record>
</data>
</odoo>

View file

@ -8,11 +8,11 @@
<field name="standard_price">0.0</field>
<field name="type">service</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="default_code">FOOD</field>
<field name="can_be_expensed" eval="True"/>
<field name="sale_ok" eval="False"/>
<field name="purchase_ok" eval="False"/>
<field name="purchase_ok" eval="True"/>
<field name="categ_id" eval="ref('product.product_category_expenses', raise_if_not_found=False)"/>
<field name="image_1920" type="base64" file="hr_expense/static/img/food.svg"/>
</record>
<record id="expense_product_travel_accommodation" model="product.product">
@ -21,23 +21,27 @@
<field name="standard_price">0.0</field>
<field name="type">service</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="default_code">TRANS &amp; ACC</field>
<field name="can_be_expensed" eval="True"/>
<field name="purchase_ok" eval="True"/>
<field name="sale_ok" eval="False"/>
<field name="purchase_ok" eval="False"/>
<field name="purchase_ok" eval="True"/>
<field name="categ_id" eval="ref('product.product_category_expenses', raise_if_not_found=False)"/>
<field name="image_1920" type="base64" file="hr_expense/static/img/transport.svg"/>
</record>
<record id="uom.product_uom_km" model="uom.uom">
<field name="active" eval="True"/>
</record>
<record id="expense_product_mileage" model="product.product">
<field name="name">Mileage</field>
<field name="standard_price">1.0</field>
<field name="type">service</field>
<field name="uom_id" ref="uom.product_uom_km"/>
<field name="uom_po_id" ref="uom.product_uom_km"/>
<field name="default_code">MIL</field>
<field name="can_be_expensed" eval="True"/>
<field name="sale_ok" eval="False"/>
<field name="purchase_ok" eval="False"/>
<field name="purchase_ok" eval="True"/>
<field name="categ_id" eval="ref('product.product_category_expenses', raise_if_not_found=False)"/>
<field name="image_1920" type="base64" file="hr_expense/static/img/mileage.svg"/>
</record>
<record id="expense_product_gift" model="product.product">
@ -45,12 +49,12 @@
<field name="description">Gifts to customers or vendors</field>
<field name="standard_price">0.0</field>
<field name="type">service</field>
<field name="uom_id" ref="uom.product_uom_km"/>
<field name="uom_po_id" ref="uom.product_uom_km"/>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="default_code">GIFT</field>
<field name="can_be_expensed" eval="True"/>
<field name="sale_ok" eval="False"/>
<field name="purchase_ok" eval="False"/>
<field name="purchase_ok" eval="True"/>
<field name="categ_id" eval="ref('product.product_category_expenses', raise_if_not_found=False)"/>
<field name="image_1920" type="base64" file="hr_expense/static/img/gift.svg"/>
</record>
<record id="expense_product_communication" model="product.product">
@ -58,21 +62,22 @@
<field name="description">Phone bills, postage, etc.</field>
<field name="standard_price">0.0</field>
<field name="type">service</field>
<field name="uom_id" ref="uom.product_uom_km"/>
<field name="uom_po_id" ref="uom.product_uom_km"/>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="default_code">COMM</field>
<field name="can_be_expensed" eval="True"/>
<field name="sale_ok" eval="False"/>
<field name="purchase_ok" eval="False"/>
<field name="purchase_ok" eval="True"/>
<field name="categ_id" eval="ref('product.product_category_expenses', raise_if_not_found=False)"/>
<field name="image_1920" type="base64" file="hr_expense/static/img/communication.svg"/>
</record>
<record id="product_product_no_cost" model="product.product">
<field name="name">Others</field>
<field name="name">Expenses</field>
<field name="standard_price">0.0</field>
<field name="type">service</field>
<field name="default_code">EXP_GEN</field>
<field name="categ_id" ref="product.cat_expense"/>
<field name="categ_id" eval="ref('product.product_category_expenses', raise_if_not_found=False)"/>
<field name="can_be_expensed" eval="True"/>
<field name="purchase_ok" eval="True"/>
<field name="image_1920" type="base64" file="hr_expense/static/img/other.svg"/>
</record>
</data>

View file

@ -1,9 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="base.user_demo" model="res.users">
<field name="group_ids" eval="[
(3, ref('hr_expense.group_hr_expense_team_approver')),
(3, ref('hr_expense.group_hr_expense_user')),
(3, ref('hr_expense.group_hr_expense_manager')),
]"/>
</record>
<record id="base.default_user_group" model="res.groups">
<field name="implied_ids" eval="[(4, ref('hr_expense.group_hr_expense_manager'))]"/>
</record>
<record id="hr.employee_mit" model="hr.employee">
<field name="address_home_id" ref="base.res_partner_address_3"/>
<field name="private_email">douglas.fletcher51@example.com</field>
<field name="private_phone">(132)-553-7242</field>
</record>
<record id="hr_expense_account_journal" model="account.journal">
@ -15,32 +27,30 @@
<field name="alias_name">purchase_expense</field>
</record>
<record id="res_partner_address_fp" model="res.partner">
<field name="name">Pieter Parter's Farm</field>
<field name="parent_id" ref="base.partner_root"/>
<field name="street">Chaussée de Namur, 40</field>
<field name="zip">1367</field>
<field name="city">Grand-Rosière-Hottomont</field>
<field name="country_id" ref="base.be"/>
<field name="type">private</field>
</record>
<record id="hr.employee_fme" model="hr.employee">
<field name="address_home_id" ref="res_partner_address_fp"/>
<field name="private_street">Chaussée de Namur, 40</field>
<field name="private_zip">1367</field>
<field name="private_city">Grand-Rosière-Hottomont</field>
<field name="private_country_id" ref="base.be"/>
</record>
<record id="hr.employee_al" model="hr.employee">
<field name="address_home_id" ref="res_partner_address_fp"/>
<field name="private_street">Chaussée de Namur, 40</field>
<field name="private_zip">1367</field>
<field name="private_city">Grand-Rosière-Hottomont</field>
<field name="private_country_id" ref="base.be"/>
</record>
<!-- ++++++++++++++ Expense sheet for Admin ++++++++++++++-->
<!-- ++++++++++++++ Expense for Admin ++++++++++++++-->
<record id="screen_expense" model="hr.expense">
<field name="name">Screen</field>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="analytic_distribution" eval="{ref('analytic.analytic_our_super_product'): 100}"/>
<field name="product_id" ref="product_product_no_cost"/>
<field eval="289.0" name="total_amount"/>
<field eval="289.0" name="total_amount_currency"/>
<field name="date" eval="time.strftime('%Y')+'-04-03'"/>
<field name="payment_mode">company_account</field>
<field name="approval_state">submitted</field>
</record>
<record id="laptop_expense" model="hr.expense">
@ -48,7 +58,7 @@
<field name="employee_id" ref="hr.employee_admin"/>
<field name="analytic_distribution" eval="{ref('analytic.analytic_our_super_product'): 100}"/>
<field name="product_id" ref="product_product_no_cost"/>
<field eval="889.0" name="total_amount"/>
<field eval="889.0" name="total_amount_currency"/>
<field name="date" eval="time.strftime('%Y')+'-04-03'"/>
</record>
@ -56,23 +66,16 @@
<field name="name">Travel by car</field>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="product_id" ref="expense_product_mileage"/>
<field eval="108.84" name="total_amount"/>
<field name="product_uom_id" ref="uom.product_uom_km"/>
<field eval="320.0" name="quantity"/>
<field eval="108.84" name="quantity"/>
<field name="approval_state">submitted</field>
</record>
<record id="breakfast_admin_expense" model="hr.expense">
<field name="name">BreakFast</field>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="product_id" ref="expense_product_meal"/>
<field eval="20" name="total_amount"/>
<field eval="1.0" name="quantity"/>
</record>
<record id="travel_ny_sheet" model="hr.expense.sheet">
<field name="name">Commercial Travel at New York</field>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="state">approve</field>
<field eval="20" name="total_amount_currency"/>
</record>
<record id="travel_by_air_expense" model="hr.expense">
@ -80,11 +83,11 @@
<field name="employee_id" ref="hr.employee_admin"/>
<field name="analytic_distribution" eval="{ref('analytic.analytic_our_super_product'): 100}"/>
<field name="product_id" ref="expense_product_travel_accommodation"/>
<field eval="700.0" name="total_amount"/>
<field eval="700.0" name="total_amount_currency"/>
<field name="product_uom_id" ref="uom.product_uom_unit"/>
<field eval="1.0" name="quantity"/>
<field name="date" eval="time.strftime('%Y-%m')+'-12'"/>
<field name="sheet_id" ref="travel_ny_sheet"/>
<field name="approval_state">submitted</field>
<field name="payment_mode">company_account</field>
</record>
<record id="hotel_bill_expense" model="hr.expense">
@ -92,11 +95,10 @@
<field name="employee_id" ref="hr.employee_admin"/>
<field name="analytic_distribution" eval="{ref('analytic.analytic_nebula'): 100}"/>
<field name="product_id" ref="expense_product_travel_accommodation"/>
<field eval="2000.0" name="total_amount"/>
<field eval="2000.0" name="total_amount_currency"/>
<field name="product_uom_id" ref="uom.product_uom_unit"/>
<field eval="5.0" name="quantity"/>
<field name="date" eval="time.strftime('%Y-%m')+'-17'"/>
<field name="sheet_id" ref="travel_ny_sheet"/>
<field name="approval_state">submitted</field>
</record>
<record id="lunch_customer_bill_expense" model="hr.expense">
@ -104,10 +106,8 @@
<field name="employee_id" ref="hr.employee_admin"/>
<field name="analytic_distribution" eval="{ref('analytic.analytic_nebula'): 100}"/>
<field name="product_id" ref="expense_product_meal"/>
<field eval="152.8" name="total_amount"/>
<field eval="152.8" name="total_amount_currency"/>
<field name="date" eval="time.strftime('%Y-%m')+'-13'"/>
<field eval="1.0" name="quantity"/>
<field name="sheet_id" ref="travel_ny_sheet"/>
</record>
<record id="lunch_bill_expense" model="hr.expense">
@ -116,28 +116,18 @@
<field name="analytic_distribution" eval="{ref('analytic.analytic_nebula'): 100}"/>
<field name="product_id" ref="expense_product_meal"/>
<field name="date" eval="time.strftime('%Y-%m')+'-15'"/>
<field eval="56.8" name="total_amount"/>
<field eval="1.0" name="quantity"/>
<field name="sheet_id" ref="travel_ny_sheet"/>
</record>
<!-- ++++++++++++++ Expense sheet for Demo ++++++++++++++-->
<record id="customer_meeting_sheet" model="hr.expense.sheet">
<field name="name">Customer meeting</field>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="state">submit</field>
<field eval="56.8" name="total_amount_currency"/>
</record>
<!-- ++++++++++++++ Expense for Demo ++++++++++++++-->
<record id="travel_demo_by_car_expense" model="hr.expense">
<field name="name">Travel by Car</field>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="analytic_distribution" eval="{ref('analytic.analytic_our_super_product'): 100}"/>
<field name="product_id" ref="expense_product_mileage"/>
<field eval="120.85" name="total_amount"/>
<field name="product_uom_id" ref="uom.product_uom_km"/>
<field name="date" eval="time.strftime('%Y')+'-01-15'"/>
<field eval="152.0" name="quantity"/>
<field name="sheet_id" ref="customer_meeting_sheet"/>
<field eval="120.85" name="quantity"/>
</record>
<record id="lunch_demo_customer_bill_expense" model="hr.expense">
@ -146,26 +136,18 @@
<field name="analytic_distribution" eval="{ref('analytic.analytic_nebula'): 100}"/>
<field name="product_id" ref="product_product_no_cost"/>
<field name="date" eval="time.strftime('%Y')+'-01-15'"/>
<field eval="152.8" name="total_amount"/>
<field name="sheet_id" ref="customer_meeting_sheet"/>
</record>
<!-- ++++++++++++++ Expense sheet for Keith Byrd ++++++++++++++-->
<record id="team_building_sheet" model="hr.expense.sheet">
<field name="name">Team Building</field>
<field name="employee_id" ref="hr.employee_fme"/>
<field name="state">submit</field>
<field eval="152.8" name="total_amount_currency"/>
<field name="approval_state">approved</field>
</record>
<!-- ++++++++++++++ Expense for Keith Byrd ++++++++++++++-->
<record id="pizzas_bill_expense" model="hr.expense">
<field name="name">Pizzas</field>
<field name="employee_id" ref="hr.employee_fme"/>
<field name="analytic_distribution" eval="{ref('analytic.analytic_nebula'): 100}"/>
<field name="product_id" ref="expense_product_meal"/>
<field name="date" eval="time.strftime('%Y-%m')+'-05'"/>
<field eval="154" name="total_amount"/>
<field eval="12.0" name="quantity"/>
<field name="sheet_id" ref="team_building_sheet"/>
<field eval="154" name="total_amount_currency"/>
</record>
<record id="drinks_bill_expense" model="hr.expense">
@ -174,9 +156,7 @@
<field name="analytic_distribution" eval="{ref('analytic.analytic_nebula'): 100}"/>
<field name="product_id" ref="expense_product_meal"/>
<field name="date" eval="time.strftime('%Y-%m')+'-05'"/>
<field eval="42.5" name="total_amount"/>
<field eval="17.0" name="quantity"/>
<field name="sheet_id" ref="team_building_sheet"/>
<field eval="42.5" name="total_amount_currency"/>
</record>
<record id="paintball_bill_expense" model="hr.expense">
@ -185,16 +165,11 @@
<field name="analytic_distribution" eval="{ref('analytic.analytic_nebula'): 100}"/>
<field name="product_id" ref="product_product_no_cost"/>
<field name="date" eval="time.strftime('%Y-%m')+'-05'"/>
<field eval="25" name="total_amount"/>
<field name="sheet_id" ref="team_building_sheet"/>
<field eval="25" name="total_amount_currency"/>
<field name="approval_state">approved</field>
</record>
<!-- ++++++++++++++ Expense sheet for Ronnie Hart ++++++++++++++-->
<record id="office_furniture_sheet" model="hr.expense.sheet">
<field name="name">Office furniture</field>
<field name="employee_id" ref="hr.employee_al"/>
<field name="state">submit</field>
</record>
<record id="chair_bill_expense" model="hr.expense">
<field name="name">Chairs</field>
@ -202,8 +177,7 @@
<field name="analytic_distribution" eval="{ref('analytic.analytic_nebula'): 100}"/>
<field name="product_id" ref="product_product_no_cost"/>
<field name="date" eval="time.strftime('%Y')+'-06-02'"/>
<field eval="55.75" name="total_amount"/>
<field name="sheet_id" ref="office_furniture_sheet"/>
<field eval="55.75" name="total_amount_currency"/>
</record>
<record id="lamp_bill_expense" model="hr.expense">
@ -212,8 +186,8 @@
<field name="analytic_distribution" eval="{ref('analytic.analytic_nebula'): 100}"/>
<field name="product_id" ref="product_product_no_cost"/>
<field name="date" eval="time.strftime('%Y')+'-06-02'"/>
<field eval="28.99" name="total_amount"/>
<field name="sheet_id" ref="office_furniture_sheet"/>
<field eval="28.99" name="total_amount_currency"/>
<field name="approval_state">refused</field>
</record>
<!-- ++++++++++++++ Expense for Randall Lewis ++++++++++++++-->
@ -223,8 +197,8 @@
<field name="analytic_distribution" eval="{ref('analytic.analytic_nebula'): 100}"/>
<field name="product_id" ref="product_product_no_cost"/>
<field name="date" eval="time.strftime('%Y')+'-03-15'"/>
<field eval="450.58" name="total_amount"/>
<field eval="450.58" name="total_amount_currency"/>
<field name="payment_mode">company_account</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_expense_tour" model="web_tour.tour">
<field name="name">hr_expense_tour</field>
<field name="rainbow_man_message">There you go - expense management in a nutshell!</field>
</record>
</odoo>

View file

@ -3,8 +3,9 @@
<data noupdate="1">
<record id="mail_act_expense_approval" model="mail.activity.type">
<field name="name">Expense Approval</field>
<field name="summary">Expense Approval</field>
<field name="icon">fa-dollar</field>
<field name="res_model">hr.expense.sheet</field>
<field name="res_model">hr.expense</field>
</record>
</data>
</odoo>

View file

@ -5,7 +5,6 @@
<record id="mail_alias_expense" model="mail.alias">
<field name="alias_name">expense</field>
<field name="alias_model_id" ref="model_hr_expense"/>
<field name="alias_user_id" ref="base.user_admin"/>
<field name="alias_contact">employees</field>
</record>
</data>

View file

@ -4,21 +4,44 @@
<!-- Expense-related subtypes for messaging / Chatter -->
<record id="mt_expense_approved" model="mail.message.subtype">
<field name="name">Approved</field>
<field name="res_model">hr.expense.sheet</field>
<field name="default" eval="True"/>
<field name="description">Expense report approved</field>
<field name="res_model">hr.expense</field>
<field name="default" eval="False"/>
<field name="description">Expense approved</field>
</record>
<record id="mt_expense_refused" model="mail.message.subtype">
<field name="name">Refused</field>
<field name="res_model">hr.expense.sheet</field>
<field name="default" eval="True"/>
<field name="description">Expense report refused</field>
<field name="res_model">hr.expense</field>
<field name="default" eval="False"/>
<field name="description">Expense refused</field>
</record>
<record id="mt_expense_paid" model="mail.message.subtype">
<field name="name">Paid</field>
<field name="res_model">hr.expense.sheet</field>
<field name="description">Expense report paid</field>
<field name="default" eval="True"/>
<field name="res_model">hr.expense</field>
<field name="description">Expense paid</field>
<field name="default" eval="False"/>
</record>
<record id="mt_expense_reset" model="mail.message.subtype">
<field name="name">Draft</field>
<field name="res_model">hr.expense</field>
<field name="default" eval="False"/>
<field name="description">Expense reset to Draft</field>
</record>
<record id="mt_expense_entry_delete" model="mail.message.subtype">
<field name="name">Journal Entry Deleted</field>
<field name="res_model">hr.expense</field>
<field name="default" eval="False"/>
<field name="description">Journal entry deleted</field>
</record>
<record id="mt_expense_entry_draft" model="mail.message.subtype">
<field name="name">Journal Entry Reset to Draft</field>
<field name="res_model">hr.expense</field>
<field name="default" eval="False"/>
<field name="description">Journal entry reset to draft</field>
</record>
</data>
</odoo>

View file

@ -2,14 +2,14 @@
<odoo>
<data noupdate="1">
<template id="hr_expense_template_refuse_reason">
<p>Your Expense <t t-if="is_sheet">Report </t><t t-esc="name"/> has been refused</p>
<p>Your Expense <t t-out="name"/> has been refused</p>
<ul class="o_timeline_tracking_value_list">
<li>Reason : <t t-esc="reason"/></li>
<li>Reason: <t t-out="reason"/></li>
</ul>
</template>
<template id="hr_expense_template_register">
<p>Dear <t t-esc="expense.employee_id.name"/>,</p>
<p>Dear <t t-out="expense.employee_id.name"/>,</p>
<p>
Your expense has been successfully registered.
<t t-if="expense.employee_id.user_id">
@ -17,27 +17,61 @@
</t>
</p>
<p t-if="expense.product_id">
Category: <t t-esc="expense.product_id.name"/>
Category: <t t-out="expense.product_id.name"/>
</p>
<div t-else="">
<p>Category: not found</p>
<p>The first word of the email subject did not correspond to any category code. You'll have to set the category manually on the expense.</p>
</div>
<p>
Price: <t t-esc="expense.unit_amount"/><t t-esc="expense.currency_id.symbol"/>
Price: <t t-out="expense.price_unit"/><t t-out="expense.currency_id.symbol"/>
</p>
<p t-if="expense.employee_id.user_id">
<br/>
<a t-att-href="'/web#id=%s&amp;model=hr.expense&amp;view_type=form' % (expense.id)" style="background-color: #9E588B; margin-top: 10px; padding: 10px; text-decoration: none; color: #fff; border-radius: 5px; font-size: 16px;">View Expense</a>
<a t-att-href="'/odoo/hr.expense/%s' % (expense.id)" class="o_expense_button_mail">View Expense</a>
</p>
</template>
<template id="hr_expense_template_submitted_expenses">
<div style="background:#F0F0F0;color:#515166;padding:10px 0px;font-family:Arial,Helvetica,sans-serif;font-size:14px;">
<table style="width:600px;margin:5px auto;background:white;border:1px solid #e1e1e1;">
<tbody><tr>
<td style="padding:15px 20px 10px 20px;">
<p style="font-size:20px;font-weight:bold;color:#515166;">Expenses approval</p>
<hr style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
<p>Dear <t t-out="manager_name"/>,</p>
<p>New expenses are waiting for your approval. You can Review them by following this link.</p>
<br/>
<a t-att-href="url" style="padding: 8px 12px; font-size: 12px; color: #FFFFFF; text-decoration: none !important; font-weight: 400; background-color: #875A7B; border: 0px solid #875A7B; border-radius:3px">View expenses</a>
<br/><br/>
<hr style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
<table style="width: 100%;">
<tbody><tr>
<td align="center" style="min-width: 590px; padding: 0 8px 0 8px; font-size:11px;">
<b t-out="company.name"/><br/>
<div style="color: #999999;">
<t t-out="company.phone"/>
<t t-if="company.email"> | <a t-att-href="'mailto:%s' % company.email" style="text-decoration:none; color: #999999;"><t t-out="company.email"/></a></t>
<t t-if="company.website"> | <a t-att-href="company.website" style="text-decoration:none; color: #999999;"><t t-out="company.website"/></a></t>
</div>
</td>
</tr></tbody>
</table>
</td>
</tr></tbody>
</table>
<p style="text-align: center;font-size: 13px;">
Powered by <a target="_blank" href="https://www.odoo.com?utm_source=db&amp;utm_medium=expense" style="color: #9E588B;">Odoo</a>.
</p>
</div>
</template>
<template id="hr_expense_template_register_no_user">
<div style="background:#F0F0F0;color:#515166;padding:10px 0px;font-family:Arial,Helvetica,sans-serif;font-size:14px;">
<table style="width:600px;margin:5px auto;">
<table style="width:600px;margin:5px auto;" t-if="not expense.employee_id.company_id.uses_default_logo">
<tbody>
<tr>
<td><a href="/"><img src="/web/binary/company_logo" style="vertical-align:baseline;max-width:100px;" /></a></td>
<td><a href="/"><img t-attf-src="/logo.png?company={{ expense.employee_id.company_id.id }}" style="vertical-align:baseline;max-width:100px;max-height:50px;" /></a></td>
</tr>
</tbody>
</table>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,9 @@
# Martin Trigaux <mat@odoo.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~15.2\n"
"Project-Id-Version: Odoo Server 11.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-26 14:21+0000\n"
"POT-Creation-Date: 2024-02-07 10:22+0000\n"
"PO-Revision-Date: 2017-11-16 08:08+0000\n"
"Last-Translator: Martin Trigaux <mat@odoo.com>, 2017\n"
"Language-Team: Spanish (Chile) (https://www.transifex.com/odoo/teams/41243/"
@ -101,16 +101,6 @@ msgstr "Agrupar por"
msgid "ID"
msgstr "ID (identificación)"
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense____last_update
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_approve_duplicate____last_update
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_refuse_wizard____last_update
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_sheet____last_update
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_split____last_update
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_split_wizard____last_update
msgid "Last Modified on"
msgstr "Última modificación en"
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense__write_uid
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_approve_duplicate__write_uid
@ -131,24 +121,15 @@ msgstr "Última actualización de"
msgid "Last Updated on"
msgstr "Última actualización en"
#. module: hr_expense
#: model_terms:ir.ui.view,arch_db:hr_expense.report_expense_sheet
msgid "Price"
msgstr "Precio"
#. module: hr_expense
#: model:ir.model,name:hr_expense.model_product_template
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_split__product_id
msgid "Product"
msgstr "Producto"
#. module: hr_expense
#: model_terms:ir.ui.view,arch_db:hr_expense.report_expense_sheet
msgid "Qty"
msgstr "Ctdad"
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense__quantity
#: model_terms:ir.ui.view,arch_db:hr_expense.report_expense_sheet
msgid "Quantity"
msgstr "Cantidad"
@ -166,8 +147,14 @@ msgid "Status"
msgstr "Estado"
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_sheet__total_amount_taxes
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_split_wizard__total_amount_taxes
#: model_terms:ir.ui.view,arch_db:hr_expense.report_expense_sheet
#: model_terms:ir.ui.view,arch_db:hr_expense.view_hr_expense_sheet_form
msgid "Subtotal"
msgstr "Subtotal"
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_sheet__total_tax_amount
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_split_wizard__tax_amount_currency
#: model_terms:ir.ui.view,arch_db:hr_expense.hr_expense_split
#: model_terms:ir.ui.view,arch_db:hr_expense.report_expense_sheet
#: model_terms:ir.ui.view,arch_db:hr_expense.view_hr_expense_sheet_form
@ -175,23 +162,20 @@ msgid "Taxes"
msgstr "Impuestos"
#. module: hr_expense
#. odoo-python
#: code:addons/hr_expense/models/hr_expense.py:0
#, python-format
msgid ""
"The private address of the employee is required to post the expense report. "
"Please add it on the employee form."
msgstr ""
"Se requiere la dirección privada del empleado para publicar el informe de "
"gastos. Por favor, agréguelo en el formulario de empleado."
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense__total_amount
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_sheet__total_amount
#: model_terms:ir.ui.view,arch_db:hr_expense.hr_expense_view_form
#: model_terms:ir.ui.view,arch_db:hr_expense.report_expense_sheet
msgid "Total"
msgstr "Total"
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense__untaxed_amount
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense__untaxed_amount_currency
msgid "Total Untaxed Amount In Currency"
msgstr "Total neto en la divisa"
msgstr "Monto neto en la divisa"
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense__unit_amount
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense__price_unit
#: model_terms:ir.ui.view,arch_db:hr_expense.hr_expense_view_expenses_analysis_tree
#: model_terms:ir.ui.view,arch_db:hr_expense.report_expense_sheet
msgid "Unit Price"
@ -204,5 +188,6 @@ msgstr "Unidad de medida"
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_hr_expense_sheet__untaxed_amount
#: model_terms:ir.ui.view,arch_db:hr_expense.report_expense_sheet
msgid "Untaxed Amount"
msgstr "Total neto"
msgstr "Monto neto"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,27 @@
from odoo import api, SUPERUSER_ID
def migrate(cr, version):
env = api.Environment(cr, SUPERUSER_ID, {})
xml_ids = [
'hr_expense.mt_expense_approved',
'hr_expense.mt_expense_refused',
'hr_expense.mt_expense_paid',
'hr_expense.mt_expense_reset',
'hr_expense.mt_expense_entry_delete',
'hr_expense.mt_expense_entry_draft',
]
subtype_ids = []
for xml_id in xml_ids:
record = env.ref(xml_id, raise_if_not_found=False)
if record:
subtype_ids.append(record.id)
if subtype_ids:
cr.execute("""
UPDATE mail_message_subtype
SET "default" = false
WHERE id in %s
""",
(tuple(subtype_ids),))

View file

@ -1,15 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import hr_employee
from . import hr_employee_public
from . import account_move
from . import account_move_line
from . import account_payment
from . import account_tax
from . import hr_department
from . import hr_expense
from . import ir_attachment
from . import product_product
from . import product_template
from . import res_config_settings
from . import account_journal_dashboard
from . import res_company
from . import analytic
from . import ir_actions_report

View file

@ -1,77 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
from odoo.tools.misc import formatLang
from odoo.addons.account.models.account_journal_dashboard import group_by_journal
class AccountJournal(models.Model):
_inherit = "account.journal"
def _get_expenses_to_pay_query(self):
"""
Returns a tuple containing as it's first element the SQL query used to
gather the expenses in reported state data, and the arguments
dictionary to use to run it as it's second.
"""
query = """SELECT total_amount as amount_total, currency_id AS currency
FROM hr_expense_sheet
WHERE state IN ('approve', 'post')
and journal_id = %(journal_id)s"""
return (query, {'journal_id': self.id})
def get_journal_dashboard_datas(self):
res = super(AccountJournal, self).get_journal_dashboard_datas()
#add the number and sum of expenses to pay to the json defining the accounting dashboard data
(query, query_args) = self._get_expenses_to_pay_query()
self.env.cr.execute(query, query_args)
query_results_to_pay = self.env.cr.dictfetchall()
(number_to_pay, sum_to_pay) = self._count_results_and_sum_amounts(query_results_to_pay, self.company_id.currency_id)
res['number_expenses_to_pay'] = number_to_pay
res['sum_expenses_to_pay'] = formatLang(self.env, sum_to_pay or 0.0, currency_obj=self.currency_id or self.company_id.currency_id)
return res
def _prepare_expense_sheet_data_domain(self):
return [
('state', '=', 'post'),
('journal_id', 'in', self.ids),
]
def _get_expense_to_pay_query(self):
return self.env['hr.expense.sheet']._where_calc(self._prepare_expense_sheet_data_domain())
def _fill_sale_purchase_dashboard_data(self, dashboard_data):
super(AccountJournal, self)._fill_sale_purchase_dashboard_data(dashboard_data)
sale_purchase_journals = self.filtered(lambda journal: journal.type in ('sale', 'purchase'))
if not sale_purchase_journals:
return
field_list = [
"hr_expense_sheet.journal_id",
"hr_expense_sheet.total_amount AS amount_total",
"hr_expense_sheet.currency_id AS currency",
]
query, params = sale_purchase_journals._get_expense_to_pay_query().select(*field_list)
self.env.cr.execute(query, params)
query_results_to_pay = group_by_journal(self.env.cr.dictfetchall())
curr_cache = {}
for journal in sale_purchase_journals:
currency = journal.currency_id or journal.company_id.currency_id
(number_expenses_to_pay, sum_expenses_to_pay) = self._count_results_and_sum_amounts(query_results_to_pay[journal.id], currency, curr_cache=curr_cache)
dashboard_data[journal.id].update({
'number_expenses_to_pay': number_expenses_to_pay,
'sum_expenses_to_pay': currency.format(sum_expenses_to_pay),
})
def open_expenses_action(self):
action = self.env['ir.actions.act_window']._for_xml_id('hr_expense.action_hr_expense_sheet_all_all')
action['context'] = {
'search_default_approved': 1,
'search_default_to_post': 1,
'search_default_journal_id': self.id,
'default_journal_id': self.id,
}
action['view_mode'] = 'tree,form'
action['views'] = [(k,v) for k,v in action['views'] if v in ['tree', 'form']]
action['domain'] = self._prepare_expense_sheet_data_domain()
return action

View file

@ -1,101 +1,112 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from markupsafe import Markup
from odoo import models, fields, api, _
from odoo import Command, models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.tools.misc import frozendict
class AccountMove(models.Model):
_inherit = "account.move"
expense_sheet_id = fields.One2many('hr.expense.sheet', 'account_move_id')
expense_ids = fields.One2many(comodel_name='hr.expense', inverse_name='account_move_id')
nb_expenses = fields.Integer(compute='_compute_nb_expenses', string='Number of Expenses', compute_sudo=True)
@api.depends('partner_id', 'expense_sheet_id', 'company_id')
def _compute_nb_expenses(self):
for move in self:
move.nb_expenses = len(move.expense_ids)
@api.depends('partner_id', 'expense_ids', 'company_id')
def _compute_commercial_partner_id(self):
own_expense_moves = self.filtered(lambda move: move.sudo().expense_sheet_id.payment_mode == 'own_account')
own_expense_moves = self.filtered(lambda move: any(expense.payment_mode == 'own_account' for expense in move.sudo().expense_ids))
for move in own_expense_moves:
if move.expense_sheet_id.payment_mode == 'own_account':
move.commercial_partner_id = (
move.partner_id.commercial_partner_id
if move.partner_id.commercial_partner_id != move.company_id.partner_id
else move.partner_id
)
move.commercial_partner_id = (
move.partner_id.commercial_partner_id
if move.partner_id.commercial_partner_id != move.company_id.partner_id
else move.partner_id
)
super(AccountMove, self - own_expense_moves)._compute_commercial_partner_id()
def action_open_expense_report(self):
@api.constrains('expense_ids')
def _check_expense_ids(self):
for move in self:
expense_payment_modes = move.expense_ids.mapped('payment_mode')
if 'company_account' in expense_payment_modes and len(move.expense_ids) > 1 :
raise ValidationError(_("Each expense paid by the company must have a distinct and dedicated journal entry."))
def action_open_expense(self):
self.ensure_one()
return {
'name': self.expense_sheet_id.name,
linked_expenses = self.expense_ids
if len(linked_expenses) > 1:
return {
'name': _("Expenses"),
'type': 'ir.actions.act_window',
'view_mode': 'list,form',
'views': [(False, 'list'), (False, 'form')],
'res_model': 'hr.expense',
'domain': [('id', 'in', linked_expenses.ids)],
}
return {
'name': linked_expenses.name,
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'hr.expense.sheet',
'res_id': self.expense_sheet_id.id
'views': [(False, 'form')],
'res_model': 'hr.expense',
'res_id': linked_expenses.id
}
# Expenses can be written on journal other than purchase, hence don't include them in the constraint check
def _check_journal_move_type(self):
return super(AccountMove, self.filtered(lambda x: not x.expense_sheet_id))._check_journal_move_type()
return super(AccountMove, self.filtered(lambda x: not x.expense_ids))._check_journal_move_type()
def _creation_message(self):
if self.expense_sheet_id:
return _("Expense entry Created")
if self.expense_ids:
if len(self.expense_ids) == 1:
return _("Journal entry created from this expense: %(link)s", link=self.expense_ids._get_html_link())
links = self.expense_ids[0]._get_html_link()
for additional_expense in self.expense_ids[1:]: # ', ' Destroys Markup, and each part here is safe
links += ', ' + additional_expense._get_html_link()
return _("Journal entry created from these expenses: %(links)s", links=links)
return super()._creation_message()
@api.depends('expense_sheet_id.payment_mode')
def _compute_payment_state(self):
company_paid = self.filtered(lambda m: m.expense_sheet_id.payment_mode == 'company_account')
for move in company_paid:
move.payment_state = 'paid'
super(AccountMove, self - company_paid)._compute_payment_state()
@api.depends('expense_sheet_id')
@api.depends('expense_ids')
def _compute_needed_terms(self):
# EXTENDS account
# We want to set the account destination based on the 'payment_mode'.
super()._compute_needed_terms()
for move in self:
if move.expense_sheet_id and move.expense_sheet_id.payment_mode == 'company_account':
if move.expense_ids and 'company_account' in move.expense_ids.mapped('payment_mode'):
term_lines = move.line_ids.filtered(lambda l: l.display_type != 'payment_term')
move.needed_terms = {
frozendict(
{
"move_id": move.id,
"date_maturity": move.expense_sheet_id.accounting_date
or fields.Date.context_today(move.expense_sheet_id),
"date_maturity": fields.Date.context_today(move.expense_ids),
}
): {
"balance": -sum(term_lines.mapped("balance")),
"amount_currency": -sum(term_lines.mapped("amount_currency")),
"name": "",
"account_id": move.expense_sheet_id.expense_line_ids[0]._get_expense_account_destination(),
"name": move.payment_reference or "",
"account_id": move.expense_ids._get_expense_account_destination(),
}
}
def _reverse_moves(self, default_values_list=None, cancel=False):
# Extends account
# Reversing vendor bills that represent employee reimbursements should clear them from the expense sheet such that another
# can be generated in place.
own_account_moves = self.filtered(lambda move: move.expense_sheet_id.payment_mode == 'own_account')
own_account_moves.expense_sheet_id.sudo().write({
'state': 'approve',
'account_move_id': False,
})
own_account_moves.ref = False # else, when restarting the expense flow we get duplicate issue on vendor.bill
def _prepare_product_base_line_for_taxes_computation(self, product_line):
# EXTENDS 'account'
results = super()._prepare_product_base_line_for_taxes_computation(product_line)
if product_line.expense_id.payment_mode == 'own_account':
results['special_mode'] = 'total_included'
return results
def _reverse_moves(self, default_values_list=None, cancel=False):
# EXTENDS account
self.filtered('expense_ids').write({'expense_ids': [Command.clear()]})
return super()._reverse_moves(default_values_list=default_values_list, cancel=cancel)
def unlink(self):
if self.expense_sheet_id:
self.expense_sheet_id.write({
'state': 'approve',
'account_move_id': False, # cannot change to delete='set null' in stable
})
return super().unlink()
def button_draft(self):
def button_cancel(self):
# EXTENDS account
employee_expense_sheets = self.expense_sheet_id.filtered(
lambda expense_sheet: expense_sheet.payment_mode == 'own_account'
)
employee_expense_sheets.state = 'post'
return super().button_draft()
# We need to override this method to remove the link with the move, else we cannot reimburse them anymore.
# And cancelling the move != cancelling the expense
res = super().button_cancel()
self.filtered('expense_ids').write({'expense_ids': [Command.clear()]})
return res

View file

@ -1,62 +1,42 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.tools.misc import frozendict
from odoo.tools import SQL
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
expense_id = fields.Many2one('hr.expense', string='Expense', copy=True)
expense_id = fields.Many2one('hr.expense', string='Expense', copy=True, index='btree_not_null') # copy=True, else we don't know price is tax incl.
def _compute_partner_id(self):
# EXTENDS account to ensure the partner is correctly set on all the move lines, preventing wrong bank accounts on payments
expense_lines = self.filtered('move_id.expense_ids') # Can't use expense_id because the payment terms line may not have it set
super(AccountMoveLine, self - expense_lines)._compute_partner_id()
for line in expense_lines:
line.partner_id = line.move_id.partner_id # The employee partner is correctly set on the move
@api.constrains('account_id', 'display_type')
def _check_payable_receivable(self):
super(AccountMoveLine, self.filtered(lambda line: line.move_id.expense_sheet_id.payment_mode != 'company_account'))._check_payable_receivable()
def reconcile(self):
# OVERRIDE
not_paid_expenses = self.move_id.expense_sheet_id.expense_line_ids.filtered(lambda expense: expense.state != 'done')
res = super().reconcile()
# Do not update expense or expense sheet states when reversing journal entries
not_paid_expense_sheets = not_paid_expenses.sheet_id.filtered(lambda sheet: sheet.account_move_id.payment_state != 'reversed')
paid_expenses = not_paid_expenses.filtered(lambda expense: expense.currency_id.is_zero(expense.amount_residual))
paid_expenses.write({'state': 'done'})
not_paid_expense_sheets.filtered(lambda sheet: all(expense.state == 'done' for expense in sheet.expense_line_ids)).set_to_paid()
return res
super(AccountMoveLine, self.filtered(lambda line: line.expense_id.payment_mode != 'company_account'))._check_payable_receivable()
def _get_attachment_domains(self):
attachment_domains = super(AccountMoveLine, self)._get_attachment_domains()
if self.expense_id:
attachment_domains.append([('res_model', '=', 'hr.expense'), ('res_id', '=', self.expense_id.id)])
attachment_domains.append([('res_model', '=', 'hr.expense'), ('res_id', 'in', self.expense_id.ids)])
return attachment_domains
def _compute_tax_key(self):
super()._compute_tax_key()
for line in self:
if line.expense_id:
line.tax_key = frozendict(**line.tax_key, expense_id=line.expense_id.id)
def _compute_all_tax(self):
expense_lines = self.filtered('expense_id')
super(AccountMoveLine, expense_lines.with_context(force_price_include=True))._compute_all_tax()
super(AccountMoveLine, self - expense_lines)._compute_all_tax()
for line in expense_lines:
for key in list(line.compute_all_tax.keys()):
new_key = frozendict(**key, expense_id=line.expense_id.id)
line.compute_all_tax[new_key] = line.compute_all_tax.pop(key)
@api.model
def _get_attachment_by_record(self, id_model2attachments, move_line):
return (
super()._get_attachment_by_record(id_model2attachments, move_line)
or id_model2attachments.get(('hr.expense', move_line.expense_id.id))
)
def _compute_totals(self):
expenses = self.filtered('expense_id')
super(AccountMoveLine, expenses.with_context(force_price_include=True))._compute_totals()
super(AccountMoveLine, self - expenses)._compute_totals()
def _convert_to_tax_base_line_dict(self):
result = super()._convert_to_tax_base_line_dict()
if self.expense_id:
result.setdefault('extra_context', {})
result['extra_context']['force_price_include'] = True
return result
def _get_extra_query_base_tax_line_mapping(self):
return ' AND (base_line.expense_id IS NULL OR account_move_line.expense_id = base_line.expense_id)'
def _get_extra_query_base_tax_line_mapping(self) -> SQL:
return SQL(' AND (base_line.expense_id IS NULL OR account_move_line.expense_id = base_line.expense_id)')

Some files were not shown because too many files have changed in this diff Show more