From 72c36898232dda58186d047256b2e7802ab05e57 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Tue, 27 Jul 2021 13:23:00 +0700
Subject: [PATCH 01/75] [14.0][ADD] hr_expense_payment
(cherry picked from commit 958f0483ef96ca924c91b21cebf2ebf21180f179)
---
hr_expense_payment/README.rst | 82 ++++
hr_expense_payment/__init__.py | 5 +
hr_expense_payment/__manifest__.py | 16 +
hr_expense_payment/hooks.py | 19 +
hr_expense_payment/models/__init__.py | 5 +
hr_expense_payment/models/account_payment.py | 19 +
hr_expense_payment/models/hr_expense.py | 63 +++
hr_expense_payment/models/hr_expense_sheet.py | 26 ++
hr_expense_payment/readme/CONTRIBUTORS.rst | 1 +
hr_expense_payment/readme/DESCRIPTION.rst | 2 +
.../static/description/icon.png | Bin 0 -> 9455 bytes
.../static/description/index.html | 422 ++++++++++++++++++
hr_expense_payment/tests/__init__.py | 3 +
.../tests/test_hr_expense_payment.py | 91 ++++
hr_expense_payment/wizard/__init__.py | 3 +
.../wizard/account_payment_register.py | 30 ++
16 files changed, 787 insertions(+)
create mode 100644 hr_expense_payment/README.rst
create mode 100644 hr_expense_payment/__init__.py
create mode 100644 hr_expense_payment/__manifest__.py
create mode 100644 hr_expense_payment/hooks.py
create mode 100644 hr_expense_payment/models/__init__.py
create mode 100644 hr_expense_payment/models/account_payment.py
create mode 100644 hr_expense_payment/models/hr_expense.py
create mode 100644 hr_expense_payment/models/hr_expense_sheet.py
create mode 100644 hr_expense_payment/readme/CONTRIBUTORS.rst
create mode 100644 hr_expense_payment/readme/DESCRIPTION.rst
create mode 100644 hr_expense_payment/static/description/icon.png
create mode 100644 hr_expense_payment/static/description/index.html
create mode 100644 hr_expense_payment/tests/__init__.py
create mode 100644 hr_expense_payment/tests/test_hr_expense_payment.py
create mode 100644 hr_expense_payment/wizard/__init__.py
create mode 100644 hr_expense_payment/wizard/account_payment_register.py
diff --git a/hr_expense_payment/README.rst b/hr_expense_payment/README.rst
new file mode 100644
index 000000000..b82c96946
--- /dev/null
+++ b/hr_expense_payment/README.rst
@@ -0,0 +1,82 @@
+==================
+HR Expense Payment
+==================
+
+.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--expense-lightgray.png?logo=github
+ :target: https://github.com/OCA/hr-expense/tree/14.0/hr_expense_payment
+ :alt: OCA/hr-expense
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/hr-expense-14-0/hr-expense-14-0-hr_expense_payment
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
+ :target: https://runbot.odoo-community.org/runbot/289/14.0
+ :alt: Try me on Runbot
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+This module allow payment link to expense.
+After you register payment on expense sheet, it will link between expense sheet and payment.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+~~~~~~~
+
+* Ecosoft
+
+Contributors
+~~~~~~~~~~~~
+
+* Saran Lim.
+
+Maintainers
+~~~~~~~~~~~
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-Saran440| image:: https://github.com/Saran440.png?size=40px
+ :target: https://github.com/Saran440
+ :alt: Saran440
+
+Current `maintainer `__:
+
+|maintainer-Saran440|
+
+This module is part of the `OCA/hr-expense `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/hr_expense_payment/__init__.py b/hr_expense_payment/__init__.py
new file mode 100644
index 000000000..c127efb6a
--- /dev/null
+++ b/hr_expense_payment/__init__.py
@@ -0,0 +1,5 @@
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from . import models
+from . import wizard
+from .hooks import post_init_hook
diff --git a/hr_expense_payment/__manifest__.py b/hr_expense_payment/__manifest__.py
new file mode 100644
index 000000000..a0d156b95
--- /dev/null
+++ b/hr_expense_payment/__manifest__.py
@@ -0,0 +1,16 @@
+# Copyright 2019 Tecnativa - Ernesto Tejeda
+# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+{
+ "name": "HR Expense Payment",
+ "version": "14.0.1.0.0",
+ "category": "Human Resources",
+ "author": "Tecnativa, Ecosoft, Odoo Community Association (OCA)",
+ "license": "AGPL-3",
+ "website": "https://github.com/OCA/hr-expense",
+ "depends": ["hr_expense"],
+ "data": [],
+ "installable": True,
+ "post_init_hook": "post_init_hook",
+}
diff --git a/hr_expense_payment/hooks.py b/hr_expense_payment/hooks.py
new file mode 100644
index 000000000..997bf6842
--- /dev/null
+++ b/hr_expense_payment/hooks.py
@@ -0,0 +1,19 @@
+# Copyright 2019 Tecnativa - Ernesto Tejeda
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from odoo import SUPERUSER_ID, api
+
+
+def post_init_hook(cr, registry):
+ """ Trying to fill the source expense sheet in payments """
+ with api.Environment.manage():
+ env = api.Environment(cr, SUPERUSER_ID, {})
+ sheets = env["hr.expense.sheet"].search([("payment_mode", "=", "own_account")])
+ for sheet in sheets:
+ amls = sheet.account_move_id.mapped("line_ids")
+ reconcile = amls.mapped("full_reconcile_id")
+ aml_payment = reconcile.mapped("reconciled_line_ids").filtered(
+ lambda r: r not in amls
+ )
+ payment = aml_payment.mapped("payment_id")
+ payment.write({"expense_sheet_ids": sheet.ids})
diff --git a/hr_expense_payment/models/__init__.py b/hr_expense_payment/models/__init__.py
new file mode 100644
index 000000000..e9fa1d92d
--- /dev/null
+++ b/hr_expense_payment/models/__init__.py
@@ -0,0 +1,5 @@
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from . import account_payment
+from . import hr_expense
+from . import hr_expense_sheet
diff --git a/hr_expense_payment/models/account_payment.py b/hr_expense_payment/models/account_payment.py
new file mode 100644
index 000000000..0edcaea31
--- /dev/null
+++ b/hr_expense_payment/models/account_payment.py
@@ -0,0 +1,19 @@
+# Copyright 2019 Tecnativa - Ernesto Tejeda
+# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/)
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from odoo import fields, models
+
+
+class AccountPayment(models.Model):
+ _inherit = "account.payment"
+
+ expense_sheet_ids = fields.Many2many(
+ comodel_name="hr.expense.sheet",
+ relation="payment_expense_sheet_rel",
+ column1="payment_id",
+ column2="sheet_id",
+ string="Expense sheet",
+ readonly=True,
+ copy=False,
+ )
diff --git a/hr_expense_payment/models/hr_expense.py b/hr_expense_payment/models/hr_expense.py
new file mode 100644
index 000000000..ad0830489
--- /dev/null
+++ b/hr_expense_payment/models/hr_expense.py
@@ -0,0 +1,63 @@
+# Copyright 2019 Tecnativa - Ernesto Tejeda
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from odoo import models
+
+
+class HrExpense(models.Model):
+ _inherit = "hr.expense"
+
+ def _prepare_payment_expense_company(
+ self,
+ payment_methods,
+ journal,
+ different_currency,
+ journal_currency,
+ total_amount_currency,
+ ):
+ self.ensure_one()
+ commercial_partner_id = (
+ self.employee_id.sudo().address_home_id.commercial_partner_id
+ )
+ payment_dict = {
+ "date": self.date,
+ "amount": total_amount_currency,
+ "payment_type": "outbound",
+ "partner_type": "supplier",
+ "ref": self.name,
+ "journal_id": journal.id,
+ "currency_id": self.currency_id.id
+ if different_currency
+ else journal_currency.id,
+ "partner_id": commercial_partner_id.id,
+ "payment_method_id": payment_methods and payment_methods[0].id or False,
+ "expense_sheet_ids": self.sheet_id.ids,
+ }
+ return payment_dict
+
+ def action_move_create(self):
+ move_group_by_sheet = super().action_move_create()
+ payment_list = []
+ for expense in self:
+ if expense.payment_mode == "company_account":
+ total_amount_currency = expense.total_amount
+ different_currency = (
+ expense.currency_id != expense.company_id.currency_id
+ )
+ journal = expense.sheet_id.bank_journal_id
+ journal_currency = journal.currency_id or journal.company_id.currency_id
+ payment_methods = journal.outbound_payment_method_ids
+ # prepare payment dict value for case paid by company
+ payment_dict = expense._prepare_payment_expense_company(
+ payment_methods,
+ journal,
+ different_currency,
+ journal_currency,
+ total_amount_currency,
+ )
+ payment_list.append(payment_dict)
+ # create payment and auto post from expense paid by company
+ if payment_list:
+ payment = self.env["account.payment"].create(payment_list)
+ payment.action_post()
+ return move_group_by_sheet
diff --git a/hr_expense_payment/models/hr_expense_sheet.py b/hr_expense_payment/models/hr_expense_sheet.py
new file mode 100644
index 000000000..4acab3067
--- /dev/null
+++ b/hr_expense_payment/models/hr_expense_sheet.py
@@ -0,0 +1,26 @@
+# Copyright 2019 Tecnativa - Ernesto Tejeda
+# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import fields, models
+
+
+class HrExpenseSheet(models.Model):
+ _inherit = "hr.expense.sheet"
+
+ payment_ids = fields.Many2many(
+ comodel_name="account.payment",
+ relation="payment_expense_sheet_rel",
+ column1="sheet_id",
+ column2="payment_id",
+ string="Payment",
+ readonly=True,
+ copy=False,
+ )
+
+ def action_register_payment(self):
+ """ Send context when you register payment from expense sheet """
+ action = super().action_register_payment()
+ if self._name == "hr.expense.sheet":
+ action["context"].update({"expense_sheet_ids": self.ids})
+ return action
diff --git a/hr_expense_payment/readme/CONTRIBUTORS.rst b/hr_expense_payment/readme/CONTRIBUTORS.rst
new file mode 100644
index 000000000..cc6b23102
--- /dev/null
+++ b/hr_expense_payment/readme/CONTRIBUTORS.rst
@@ -0,0 +1 @@
+* Saran Lim.
diff --git a/hr_expense_payment/readme/DESCRIPTION.rst b/hr_expense_payment/readme/DESCRIPTION.rst
new file mode 100644
index 000000000..2bfb486f3
--- /dev/null
+++ b/hr_expense_payment/readme/DESCRIPTION.rst
@@ -0,0 +1,2 @@
+This module allow payment link to expense.
+After you register payment on expense sheet, it will link between expense sheet and payment.
diff --git a/hr_expense_payment/static/description/icon.png b/hr_expense_payment/static/description/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d
GIT binary patch
literal 9455
zcmW++2RxMjAAjx~&dlBk9S+%}OXg)AGE&Cb*&}d0jUxM@u(PQx^-s)697TX`ehR4?GS^qbkof1cslKgkU)h65qZ9Oc=ml_0temigYLJfnz{IDzUf>bGs4N!v3=Z3jMq&A#7%rM5eQ#dc?k~!
zVpnB`o+K7|Al`Q_U;eD$B
zfJtP*jH`siUq~{KE)`jP2|#TUEFGRryE2`i0**z#*^6~AI|YzIWy$Cu#CSLW3q=GA
z6`?GZymC;dCPk~rBS%eCb`5OLr;RUZ;D`}um=H)BfVIq%7VhiMr)_#G0N#zrNH|__
zc+blN2UAB0=617@>_u;MPHN;P;N#YoE=)R#i$k_`UAA>WWCcEVMh~L_
zj--gtp&|K1#58Yz*AHCTMziU1Jzt_jG0I@qAOHsk$2}yTmVkBp_eHuY$A9)>P6o~I
z%aQ?!(GqeQ-Y+b0I(m9pwgi(IIZZzsbMv+9w{PFtd_<_(LA~0H(xz{=FhLB@(1&qHA5EJw1>>=%q2f&^X>IQ{!GJ4e9U
z&KlB)z(84HmNgm2hg2C0>WM{E(DdPr+EeU_N@57;PC2&DmGFW_9kP&%?X4}+xWi)(
z;)z%wI5>D4a*5XwD)P--sPkoY(a~WBw;E~AW`Yue4kFa^LM3X`8x|}ZUeMnqr}>kH
zG%WWW>3ml$Yez?i%)2pbKPI7?5o?hydokgQyZsNEr{a|mLdt;X2TX(#B1j35xPnPW
z*bMSSOauW>o;*=kO8ojw91VX!qoOQb)zHJ!odWB}d+*K?#sY_jqPdg{Sm2HdYzdEx
zOGVPhVRTGPtv0o}RfVP;Nd(|CB)I;*t&QO8h
zFfekr30S!-LHmV_Su-W+rEwYXJ^;6&3|L$mMC8*bQptyOo9;>Qb9Q9`ySe3%V$A*9
zeKEe+b0{#KWGp$F+tga)0RtI)nhMa-K@JS}2krK~n8vJ=Ngm?R!9G<~RyuU0d?nz#
z-5EK$o(!F?hmX*2Yt6+coY`6jGbb7tF#6nHA
zuKk=GGJ;ZwON1iAfG$E#Y7MnZVmrY|j0eVI(DN_MNFJmyZ|;w4tf@=CCDZ#5N_0K=
z$;R~bbk?}TpfDjfB&aiQ$VA}s?P}xPERJG{kxk5~R`iRS(SK5d+Xs9swCozZISbnS
zk!)I0>t=A<-^z(cmSFz3=jZ23u13X><0b)P)^1T_))Kr`e!-pb#q&J*Q`p+B6la%C
zuVl&0duN<;uOsB3%T9Fp8t{ED108)`y_~Hnd9AUX7h-H?jVuU|}My+C=TjH(jKz
zqMVr0re3S$H@t{zI95qa)+Crz*5Zj}Ao%4Z><+W(nOZd?gDnfNBC3>M8WE61$So|P
zVvqH0SNtDTcsUdzaMDpT=Ty0pDHHNL@Z0w$Y`XO
z2M-_r1S+GaH%pz#Uy0*w$Vdl=X=rQXEzO}d6J^R6zjM1u&c9vYLvLp?W7w(?np9x1
zE_0JSAJCPB%i7p*Wvg)pn5T`8k3-uR?*NT|J`eS#_#54p>!p(mLDvmc-3o0mX*mp_
zN*AeS<>#^-{S%W<*mz^!X$w_2dHWpcJ6^j64qFBft-o}o_Vx80o0>}Du;>kLts;$8
zC`7q$QI(dKYG`Wa8#wl@V4jVWBRGQ@1dr-hstpQL)Tl+aqVpGpbSfN>5i&QMXfiZ>
zaA?T1VGe?rpQ@;+pkrVdd{klI&jVS@I5_iz!=UMpTsa~mBga?1r}aRBm1WS;TT*s0f0lY=JBl66Upy)-k4J}lh=P^8(SXk~0xW=T9v*B|gzIhN
z>qsO7dFd~mgxAy4V?&)=5ieYq?zi?ZEoj)&2o)RLy=@hbCRcfT5jigwtQGE{L*8<@Yd{zg;CsL5mvzfDY}P-wos_6PfprFVaeqNE%h
zKZhLtcQld;ZD+>=nqN~>GvROfueSzJD&BE*}XfU|H&(FssBqY=hPCt`d
zH?@s2>I(|;fcW&YM6#V#!kUIP8$Nkdh0A(bEVj``-AAyYgwY~jB
zT|I7Bf@%;7aL7Wf4dZ%VqF$eiaC38OV6oy3Z#TER2G+fOCd9Iaoy6aLYbPTN{XRPz
z;U!V|vBf%H!}52L2gH_+j;`bTcQRXB+y9onc^wLm5wi3-Be}U>k_u>2Eg$=k!(l@I
zcCg+flakT2Nej3i0yn+g+}%NYb?ta;R?(g5SnwsQ49U8Wng8d|{B+lyRcEDvR3+`O{zfmrmvFrL6acVP%yG98X
zo&+VBg@px@i)%o?dG(`T;n*$S5*rnyiR#=wW}}GsAcfyQpE|>a{=$Hjg=-*_K;UtD
z#z-)AXwSRY?OPefw^iI+
z)AXz#PfEjlwTes|_{sB?4(O@fg0AJ^g8gP}ex9Ucf*@_^J(s_5jJV}c)s$`Myn|Kd
z$6>}#q^n{4vN@+Os$m7KV+`}c%4)4pv@06af4-x5#wj!KKb%caK{A&Y#Rfs
z-po?Dcb1({W=6FKIUirH&(yg=*6aLCekcKwyfK^JN5{wcA3nhO(o}SK#!CINhI`-I
z1)6&n7O&ZmyFMuNwvEic#IiOAwNkR=u5it{B9n2sAJV5pNhar=j5`*N!Na;c7g!l$
z3aYBqUkqqTJ=Re-;)s!EOeij=7SQZ3Hq}ZRds%IM*PtM$wV
z@;rlc*NRK7i3y5BETSKuumEN`Xu_8GP1Ri=OKQ$@I^ko8>H6)4rjiG5{VBM>B|%`&&s^)jS|-_95&yc=GqjNo{zFkw%%HHhS~e=s
zD#sfS+-?*t|J!+ozP6KvtOl!R)@@-z24}`9{QaVLD^9VCSR2b`b!KC#o;Ki<+wXB6
zx3&O0LOWcg4&rv4QG0)4yb}7BFSEg~=IR5#ZRj8kg}dS7_V&^%#Do==#`u
zpy6{ox?jWuR(;pg+f@mT>#HGWHAJRRDDDv~@(IDw&R>9643kK#HN`!1vBJHnC+RM&yIh8{gG2q
zA%e*U3|N0XSRa~oX-3EAneep)@{h2vvd3Xvy$7og(sayr@95+e6~Xvi1tUqnIxoIH
zVWo*OwYElb#uyW{Imam6f2rGbjR!Y3`#gPqkv57dB6K^wRGxc9B(t|aYDGS=m$&S!NmCtrMMaUg(c
zc2qC=2Z`EEFMW-me5B)24AqF*bV5Dr-M5ig(l-WPS%CgaPzs6p_gnCIvTJ=Y<6!gT
zVt@AfYCzjjsMEGi=rDQHo0yc;HqoRNnNFeWZgcm?f;cp(6CNylj36DoL(?TS7eU#+
z7&mfr#y))+CJOXQKUMZ7QIdS9@#-}7y2K1{8)cCt0~-X0O!O?Qx#E4Og+;A2SjalQ
zs7r?qn0H044=sDN$SRG$arw~n=+T_DNdSrarmu)V6@|?1-ZB#hRn`uilTGPJ@fqEy
zGt(f0B+^JDP&f=r{#Y_wi#AVDf-y!RIXU^0jXsFpf>=Ji*TeqSY!H~AMbJdCGLhC)
zn7Rx+sXw6uYj;WRYrLd^5IZq@6JI1C^YkgnedZEYy<&4(z%Q$5yv#Boo{AH8n$a
zhb4Y3PWdr269&?V%uI$xMcUrMzl=;w<_nm*qr=c3Rl@i5wWB;e-`t7D&c-mcQl7x!
zZWB`UGcw=Y2=}~wzrfLx=uet<;m3~=8I~ZRuzvMQUQdr+yTV|ATf1Uuomr__nDf=X
zZ3WYJtHp_ri(}SQAPjv+Y+0=fH4krOP@S&=zZ-t1jW1o@}z;xk8
z(Nz1co&El^HK^NrhVHa-_;&88vTU>_J33=%{if;BEY*J#1n59=07jrGQ#IP>@u#3A
z;!q+E1Rj3ZJ+!4bq9F8PXJ@yMgZL;>&gYA0%_Kbi8?S=XGM~dnQZQ!yBSgcZhY96H
zrWnU;k)qy`rX&&xlDyA%(a1Hhi5CWkmg(`Gb%m(HKi-7Z!LKGRP_B8@`7&hdDy5n=
z`OIxqxiVfX@OX1p(mQu>0Ai*v_cTMiw4qRt3~NBvr9oBy0)r>w3p~V0SCm=An6@3n)>@z!|o-$HvDK
z|3D2ZMJkLE5loMKl6R^ez@Zz%S$&mbeoqH5`Bb){Ei21q&VP)hWS2tjShfFtGE+$z
zzCR$P#uktu+#!w)cX!lWN1XU%K-r=s{|j?)Akf@q#3b#{6cZCuJ~gCxuMXRmI$nGtnH+-h
z+GEi!*X=AP<|fG`1>MBdTb?28JYc=fGvAi2I<$B(rs$;eoJCyR6_bc~p!XR@O-+sD
z=eH`-ye})I5ic1eL~TDmtfJ|8`0VJ*Yr=hNCd)G1p2MMz4C3^Mj?7;!w|Ly%JqmuW
zlIEW^Ft%z?*|fpXda>Jr^1noFZEwFgVV%|*XhH@acv8rdGxeEX{M$(vG{Zw+x(ei@
zmfXb22}8-?Fi`vo-YVrTH*C?a8%M=Hv9MqVH7H^J$KsD?>!SFZ;ZsvnHr_gn=7acz
z#W?0eCdVhVMWN12VV^$>WlQ?f;P^{(&pYTops|btm6aj>_Uz+hqpGwB)vWp0Cf5y<
zft8-je~nn?W11plq}N)4A{l8I7$!ks_x$PXW-2XaRFswX_BnF{R#6YIwMhAgd5F9X
zGmwdadS6(a^fjHtXg8=l?Rc0Sm%hk6E9!5cLVloEy4eh(=FwgP`)~I^5~pBEWo+F6
zSf2ncyMurJN91#cJTy_u8Y}@%!bq1RkGC~-bV@SXRd4F{R-*V`bS+6;W5vZ(&+I<9$;-V|eNfLa5n-6%
z2(}&uGRF;p92eS*sE*oR$@pexaqr*meB)VhmIg@h{uzkk$9~qh#cHhw#>O%)b@+(|
z^IQgqzuj~Sk(J;swEM-3TrJAPCq9k^^^`q{IItKBRXYe}e0Tdr=Huf7da3$l4PdpwWDop%^}n;dD#K4s#DYA8SHZ
z&1!riV4W4R7R#C))JH1~axJ)RYnM$$lIR%6fIVA@zV{XVyx}C+a-Dt8Y9M)^KU0+H
zR4IUb2CJ{Hg>CuaXtD50jB(_Tcx=Z$^WYu2u5kubqmwp%drJ6
z?Fo40g!Qd<-l=TQxqHEOuPX0;^z7iX?Ke^a%XT<13TA^5`4Xcw6D@Ur&VT&CUe0d}
z1GjOVF1^L@>O)l@?bD~$wzgf(nxX1OGD8fEV?TdJcZc2KoUe|oP1#=$$7ee|xbY)A
zDZq+cuTpc(fFdj^=!;{k03C69lMQ(|>uhRfRu%+!k&YOi-3|1QKB
z
z?n?eq1XP>p-IM$Z^C;2L3itnbJZAip*Zo0aw2bs8@(s^~*8T9go!%dHcAz2lM;`yp
zD=7&xjFV$S&5uDaiScyD?B-i1ze`+CoRtz`Wn+Zls4&}MO{@N!ufrzjG$B79)Y2d3tBk&)TxUTw@QS0TEL_?njX|@vq?Uz(nBFK5Pq7*xj#u*R&i|?7+6#
z+|r_n#SW&LXhtheZdah{ZVoqwyT{D>MC3nkFF#N)xLi{p7J1jXlmVeb;cP5?e(=f#
zuT7fvjSbjS781v?7{)-X3*?>tq?)Yd)~|1{BDS(pqC
zC}~H#WXlkUW*H5CDOo<)#x7%RY)A;ShGhI5s*#cRDA8YgqG(HeKDx+#(ZQ?386dv!
zlXCO)w91~Vw4AmOcATuV653fa9R$fyK8ul%rG
z-wfS
zihugoZyr38Im?Zuh6@RcF~t1anQu7>#lPpb#}4cOA!EM11`%f*07RqOVkmX{p~KJ9
z^zP;K#|)$`^Rb{rnHGH{~>1(fawV0*Z#)}M`m8-?ZJV<+e}s9wE#
z)l&az?w^5{)`S(%MRzxdNqrs1n*-=jS^_jqE*5XDrA0+VE`5^*p3CuM<&dZEeCjoz
zR;uu_H9ZPZV|fQq`Cyw4nscrVwi!fE6ciMmX$!_hN7uF;jjKG)d2@aC4ropY)8etW=xJvni)8eHi`H$%#zn^WJ5NLc-rqk|u&&4Z6fD_m&JfSI1Bvb?b<*n&sfl0^t
z=HnmRl`XrFvMKB%9}>PaA`m-fK6a0(8=qPkWS5bb4=v?XcWi&hRY?O5HdulRi4?fN
zlsJ*N-0Qw+Yic@s0(2uy%F@ib;GjXt01Fmx5XbRo6+n|pP(&nodMoap^z{~q
ziEeaUT@Mxe3vJSfI6?uLND(CNr=#^W<1b}jzW58bIfyWTDle$mmS(|x-0|2UlX+9k
zQ^EX7Nw}?EzVoBfT(-LT|=9N@^hcn-_p&sqG
z&*oVs2JSU+N4ZD`FhCAWaS;>|wH2G*Id|?pa#@>tyxX`+4HyIArWDvVrX)2WAOQff
z0qyHu&-S@i^MS-+j--!pr4fPBj~_8({~e1bfcl0wI1kaoN>mJL6KUPQm5N7lB(ui1
zE-o%kq)&djzWJ}ob<-GfDlkB;F31j-VHKvQUGQ3sp`CwyGJk_i!y^sD0fqC@$9|jO
zOqN!r!8-p==F@ZVP=U$qSpY(gQ0)59P1&t@y?5rvg<}E+GB}26NYPp4f2YFQrQtot5mn3wu_qprZ=>Ig-$
zbW26Ws~IgY>}^5w`vTB(G`PTZaDiGBo5o(tp)qli|NeV(
z@H_=R8V39rt5J5YB2Ky?4eJJ#b`_iBe2ot~6%7mLt5t8Vwi^Jy7|jWXqa3amOIoRb
zOr}WVFP--DsS`1WpN%~)t3R!arKF^Q$e12KEqU36AWwnCBICpH4XCsfnyrHr>$I$4
z!DpKX$OKLWarN7nv@!uIA+~RNO)l$$w}p(;b>mx8pwYvu;dD_unryX_NhT8*Tj>BTrTTL&!?O+%Rv;b?B??gSzdp?6Uug9{
zd@V08Z$BdI?fpoCS$)t4mg4rT8Q_I}h`0d-vYZ^|dOB*Q^S|xqTV*vIg?@fVFSmMpaw0qtTRbx}
z({Pg?#{2`sc9)M5N$*N|4;^t$+QP?#mov
zGVC@I*lBVrOU-%2y!7%)fAKjpEFsgQc4{amtiHb95KQEwvf<(3T<9-Zm$xIew#P22
zc2Ix|App^>v6(3L_MCU0d3W##AB0M~3D00EWoKZqsJYT(#@w$Y_H7G22M~ApVFTRHMI_3be)Lkn#0F*V8Pq
zc}`Cjy$bE;FJ6H7p=0y#R>`}-m4(0F>%@P|?7fx{=R^uFdISRnZ2W_xQhD{YuR3t<
z{6yxu=4~JkeA;|(J6_nv#>Nvs&FuLA&PW^he@t(UwFFE8)|a!R{`E`K`i^ZnyE4$k
z;(749Ix|oi$c3QbEJ3b~D_kQsPz~fIUKym($a_7dJ?o+40*OLl^{=&oq$<#Q(yyrp
z{J-FAniyAw9tPbe&IhQ|a`DqFTVQGQ&Gq3!C2==4x{6EJwiPZ8zub-iXoUtkJiG{}
zPaR&}_fn8_z~(=;5lD-aPWD3z8PZS@AaUiomF!G8I}Mf>e~0g#BelA-5#`cj;O5>N
Xviia!U7SGha1wx#SCgwmn*{w2TRX*I
literal 0
HcmV?d00001
diff --git a/hr_expense_payment/static/description/index.html b/hr_expense_payment/static/description/index.html
new file mode 100644
index 000000000..e6349f8e0
--- /dev/null
+++ b/hr_expense_payment/static/description/index.html
@@ -0,0 +1,422 @@
+
+
+
+
+
+
+HR Expense Payment
+
+
+
+
+
HR Expense Payment
+
+
+
+
This module allow payment link to expense.
+After you register payment on expense sheet, it will link between expense sheet and payment.
+
Table of contents
+
+
+
+
Bugs are tracked on GitHub Issues .
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+feedback .
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
+
+
+
+
+
This module is maintained by the OCA.
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
Current maintainer :
+
+
This module is part of the OCA/hr-expense project on GitHub.
+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute .
+
+
+
+
+
diff --git a/hr_expense_payment/tests/__init__.py b/hr_expense_payment/tests/__init__.py
new file mode 100644
index 000000000..36579e552
--- /dev/null
+++ b/hr_expense_payment/tests/__init__.py
@@ -0,0 +1,3 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from . import test_hr_expense_payment
diff --git a/hr_expense_payment/tests/test_hr_expense_payment.py b/hr_expense_payment/tests/test_hr_expense_payment.py
new file mode 100644
index 000000000..b24ae55c3
--- /dev/null
+++ b/hr_expense_payment/tests/test_hr_expense_payment.py
@@ -0,0 +1,91 @@
+# Copyright 2019 Tecnativa - Ernesto Tejeda
+# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/)
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo.tests.common import Form, TransactionCase
+
+from ..hooks import post_init_hook
+
+
+class TestHrExpensePayment(TransactionCase):
+ def setUp(self):
+ super().setUp()
+ self.account_payment_register = self.env["account.payment.register"]
+ self.payment_journal = self.env["account.journal"].search(
+ [("type", "in", ["cash", "bank"])], limit=1
+ )
+
+ company = self.env.ref("base.main_company")
+ self.expense_journal = self.env["account.journal"].create(
+ {
+ "name": "Purchase Journal - Test",
+ "code": "HRTPJ",
+ "type": "purchase",
+ "company_id": company.id,
+ }
+ )
+
+ self.expense_sheet = self.env["hr.expense.sheet"].create(
+ {
+ "employee_id": self.ref("hr.employee_admin"),
+ "name": "Expense test",
+ "journal_id": self.expense_journal.id,
+ }
+ )
+ self.expense_sheet.approve_expense_sheets()
+
+ self.expense = self.env["hr.expense"].create(
+ {
+ "name": "Expense test",
+ "employee_id": self.ref("hr.employee_admin"),
+ "product_id": self.ref("hr_expense.air_ticket"),
+ "unit_amount": 1,
+ "quantity": 10,
+ "sheet_id": self.expense_sheet.id,
+ }
+ )
+
+ def _get_payment_wizard(self, expense_sheet):
+ action = expense_sheet.action_register_payment()
+ ctx = action.get("context")
+ with Form(
+ self.account_payment_register.with_context(ctx),
+ view="account.view_account_payment_register_form",
+ ) as f:
+ f.journal_id = self.payment_journal
+ f.amount = self.expense_sheet.total_amount
+ register_payment = f.save()
+ return register_payment
+
+ def test_post_init_hook(self):
+ self.expense_sheet.action_sheet_move_create()
+ payment_wizard = self._get_payment_wizard(self.expense_sheet)
+ payment_wizard.action_create_payments()
+
+ payment = self.expense_sheet.payment_ids
+
+ self.assertEqual(len(payment), 1)
+ self.assertEqual(len(payment.expense_sheet_ids), 1)
+
+ payment.expense_sheet_ids = False
+ # Recompute many2one
+ payment = self.expense_sheet.payment_ids
+
+ self.assertFalse(payment)
+ self.assertFalse(payment.expense_sheet_ids)
+ post_init_hook(self.env.cr, self.registry)
+
+ self.assertEqual(len(self.expense_sheet.payment_ids), 1)
+
+ def test_get_payment_vals(self):
+ self.expense_sheet.action_sheet_move_create()
+ payment_wizard = self._get_payment_wizard(self.expense_sheet)
+ self.assertFalse(self.expense_sheet.payment_ids)
+ payment_wizard.action_create_payments()
+ self.assertEqual(len(self.expense_sheet.payment_ids), 1)
+
+ def test_action_sheet_move_create(self):
+ self.expense.payment_mode = "company_account"
+ self.assertFalse(self.expense_sheet.payment_ids)
+ self.expense_sheet.action_sheet_move_create()
+ self.assertEqual(len(self.expense_sheet.payment_ids), 1)
diff --git a/hr_expense_payment/wizard/__init__.py b/hr_expense_payment/wizard/__init__.py
new file mode 100644
index 000000000..09e22cb07
--- /dev/null
+++ b/hr_expense_payment/wizard/__init__.py
@@ -0,0 +1,3 @@
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from . import account_payment_register
diff --git a/hr_expense_payment/wizard/account_payment_register.py b/hr_expense_payment/wizard/account_payment_register.py
new file mode 100644
index 000000000..dc4f40e1f
--- /dev/null
+++ b/hr_expense_payment/wizard/account_payment_register.py
@@ -0,0 +1,30 @@
+# Copyright 2019 Tecnativa - Ernesto Tejeda
+# Copyright 2020 Ecosoft Co., Ltd (https://ecosoft.co.th/)
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from odoo import models
+
+
+class AccountPaymentRegister(models.TransientModel):
+ _inherit = "account.payment.register"
+
+ def _create_payment_vals_from_wizard(self):
+ payment_vals = super()._create_payment_vals_from_wizard()
+ expense_sheet_ids = self._context.get("expense_sheet_ids", False)
+ if expense_sheet_ids:
+ payment_vals.update(expense_sheet_ids=expense_sheet_ids)
+ return payment_vals
+
+ def _create_payment_vals_from_batch(self, batch_result):
+ payment_vals = super()._create_payment_vals_from_batch(batch_result)
+ expense_sheet_ids = self._context.get("expense_sheet_ids", False)
+ if expense_sheet_ids:
+ move_line_ids = self.env["account.move.line"].browse(
+ batch_result["lines"].ids
+ )
+ sheet_ids = self.env["hr.expense.sheet"].browse(expense_sheet_ids)
+ sheet_id = sheet_ids.filtered(
+ lambda l: l.account_move_id.id == move_line_ids.move_id.id
+ )
+ payment_vals.update(expense_sheet_ids=sheet_id.ids)
+ return payment_vals
From e221478e34e01d74ffc2aa6eceed8ea63387bb1d Mon Sep 17 00:00:00 2001
From: Saran440
Date: Tue, 27 Jul 2021 13:28:28 +0700
Subject: [PATCH 02/75] [ENH] hr_expense_cancel depend on hr_expense_payment
(cherry picked from commit 1fb90c168ca022fc6f027c04ae41f68821148d07)
---
hr_expense_payment/models/hr_expense.py | 3 +++
hr_expense_payment/tests/test_hr_expense_payment.py | 9 +++++++++
2 files changed, 12 insertions(+)
diff --git a/hr_expense_payment/models/hr_expense.py b/hr_expense_payment/models/hr_expense.py
index ad0830489..d80514042 100644
--- a/hr_expense_payment/models/hr_expense.py
+++ b/hr_expense_payment/models/hr_expense.py
@@ -38,6 +38,9 @@ def _prepare_payment_expense_company(
def action_move_create(self):
move_group_by_sheet = super().action_move_create()
payment_list = []
+ # you can skip create payment from expense paid by company_account
+ if self.env.context.get("skip_create_payment_company_account", False):
+ return move_group_by_sheet
for expense in self:
if expense.payment_mode == "company_account":
total_amount_currency = expense.total_amount
diff --git a/hr_expense_payment/tests/test_hr_expense_payment.py b/hr_expense_payment/tests/test_hr_expense_payment.py
index b24ae55c3..c1ffc9c75 100644
--- a/hr_expense_payment/tests/test_hr_expense_payment.py
+++ b/hr_expense_payment/tests/test_hr_expense_payment.py
@@ -89,3 +89,12 @@ def test_action_sheet_move_create(self):
self.assertFalse(self.expense_sheet.payment_ids)
self.expense_sheet.action_sheet_move_create()
self.assertEqual(len(self.expense_sheet.payment_ids), 1)
+
+ def test_action_sheet_move_create_skip_payment(self):
+ self.expense.payment_mode = "company_account"
+ self.assertFalse(self.expense_sheet.payment_ids)
+ self.expense_sheet.with_context(
+ skip_create_payment_company_account=True
+ ).action_sheet_move_create()
+ self.assertFalse(self.expense_sheet.payment_ids)
+ self.assertEqual(self.expense_sheet.state, "done")
From 1b8b98afb723b9cd7e7d59dbab93eb0333af51bd Mon Sep 17 00:00:00 2001
From: oca-travis
Date: Sat, 5 Mar 2022 05:21:41 +0000
Subject: [PATCH 03/75] [UPD] Update hr_expense_payment.pot
(cherry picked from commit 3117aaf5ceeb27ee8722aababaf4574d5ed8e1d0)
---
.../i18n/hr_expense_payment.pot | 68 +++++++++++++++++++
1 file changed, 68 insertions(+)
create mode 100644 hr_expense_payment/i18n/hr_expense_payment.pot
diff --git a/hr_expense_payment/i18n/hr_expense_payment.pot b/hr_expense_payment/i18n/hr_expense_payment.pot
new file mode 100644
index 000000000..a7d848668
--- /dev/null
+++ b/hr_expense_payment/i18n/hr_expense_payment.pot
@@ -0,0 +1,68 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * hr_expense_payment
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: hr_expense_payment
+#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment__display_name
+#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment_register__display_name
+#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense__display_name
+#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense_sheet__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: hr_expense_payment
+#: model:ir.model,name:hr_expense_payment.model_hr_expense
+msgid "Expense"
+msgstr ""
+
+#. module: hr_expense_payment
+#: model:ir.model,name:hr_expense_payment.model_hr_expense_sheet
+msgid "Expense Report"
+msgstr ""
+
+#. module: hr_expense_payment
+#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment__expense_sheet_ids
+msgid "Expense sheet"
+msgstr ""
+
+#. module: hr_expense_payment
+#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment__id
+#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment_register__id
+#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense__id
+#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense_sheet__id
+msgid "ID"
+msgstr ""
+
+#. module: hr_expense_payment
+#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment____last_update
+#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment_register____last_update
+#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense____last_update
+#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense_sheet____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: hr_expense_payment
+#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense_sheet__payment_ids
+msgid "Payment"
+msgstr ""
+
+#. module: hr_expense_payment
+#: model:ir.model,name:hr_expense_payment.model_account_payment
+msgid "Payments"
+msgstr ""
+
+#. module: hr_expense_payment
+#: model:ir.model,name:hr_expense_payment.model_account_payment_register
+msgid "Register Payment"
+msgstr ""
From 3a2b58b5731b9510677b92c22cab1dbc7889babb Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Sat, 5 Mar 2022 05:26:07 +0000
Subject: [PATCH 04/75] [UPD] README.rst
(cherry picked from commit 8af8d1d51c569e7a055ea8e099e81c07658eab4f)
---
hr_expense_payment/README.rst | 9 +--------
hr_expense_payment/static/description/index.html | 5 ++---
2 files changed, 3 insertions(+), 11 deletions(-)
diff --git a/hr_expense_payment/README.rst b/hr_expense_payment/README.rst
index b82c96946..d15fae650 100644
--- a/hr_expense_payment/README.rst
+++ b/hr_expense_payment/README.rst
@@ -49,6 +49,7 @@ Credits
Authors
~~~~~~~
+* Tecnativa
* Ecosoft
Contributors
@@ -69,14 +70,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-.. |maintainer-Saran440| image:: https://github.com/Saran440.png?size=40px
- :target: https://github.com/Saran440
- :alt: Saran440
-
-Current `maintainer `__:
-
-|maintainer-Saran440|
-
This module is part of the `OCA/hr-expense `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/hr_expense_payment/static/description/index.html b/hr_expense_payment/static/description/index.html
index e6349f8e0..8fb2e7e56 100644
--- a/hr_expense_payment/static/description/index.html
+++ b/hr_expense_payment/static/description/index.html
@@ -3,7 +3,7 @@
-
+
HR Expense Payment
-
-
HR Expense Payment
+
+
+
+
+
+
+
HR Expense Payment
-
-
This module allow payment link to expense. After you register payment on
-expense sheet, it will link between expense sheet and payment.
+
+
This module records a bidirectional link between each hr.expense
+record and the account.payment records that paid it (via
+hr.expense.payment_ids and account.payment.expense_ids ).
+
When a user clicks Register Payment on a posted employee-paid
+expense (payment_mode='own_account' ), the resulting payment
+back-links to the source expense. A post_init_hook backfills the
+link for payments that predate the module install by walking
+reconciliation chains.
+
19.0 note: Odoo core’s hr.expense.action_pay() already exists and
+launches the standard register-payment wizard. This module adds the
+back-link — core doesn’t track which payment(s) paid which expense
+beyond the implicit reconciliation graph.
Table of contents
@@ -385,24 +400,24 @@ HR Expense Payment
-
+
Bugs are tracked on GitHub Issues .
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
-feedback .
+
feedback .
Do not contact contributors directly about support or help with technical issues.
+
From 34972fa63d4ee9fb465d257d94248794f4d228e5 Mon Sep 17 00:00:00 2001
From: Kitti U
Date: Fri, 26 Apr 2019 18:23:22 +0700
Subject: [PATCH 32/75] [12.0][ADD] hr_expense_advance_clearing
[UPD] Update hr_expense_advance_clearing.pot
[UPD] README.rst
(cherry picked from commit 47c739874bff9659e9580f528c5b1470f61a0e81)
---
hr_expense_advance_clearing/README.rst | 138 +++++
hr_expense_advance_clearing/__init__.py | 2 +
hr_expense_advance_clearing/__manifest__.py | 20 +
.../data/advance_product.xml | 12 +
.../i18n/hr_expense_advance_clearing.pot | 179 +++++++
.../models/__init__.py | 3 +
.../models/account_payment.py | 16 +
.../models/hr_expense.py | 81 +++
.../models/hr_expense_sheet.py | 111 ++++
.../readme/CONFIGURE.rst | 10 +
.../readme/CONTRIBUTORS.rst | 1 +
.../readme/DESCRIPTION.rst | 11 +
.../readme/INSTALL.rst | 1 +
hr_expense_advance_clearing/readme/USAGE.rst | 32 ++
.../static/description/icon.png | Bin 0 -> 9455 bytes
.../static/description/index.html | 487 ++++++++++++++++++
hr_expense_advance_clearing/tests/__init__.py | 1 +
.../tests/test_hr_expense_advance_clearing.py | 205 ++++++++
.../views/hr_expense_views.xml | 70 +++
.../wizard/__init__.py | 1 +
.../hr_expense_sheet_register_payment.py | 65 +++
21 files changed, 1446 insertions(+)
create mode 100644 hr_expense_advance_clearing/README.rst
create mode 100644 hr_expense_advance_clearing/__init__.py
create mode 100644 hr_expense_advance_clearing/__manifest__.py
create mode 100644 hr_expense_advance_clearing/data/advance_product.xml
create mode 100644 hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot
create mode 100644 hr_expense_advance_clearing/models/__init__.py
create mode 100644 hr_expense_advance_clearing/models/account_payment.py
create mode 100644 hr_expense_advance_clearing/models/hr_expense.py
create mode 100644 hr_expense_advance_clearing/models/hr_expense_sheet.py
create mode 100644 hr_expense_advance_clearing/readme/CONFIGURE.rst
create mode 100644 hr_expense_advance_clearing/readme/CONTRIBUTORS.rst
create mode 100644 hr_expense_advance_clearing/readme/DESCRIPTION.rst
create mode 100644 hr_expense_advance_clearing/readme/INSTALL.rst
create mode 100644 hr_expense_advance_clearing/readme/USAGE.rst
create mode 100644 hr_expense_advance_clearing/static/description/icon.png
create mode 100644 hr_expense_advance_clearing/static/description/index.html
create mode 100644 hr_expense_advance_clearing/tests/__init__.py
create mode 100644 hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
create mode 100644 hr_expense_advance_clearing/views/hr_expense_views.xml
create mode 100644 hr_expense_advance_clearing/wizard/__init__.py
create mode 100644 hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
diff --git a/hr_expense_advance_clearing/README.rst b/hr_expense_advance_clearing/README.rst
new file mode 100644
index 000000000..35059b9fd
--- /dev/null
+++ b/hr_expense_advance_clearing/README.rst
@@ -0,0 +1,138 @@
+=============================
+Employee Advance and Clearing
+=============================
+
+.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr-lightgray.png?logo=github
+ :target: https://github.com/OCA/hr/tree/12.0/hr_expense_advance_clearing
+ :alt: OCA/hr
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/hr-12-0/hr-12-0-hr_expense_advance_clearing
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
+ :target: https://runbot.odoo-community.org/runbot/116/12.0
+ :alt: Try me on Runbot
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+Standard Expenses module allow employee to do the expense reimbursement only after the expense has been made.
+In other world, employee will need to pay first and reimburse later.
+
+This module, allow company to advance an amount to the employee.
+Employee can then use that advance amount to purchase product/service first, then back to company and do the clearing.
+
+There can be 3 scenarios for advance and clearing
+
+* When clearing amount = advance amount, no other operation is required.
+* When clearing amount > advance amount, company will pay the extra to employee.
+* When clearing amount < advance amount, employee will return the remain to company.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Installation
+============
+
+* No specific installation required
+
+Configuration
+=============
+
+This module will create a new product "Employee Advance" automatically.
+You will need to setup the Expense Account of this product to your Employee Advance account manually.
+
+* Open Product window and search for "Employee Advance"
+* On Accounting tab, select appropriate employee advance account from your chart of account
+
+Note:
+
+* You will need the "Show Full Accounting Features" to see accounting data
+* Employee Advance account code, if not already exists, you can create one. Use type = Current Asset and check Allow Reconciliation.
+
+Usage
+=====
+
+**Create an Employee Advance**
+
+#. Go to Expenses > My Expenses
+#. Create a new Expense as normal, but also check "Employee Advance" checkbox
+#. Product = Employee Advance will be set automatically, do not change.
+#. Set the unit price to advance amount
+#. Click Create Report as normal. Please note that, this expense report will also has flag "Employee Advance" checked.
+#. As normal, do Submit to Manager > Approve > Post Journal Entries > Register Payment.
+#. As this is Advance, you will see new field "Amount to clear".
+
+**Clear Advance**
+
+#. Go to Expenses > My Expense Report.
+#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance.
+#. Open an Advance which is now in paid status with some Amount to be cleared.
+#. Click button "Clear Advance", system will create new Expense Report with reference to the previous step Advance.
+#. Add or create Expense line(s) as normal.
+#. As normal, do Submit to Manager > Approve > Post Journal Entries
+
+Note:
+
+* If the total expense amount less than or equal to the advance amount, the status will be set to Paid right after post journal entries.
+* If the total expense amount more than the advance amount, Register Payment will pay the extra amount then set state to Paid.
+
+**Return Advance**
+
+#. Go to Expenses > My Expense Report.
+#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance.
+#. Open an Advance which is now in paid status with some Amount to be cleared.
+#. Click button "Return Advance" will open Register Payment wizard with Amount to clear.
+#. Click button "Validate" to return that amount back
+#. All returned, Amount to clear is now equal to 0.0
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+~~~~~~~
+
+* Ecosoft
+
+Contributors
+~~~~~~~~~~~~
+
+* Kitti Upariphutthiphong
+
+Maintainers
+~~~~~~~~~~~
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+This module is part of the `OCA/hr `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/hr_expense_advance_clearing/__init__.py b/hr_expense_advance_clearing/__init__.py
new file mode 100644
index 000000000..9b4296142
--- /dev/null
+++ b/hr_expense_advance_clearing/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/hr_expense_advance_clearing/__manifest__.py b/hr_expense_advance_clearing/__manifest__.py
new file mode 100644
index 000000000..ffceed8a1
--- /dev/null
+++ b/hr_expense_advance_clearing/__manifest__.py
@@ -0,0 +1,20 @@
+# Copyright 2019 Kitti Upariphutthiphong
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+{
+ 'name': 'Employee Advance and Clearing',
+ 'version': '12.0.1.0.0',
+ 'category': 'Human Resources',
+ 'author': 'Ecosoft, '
+ 'Odoo Community Association (OCA)',
+ 'license': 'AGPL-3',
+ 'website': 'https://github.com/OCA/hr',
+ 'depends': [
+ 'hr_expense',
+ ],
+ 'data': [
+ 'data/advance_product.xml',
+ 'views/hr_expense_views.xml',
+ ],
+ 'installable': True,
+}
diff --git a/hr_expense_advance_clearing/data/advance_product.xml b/hr_expense_advance_clearing/data/advance_product.xml
new file mode 100644
index 000000000..3784ef122
--- /dev/null
+++ b/hr_expense_advance_clearing/data/advance_product.xml
@@ -0,0 +1,12 @@
+
+
+ Employee Advance
+
+ service
+
+
+
+ False
+ False
+
+
diff --git a/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot b/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot
new file mode 100644
index 000000000..0c4a1a606
--- /dev/null
+++ b/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot
@@ -0,0 +1,179 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * hr_expense_advance_clearing
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py:46
+#, python-format
+msgid "A remaining advance return of %s %s with the reference %s related to your expense %s has been made."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_filter
+msgid "Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_filter
+msgid "Advance (not cleared)"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid "Advance Clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
+msgid "Advance Remaining"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:60
+#, python-format
+msgid "Advance clearing must not contain any advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:63
+#, python-format
+msgid "Advance must contain only 1 advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:55
+#, python-format
+msgid "Advance: %s has no amount to clear"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__residual
+msgid "Amount to clear"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__residual
+msgid "Amount to clear of this expense sheet in company currency"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Clear Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_filter
+msgid "Clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid "Create a new expense report"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance
+#: model:product.product,name:hr_expense_advance_clearing.product_emp_advance
+#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template
+msgid "Employee Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:24
+#, python-format
+msgid "Employee advance product has no payable account"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:30
+#, python-format
+msgid "Employee advance, all taxes must be removed"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:33
+#, python-format
+msgid "Employee advance, paid by must be employee"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:27
+#, python-format
+msgid "Employee advance, selected product is not valid"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense
+msgid "Expense"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet_register_payment_wizard
+msgid "Expense Register Payment Wizard"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet
+msgid "Expense Report"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable
+msgid "Final regiter payment amount even after advance clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid "Once you have created your expense, submit it to your manager who will validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable
+msgid "Payable Amount"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment
+msgid "Payments"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
+msgid "Remaining amount to clear the selected advance sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Return Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
+msgid "Show remaining advance of this employee"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:product.product,uom_name:hr_expense_advance_clearing.product_emp_advance
+#: model:product.template,uom_name:hr_expense_advance_clearing.product_emp_advance_product_template
+msgid "Unit(s)"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:product.product,weight_uom_name:hr_expense_advance_clearing.product_emp_advance
+#: model:product.template,weight_uom_name:hr_expense_advance_clearing.product_emp_advance_product_template
+msgid "kg"
+msgstr ""
+
diff --git a/hr_expense_advance_clearing/models/__init__.py b/hr_expense_advance_clearing/models/__init__.py
new file mode 100644
index 000000000..01f905ef5
--- /dev/null
+++ b/hr_expense_advance_clearing/models/__init__.py
@@ -0,0 +1,3 @@
+from . import hr_expense
+from . import hr_expense_sheet
+from . import account_payment
diff --git a/hr_expense_advance_clearing/models/account_payment.py b/hr_expense_advance_clearing/models/account_payment.py
new file mode 100644
index 000000000..165563790
--- /dev/null
+++ b/hr_expense_advance_clearing/models/account_payment.py
@@ -0,0 +1,16 @@
+# Copyright 2019 Kitti Upariphutthiphong
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import models
+
+
+class AccountPayment(models.Model):
+ _inherit = 'account.payment'
+
+ def _get_counterpart_move_line_vals(self, invoice=False):
+ emp_advance = self.env.ref('hr_expense_advance_clearing.'
+ 'product_emp_advance')
+ res = super()._get_counterpart_move_line_vals(invoice=invoice)
+ if self._context.get('hr_return_advance', False):
+ res['account_id'] = emp_advance.property_account_expense_id.id
+ return res
diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py
new file mode 100644
index 000000000..58a9ab8ff
--- /dev/null
+++ b/hr_expense_advance_clearing/models/hr_expense.py
@@ -0,0 +1,81 @@
+# Copyright 2019 Kitti Upariphutthiphong
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import api, fields, models, _
+from odoo.exceptions import ValidationError
+
+
+class HrExpense(models.Model):
+ _inherit = 'hr.expense'
+
+ advance = fields.Boolean(
+ string='Employee Advance',
+ default=False,
+ )
+
+ @api.multi
+ @api.constrains('advance')
+ def _check_advance(self):
+ for expense in self.filtered('advance'):
+ emp_advance = self.env.ref('hr_expense_advance_clearing.'
+ 'product_emp_advance')
+ if not emp_advance.property_account_expense_id:
+ raise ValidationError(
+ _('Employee advance product has no payable account'))
+ if expense.product_id != emp_advance:
+ raise ValidationError(
+ _('Employee advance, selected product is not valid'))
+ if expense.tax_ids:
+ raise ValidationError(
+ _('Employee advance, all taxes must be removed'))
+ if expense.payment_mode != 'own_account':
+ raise ValidationError(
+ _('Employee advance, paid by must be employee'))
+ return True
+
+ @api.onchange('advance')
+ def onchange_advance(self):
+ self.tax_ids = False
+ self.payment_mode = 'own_account'
+ if self.advance:
+ self.product_id = self.env.ref(
+ 'hr_expense_advance_clearing.product_emp_advance')
+ else:
+ self.product_id = False
+
+ @api.multi
+ def _get_account_move_line_values(self):
+ move_line_values_by_expense = super()._get_account_move_line_values()
+ # Only when do the clearing, change cr payable to cr advance
+ emp_advance = self.env.ref('hr_expense_advance_clearing.'
+ 'product_emp_advance')
+ sheets = self.mapped('sheet_id').filtered('advance_sheet_id')
+ sheets_x = sheets.filtered(lambda x: x.advance_sheet_residual <= 0.0)
+ if sheets_x: # Advance Sheets with no residual left
+ raise ValidationError(_('Advance: %s has no amount to clear') %
+ ', '.join(sheets_x.mapped('name')))
+ for sheet in sheets:
+ advance_to_clear = sheet.advance_sheet_residual
+ for expense_id, move_lines in move_line_values_by_expense.items():
+ payable_move_line = False
+ for move_line in move_lines:
+ credit = move_line['credit']
+ if not credit:
+ continue
+ # cr payable -> cr advance
+ remain_payable = 0.0
+ if credit > advance_to_clear:
+ remain_payable = credit - advance_to_clear
+ move_line['credit'] = advance_to_clear
+ advance_to_clear = 0.0
+ # extra payable line
+ payable_move_line = move_line.copy()
+ payable_move_line['credit'] = remain_payable
+ else:
+ advance_to_clear -= credit
+ # advance line
+ move_line['account_id'] = \
+ emp_advance.property_account_expense_id.id
+ if payable_move_line:
+ move_lines.append(payable_move_line)
+ return move_line_values_by_expense
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
new file mode 100644
index 000000000..47585a110
--- /dev/null
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -0,0 +1,111 @@
+# Copyright 2019 Kitti Upariphutthiphong
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import api, models, fields, _
+from odoo.tools.safe_eval import safe_eval
+from odoo.tools import pycompat
+from odoo.exceptions import ValidationError
+
+
+class HrExpenseSheet(models.Model):
+ _inherit = 'hr.expense.sheet'
+
+ advance = fields.Boolean(
+ string='Employee Advance',
+ compute='_compute_advance',
+ store=True,
+ )
+ advance_sheet_id = fields.Many2one(
+ comodel_name='hr.expense.sheet',
+ string='Clear Advance',
+ domain="[('advance', '=', True), ('employee_id', '=', employee_id),"
+ " ('residual', '>', 0.0)]",
+ readonly=True,
+ states={'draft': [('readonly', False)],
+ 'submit': [('readonly', False)],
+ 'approve': [('readonly', False)]},
+ help="Show remaining advance of this employee",
+ )
+ residual = fields.Monetary(
+ string='Amount to clear',
+ compute='_compute_residual',
+ store=True,
+ help="Amount to clear of this expense sheet in company currency",
+ )
+ advance_sheet_residual = fields.Monetary(
+ string='Advance Remaining',
+ related='advance_sheet_id.residual',
+ store=True,
+ help="Remaining amount to clear the selected advance sheet",
+ )
+ amount_payable = fields.Monetary(
+ string='Payable Amount',
+ compute='_compute_amount_payable',
+ help="Final regiter payment amount even after advance clearing",
+ )
+
+ @api.multi
+ @api.depends('expense_line_ids')
+ def _compute_advance(self):
+ for sheet in self:
+ sheet.advance = bool(sheet.expense_line_ids.filtered('advance') and
+ len(sheet.expense_line_ids) == 1)
+ return
+
+ @api.one
+ @api.constrains('advance_sheet_id', 'expense_line_ids')
+ def _check_advance_expense(self):
+ advance_lines = self.expense_line_ids.filtered('advance')
+ if self.advance_sheet_id and advance_lines:
+ raise ValidationError(_('Advance clearing must not contain any '
+ 'advance expense line'))
+ if advance_lines and len(self.expense_line_ids) != 1:
+ raise ValidationError(_('Advance must contain only 1 '
+ 'advance expense line'))
+
+ @api.one
+ @api.depends('account_move_id.line_ids.amount_residual')
+ def _compute_residual(self):
+ residual_company = 0.0
+ emp_advance = self.env.ref('hr_expense_advance_clearing.'
+ 'product_emp_advance')
+ for line in self.sudo().account_move_id.line_ids:
+ if line.account_id == emp_advance.property_account_expense_id:
+ residual_company += line.amount_residual
+ self.residual = residual_company
+
+ @api.multi
+ def _compute_amount_payable(self):
+ for sheet in self:
+ rec_lines = sheet.account_move_id.line_ids.filtered(
+ lambda x:
+ x.credit and x.account_id.reconcile and not x.reconciled)
+ sheet.amount_payable = -sum(rec_lines.mapped('amount_residual'))
+
+ @api.multi
+ def action_sheet_move_create(self):
+ res = super(HrExpenseSheet, self).action_sheet_move_create()
+ # Reconcile advance of this sheet with the advance_sheet
+ emp_advance = self.env.ref('hr_expense_advance_clearing.'
+ 'product_emp_advance')
+ for sheet in self:
+ move_lines = sheet.account_move_id.line_ids | \
+ sheet.advance_sheet_id.account_move_id.line_ids
+ account_id = emp_advance.property_account_expense_id.id
+ adv_move_lines = self.env['account.move.line'].sudo().search([
+ ('id', 'in', move_lines.ids), ('account_id', '=', account_id)])
+ adv_move_lines.reconcile()
+ return res
+
+ @api.multi
+ def open_clear_advance(self):
+ self.ensure_one()
+ action = self.env.ref('hr_expense_advance_clearing.'
+ 'action_hr_expense_sheet_advance_clearing')
+ vals = action.read()[0]
+ context1 = vals.get('context', {})
+ if isinstance(context1, pycompat.string_types):
+ context1 = safe_eval(context1)
+ context1['default_advance_sheet_id'] = self.id
+ vals['context'] = context1
+ return vals
diff --git a/hr_expense_advance_clearing/readme/CONFIGURE.rst b/hr_expense_advance_clearing/readme/CONFIGURE.rst
new file mode 100644
index 000000000..4af425617
--- /dev/null
+++ b/hr_expense_advance_clearing/readme/CONFIGURE.rst
@@ -0,0 +1,10 @@
+This module will create a new product "Employee Advance" automatically.
+You will need to setup the Expense Account of this product to your Employee Advance account manually.
+
+* Open Product window and search for "Employee Advance"
+* On Accounting tab, select appropriate employee advance account from your chart of account
+
+Note:
+
+* You will need the "Show Full Accounting Features" to see accounting data
+* Employee Advance account code, if not already exists, you can create one. Use type = Current Asset and check Allow Reconciliation.
diff --git a/hr_expense_advance_clearing/readme/CONTRIBUTORS.rst b/hr_expense_advance_clearing/readme/CONTRIBUTORS.rst
new file mode 100644
index 000000000..033e67f43
--- /dev/null
+++ b/hr_expense_advance_clearing/readme/CONTRIBUTORS.rst
@@ -0,0 +1 @@
+* Kitti Upariphutthiphong
diff --git a/hr_expense_advance_clearing/readme/DESCRIPTION.rst b/hr_expense_advance_clearing/readme/DESCRIPTION.rst
new file mode 100644
index 000000000..8dd91962f
--- /dev/null
+++ b/hr_expense_advance_clearing/readme/DESCRIPTION.rst
@@ -0,0 +1,11 @@
+Standard Expenses module allow employee to do the expense reimbursement only after the expense has been made.
+In other world, employee will need to pay first and reimburse later.
+
+This module, allow company to advance an amount to the employee.
+Employee can then use that advance amount to purchase product/service first, then back to company and do the clearing.
+
+There can be 3 scenarios for advance and clearing
+
+* When clearing amount = advance amount, no other operation is required.
+* When clearing amount > advance amount, company will pay the extra to employee.
+* When clearing amount < advance amount, employee will return the remain to company.
diff --git a/hr_expense_advance_clearing/readme/INSTALL.rst b/hr_expense_advance_clearing/readme/INSTALL.rst
new file mode 100644
index 000000000..f381baecb
--- /dev/null
+++ b/hr_expense_advance_clearing/readme/INSTALL.rst
@@ -0,0 +1 @@
+* No specific installation required
diff --git a/hr_expense_advance_clearing/readme/USAGE.rst b/hr_expense_advance_clearing/readme/USAGE.rst
new file mode 100644
index 000000000..6f9499b71
--- /dev/null
+++ b/hr_expense_advance_clearing/readme/USAGE.rst
@@ -0,0 +1,32 @@
+**Create an Employee Advance**
+
+#. Go to Expenses > My Expenses
+#. Create a new Expense as normal, but also check "Employee Advance" checkbox
+#. Product = Employee Advance will be set automatically, do not change.
+#. Set the unit price to advance amount
+#. Click Create Report as normal. Please note that, this expense report will also has flag "Employee Advance" checked.
+#. As normal, do Submit to Manager > Approve > Post Journal Entries > Register Payment.
+#. As this is Advance, you will see new field "Amount to clear".
+
+**Clear Advance**
+
+#. Go to Expenses > My Expense Report.
+#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance.
+#. Open an Advance which is now in paid status with some Amount to be cleared.
+#. Click button "Clear Advance", system will create new Expense Report with reference to the previous step Advance.
+#. Add or create Expense line(s) as normal.
+#. As normal, do Submit to Manager > Approve > Post Journal Entries
+
+Note:
+
+* If the total expense amount less than or equal to the advance amount, the status will be set to Paid right after post journal entries.
+* If the total expense amount more than the advance amount, Register Payment will pay the extra amount then set state to Paid.
+
+**Return Advance**
+
+#. Go to Expenses > My Expense Report.
+#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance.
+#. Open an Advance which is now in paid status with some Amount to be cleared.
+#. Click button "Return Advance" will open Register Payment wizard with Amount to clear.
+#. Click button "Validate" to return that amount back
+#. All returned, Amount to clear is now equal to 0.0
diff --git a/hr_expense_advance_clearing/static/description/icon.png b/hr_expense_advance_clearing/static/description/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d
GIT binary patch
literal 9455
zcmW++2RxMjAAjx~&dlBk9S+%}OXg)AGE&Cb*&}d0jUxM@u(PQx^-s)697TX`ehR4?GS^qbkof1cslKgkU)h65qZ9Oc=ml_0temigYLJfnz{IDzUf>bGs4N!v3=Z3jMq&A#7%rM5eQ#dc?k~!
zVpnB`o+K7|Al`Q_U;eD$B
zfJtP*jH`siUq~{KE)`jP2|#TUEFGRryE2`i0**z#*^6~AI|YzIWy$Cu#CSLW3q=GA
z6`?GZymC;dCPk~rBS%eCb`5OLr;RUZ;D`}um=H)BfVIq%7VhiMr)_#G0N#zrNH|__
zc+blN2UAB0=617@>_u;MPHN;P;N#YoE=)R#i$k_`UAA>WWCcEVMh~L_
zj--gtp&|K1#58Yz*AHCTMziU1Jzt_jG0I@qAOHsk$2}yTmVkBp_eHuY$A9)>P6o~I
z%aQ?!(GqeQ-Y+b0I(m9pwgi(IIZZzsbMv+9w{PFtd_<_(LA~0H(xz{=FhLB@(1&qHA5EJw1>>=%q2f&^X>IQ{!GJ4e9U
z&KlB)z(84HmNgm2hg2C0>WM{E(DdPr+EeU_N@57;PC2&DmGFW_9kP&%?X4}+xWi)(
z;)z%wI5>D4a*5XwD)P--sPkoY(a~WBw;E~AW`Yue4kFa^LM3X`8x|}ZUeMnqr}>kH
zG%WWW>3ml$Yez?i%)2pbKPI7?5o?hydokgQyZsNEr{a|mLdt;X2TX(#B1j35xPnPW
z*bMSSOauW>o;*=kO8ojw91VX!qoOQb)zHJ!odWB}d+*K?#sY_jqPdg{Sm2HdYzdEx
zOGVPhVRTGPtv0o}RfVP;Nd(|CB)I;*t&QO8h
zFfekr30S!-LHmV_Su-W+rEwYXJ^;6&3|L$mMC8*bQptyOo9;>Qb9Q9`ySe3%V$A*9
zeKEe+b0{#KWGp$F+tga)0RtI)nhMa-K@JS}2krK~n8vJ=Ngm?R!9G<~RyuU0d?nz#
z-5EK$o(!F?hmX*2Yt6+coY`6jGbb7tF#6nHA
zuKk=GGJ;ZwON1iAfG$E#Y7MnZVmrY|j0eVI(DN_MNFJmyZ|;w4tf@=CCDZ#5N_0K=
z$;R~bbk?}TpfDjfB&aiQ$VA}s?P}xPERJG{kxk5~R`iRS(SK5d+Xs9swCozZISbnS
zk!)I0>t=A<-^z(cmSFz3=jZ23u13X><0b)P)^1T_))Kr`e!-pb#q&J*Q`p+B6la%C
zuVl&0duN<;uOsB3%T9Fp8t{ED108)`y_~Hnd9AUX7h-H?jVuU|}My+C=TjH(jKz
zqMVr0re3S$H@t{zI95qa)+Crz*5Zj}Ao%4Z><+W(nOZd?gDnfNBC3>M8WE61$So|P
zVvqH0SNtDTcsUdzaMDpT=Ty0pDHHNL@Z0w$Y`XO
z2M-_r1S+GaH%pz#Uy0*w$Vdl=X=rQXEzO}d6J^R6zjM1u&c9vYLvLp?W7w(?np9x1
zE_0JSAJCPB%i7p*Wvg)pn5T`8k3-uR?*NT|J`eS#_#54p>!p(mLDvmc-3o0mX*mp_
zN*AeS<>#^-{S%W<*mz^!X$w_2dHWpcJ6^j64qFBft-o}o_Vx80o0>}Du;>kLts;$8
zC`7q$QI(dKYG`Wa8#wl@V4jVWBRGQ@1dr-hstpQL)Tl+aqVpGpbSfN>5i&QMXfiZ>
zaA?T1VGe?rpQ@;+pkrVdd{klI&jVS@I5_iz!=UMpTsa~mBga?1r}aRBm1WS;TT*s0f0lY=JBl66Upy)-k4J}lh=P^8(SXk~0xW=T9v*B|gzIhN
z>qsO7dFd~mgxAy4V?&)=5ieYq?zi?ZEoj)&2o)RLy=@hbCRcfT5jigwtQGE{L*8<@Yd{zg;CsL5mvzfDY}P-wos_6PfprFVaeqNE%h
zKZhLtcQld;ZD+>=nqN~>GvROfueSzJD&BE*}XfU|H&(FssBqY=hPCt`d
zH?@s2>I(|;fcW&YM6#V#!kUIP8$Nkdh0A(bEVj``-AAyYgwY~jB
zT|I7Bf@%;7aL7Wf4dZ%VqF$eiaC38OV6oy3Z#TER2G+fOCd9Iaoy6aLYbPTN{XRPz
z;U!V|vBf%H!}52L2gH_+j;`bTcQRXB+y9onc^wLm5wi3-Be}U>k_u>2Eg$=k!(l@I
zcCg+flakT2Nej3i0yn+g+}%NYb?ta;R?(g5SnwsQ49U8Wng8d|{B+lyRcEDvR3+`O{zfmrmvFrL6acVP%yG98X
zo&+VBg@px@i)%o?dG(`T;n*$S5*rnyiR#=wW}}GsAcfyQpE|>a{=$Hjg=-*_K;UtD
z#z-)AXwSRY?OPefw^iI+
z)AXz#PfEjlwTes|_{sB?4(O@fg0AJ^g8gP}ex9Ucf*@_^J(s_5jJV}c)s$`Myn|Kd
z$6>}#q^n{4vN@+Os$m7KV+`}c%4)4pv@06af4-x5#wj!KKb%caK{A&Y#Rfs
z-po?Dcb1({W=6FKIUirH&(yg=*6aLCekcKwyfK^JN5{wcA3nhO(o}SK#!CINhI`-I
z1)6&n7O&ZmyFMuNwvEic#IiOAwNkR=u5it{B9n2sAJV5pNhar=j5`*N!Na;c7g!l$
z3aYBqUkqqTJ=Re-;)s!EOeij=7SQZ3Hq}ZRds%IM*PtM$wV
z@;rlc*NRK7i3y5BETSKuumEN`Xu_8GP1Ri=OKQ$@I^ko8>H6)4rjiG5{VBM>B|%`&&s^)jS|-_95&yc=GqjNo{zFkw%%HHhS~e=s
zD#sfS+-?*t|J!+ozP6KvtOl!R)@@-z24}`9{QaVLD^9VCSR2b`b!KC#o;Ki<+wXB6
zx3&O0LOWcg4&rv4QG0)4yb}7BFSEg~=IR5#ZRj8kg}dS7_V&^%#Do==#`u
zpy6{ox?jWuR(;pg+f@mT>#HGWHAJRRDDDv~@(IDw&R>9643kK#HN`!1vBJHnC+RM&yIh8{gG2q
zA%e*U3|N0XSRa~oX-3EAneep)@{h2vvd3Xvy$7og(sayr@95+e6~Xvi1tUqnIxoIH
zVWo*OwYElb#uyW{Imam6f2rGbjR!Y3`#gPqkv57dB6K^wRGxc9B(t|aYDGS=m$&S!NmCtrMMaUg(c
zc2qC=2Z`EEFMW-me5B)24AqF*bV5Dr-M5ig(l-WPS%CgaPzs6p_gnCIvTJ=Y<6!gT
zVt@AfYCzjjsMEGi=rDQHo0yc;HqoRNnNFeWZgcm?f;cp(6CNylj36DoL(?TS7eU#+
z7&mfr#y))+CJOXQKUMZ7QIdS9@#-}7y2K1{8)cCt0~-X0O!O?Qx#E4Og+;A2SjalQ
zs7r?qn0H044=sDN$SRG$arw~n=+T_DNdSrarmu)V6@|?1-ZB#hRn`uilTGPJ@fqEy
zGt(f0B+^JDP&f=r{#Y_wi#AVDf-y!RIXU^0jXsFpf>=Ji*TeqSY!H~AMbJdCGLhC)
zn7Rx+sXw6uYj;WRYrLd^5IZq@6JI1C^YkgnedZEYy<&4(z%Q$5yv#Boo{AH8n$a
zhb4Y3PWdr269&?V%uI$xMcUrMzl=;w<_nm*qr=c3Rl@i5wWB;e-`t7D&c-mcQl7x!
zZWB`UGcw=Y2=}~wzrfLx=uet<;m3~=8I~ZRuzvMQUQdr+yTV|ATf1Uuomr__nDf=X
zZ3WYJtHp_ri(}SQAPjv+Y+0=fH4krOP@S&=zZ-t1jW1o@}z;xk8
z(Nz1co&El^HK^NrhVHa-_;&88vTU>_J33=%{if;BEY*J#1n59=07jrGQ#IP>@u#3A
z;!q+E1Rj3ZJ+!4bq9F8PXJ@yMgZL;>&gYA0%_Kbi8?S=XGM~dnQZQ!yBSgcZhY96H
zrWnU;k)qy`rX&&xlDyA%(a1Hhi5CWkmg(`Gb%m(HKi-7Z!LKGRP_B8@`7&hdDy5n=
z`OIxqxiVfX@OX1p(mQu>0Ai*v_cTMiw4qRt3~NBvr9oBy0)r>w3p~V0SCm=An6@3n)>@z!|o-$HvDK
z|3D2ZMJkLE5loMKl6R^ez@Zz%S$&mbeoqH5`Bb){Ei21q&VP)hWS2tjShfFtGE+$z
zzCR$P#uktu+#!w)cX!lWN1XU%K-r=s{|j?)Akf@q#3b#{6cZCuJ~gCxuMXRmI$nGtnH+-h
z+GEi!*X=AP<|fG`1>MBdTb?28JYc=fGvAi2I<$B(rs$;eoJCyR6_bc~p!XR@O-+sD
z=eH`-ye})I5ic1eL~TDmtfJ|8`0VJ*Yr=hNCd)G1p2MMz4C3^Mj?7;!w|Ly%JqmuW
zlIEW^Ft%z?*|fpXda>Jr^1noFZEwFgVV%|*XhH@acv8rdGxeEX{M$(vG{Zw+x(ei@
zmfXb22}8-?Fi`vo-YVrTH*C?a8%M=Hv9MqVH7H^J$KsD?>!SFZ;ZsvnHr_gn=7acz
z#W?0eCdVhVMWN12VV^$>WlQ?f;P^{(&pYTops|btm6aj>_Uz+hqpGwB)vWp0Cf5y<
zft8-je~nn?W11plq}N)4A{l8I7$!ks_x$PXW-2XaRFswX_BnF{R#6YIwMhAgd5F9X
zGmwdadS6(a^fjHtXg8=l?Rc0Sm%hk6E9!5cLVloEy4eh(=FwgP`)~I^5~pBEWo+F6
zSf2ncyMurJN91#cJTy_u8Y}@%!bq1RkGC~-bV@SXRd4F{R-*V`bS+6;W5vZ(&+I<9$;-V|eNfLa5n-6%
z2(}&uGRF;p92eS*sE*oR$@pexaqr*meB)VhmIg@h{uzkk$9~qh#cHhw#>O%)b@+(|
z^IQgqzuj~Sk(J;swEM-3TrJAPCq9k^^^`q{IItKBRXYe}e0Tdr=Huf7da3$l4PdpwWDop%^}n;dD#K4s#DYA8SHZ
z&1!riV4W4R7R#C))JH1~axJ)RYnM$$lIR%6fIVA@zV{XVyx}C+a-Dt8Y9M)^KU0+H
zR4IUb2CJ{Hg>CuaXtD50jB(_Tcx=Z$^WYu2u5kubqmwp%drJ6
z?Fo40g!Qd<-l=TQxqHEOuPX0;^z7iX?Ke^a%XT<13TA^5`4Xcw6D@Ur&VT&CUe0d}
z1GjOVF1^L@>O)l@?bD~$wzgf(nxX1OGD8fEV?TdJcZc2KoUe|oP1#=$$7ee|xbY)A
zDZq+cuTpc(fFdj^=!;{k03C69lMQ(|>uhRfRu%+!k&YOi-3|1QKB
z
z?n?eq1XP>p-IM$Z^C;2L3itnbJZAip*Zo0aw2bs8@(s^~*8T9go!%dHcAz2lM;`yp
zD=7&xjFV$S&5uDaiScyD?B-i1ze`+CoRtz`Wn+Zls4&}MO{@N!ufrzjG$B79)Y2d3tBk&)TxUTw@QS0TEL_?njX|@vq?Uz(nBFK5Pq7*xj#u*R&i|?7+6#
z+|r_n#SW&LXhtheZdah{ZVoqwyT{D>MC3nkFF#N)xLi{p7J1jXlmVeb;cP5?e(=f#
zuT7fvjSbjS781v?7{)-X3*?>tq?)Yd)~|1{BDS(pqC
zC}~H#WXlkUW*H5CDOo<)#x7%RY)A;ShGhI5s*#cRDA8YgqG(HeKDx+#(ZQ?386dv!
zlXCO)w91~Vw4AmOcATuV653fa9R$fyK8ul%rG
z-wfS
zihugoZyr38Im?Zuh6@RcF~t1anQu7>#lPpb#}4cOA!EM11`%f*07RqOVkmX{p~KJ9
z^zP;K#|)$`^Rb{rnHGH{~>1(fawV0*Z#)}M`m8-?ZJV<+e}s9wE#
z)l&az?w^5{)`S(%MRzxdNqrs1n*-=jS^_jqE*5XDrA0+VE`5^*p3CuM<&dZEeCjoz
zR;uu_H9ZPZV|fQq`Cyw4nscrVwi!fE6ciMmX$!_hN7uF;jjKG)d2@aC4ropY)8etW=xJvni)8eHi`H$%#zn^WJ5NLc-rqk|u&&4Z6fD_m&JfSI1Bvb?b<*n&sfl0^t
z=HnmRl`XrFvMKB%9}>PaA`m-fK6a0(8=qPkWS5bb4=v?XcWi&hRY?O5HdulRi4?fN
zlsJ*N-0Qw+Yic@s0(2uy%F@ib;GjXt01Fmx5XbRo6+n|pP(&nodMoap^z{~q
ziEeaUT@Mxe3vJSfI6?uLND(CNr=#^W<1b}jzW58bIfyWTDle$mmS(|x-0|2UlX+9k
zQ^EX7Nw}?EzVoBfT(-LT|=9N@^hcn-_p&sqG
z&*oVs2JSU+N4ZD`FhCAWaS;>|wH2G*Id|?pa#@>tyxX`+4HyIArWDvVrX)2WAOQff
z0qyHu&-S@i^MS-+j--!pr4fPBj~_8({~e1bfcl0wI1kaoN>mJL6KUPQm5N7lB(ui1
zE-o%kq)&djzWJ}ob<-GfDlkB;F31j-VHKvQUGQ3sp`CwyGJk_i!y^sD0fqC@$9|jO
zOqN!r!8-p==F@ZVP=U$qSpY(gQ0)59P1&t@y?5rvg<}E+GB}26NYPp4f2YFQrQtot5mn3wu_qprZ=>Ig-$
zbW26Ws~IgY>}^5w`vTB(G`PTZaDiGBo5o(tp)qli|NeV(
z@H_=R8V39rt5J5YB2Ky?4eJJ#b`_iBe2ot~6%7mLt5t8Vwi^Jy7|jWXqa3amOIoRb
zOr}WVFP--DsS`1WpN%~)t3R!arKF^Q$e12KEqU36AWwnCBICpH4XCsfnyrHr>$I$4
z!DpKX$OKLWarN7nv@!uIA+~RNO)l$$w}p(;b>mx8pwYvu;dD_unryX_NhT8*Tj>BTrTTL&!?O+%Rv;b?B??gSzdp?6Uug9{
zd@V08Z$BdI?fpoCS$)t4mg4rT8Q_I}h`0d-vYZ^|dOB*Q^S|xqTV*vIg?@fVFSmMpaw0qtTRbx}
z({Pg?#{2`sc9)M5N$*N|4;^t$+QP?#mov
zGVC@I*lBVrOU-%2y!7%)fAKjpEFsgQc4{amtiHb95KQEwvf<(3T<9-Zm$xIew#P22
zc2Ix|App^>v6(3L_MCU0d3W##AB0M~3D00EWoKZqsJYT(#@w$Y_H7G22M~ApVFTRHMI_3be)Lkn#0F*V8Pq
zc}`Cjy$bE;FJ6H7p=0y#R>`}-m4(0F>%@P|?7fx{=R^uFdISRnZ2W_xQhD{YuR3t<
z{6yxu=4~JkeA;|(J6_nv#>Nvs&FuLA&PW^he@t(UwFFE8)|a!R{`E`K`i^ZnyE4$k
z;(749Ix|oi$c3QbEJ3b~D_kQsPz~fIUKym($a_7dJ?o+40*OLl^{=&oq$<#Q(yyrp
z{J-FAniyAw9tPbe&IhQ|a`DqFTVQGQ&Gq3!C2==4x{6EJwiPZ8zub-iXoUtkJiG{}
zPaR&}_fn8_z~(=;5lD-aPWD3z8PZS@AaUiomF!G8I}Mf>e~0g#BelA-5#`cj;O5>N
Xviia!U7SGha1wx#SCgwmn*{w2TRX*I
literal 0
HcmV?d00001
diff --git a/hr_expense_advance_clearing/static/description/index.html b/hr_expense_advance_clearing/static/description/index.html
new file mode 100644
index 000000000..9da747210
--- /dev/null
+++ b/hr_expense_advance_clearing/static/description/index.html
@@ -0,0 +1,487 @@
+
+
+
+
+
+
+Employee Advance and Clearing
+
+
+
+
+
Employee Advance and Clearing
+
+
+
+
Standard Expenses module allow employee to do the expense reimbursement only after the expense has been made.
+In other world, employee will need to pay first and reimburse later.
+
This module, allow company to advance an amount to the employee.
+Employee can then use that advance amount to purchase product/service first, then back to company and do the clearing.
+
There can be 3 scenarios for advance and clearing
+
+When clearing amount = advance amount, no other operation is required.
+When clearing amount > advance amount, company will pay the extra to employee.
+When clearing amount < advance amount, employee will return the remain to company.
+
+
Table of contents
+
+
+
+
+No specific installation required
+
+
+
+
+
This module will create a new product “Employee Advance” automatically.
+You will need to setup the Expense Account of this product to your Employee Advance account manually.
+
+Open Product window and search for “Employee Advance”
+On Accounting tab, select appropriate employee advance account from your chart of account
+
+
Note:
+
+You will need the “Show Full Accounting Features” to see accounting data
+Employee Advance account code, if not already exists, you can create one. Use type = Current Asset and check Allow Reconciliation.
+
+
+
+
+
Create an Employee Advance
+
+Go to Expenses > My Expenses
+Create a new Expense as normal, but also check “Employee Advance” checkbox
+Product = Employee Advance will be set automatically, do not change.
+Set the unit price to advance amount
+Click Create Report as normal. Please note that, this expense report will also has flag “Employee Advance” checked.
+As normal, do Submit to Manager > Approve > Post Journal Entries > Register Payment.
+As this is Advance, you will see new field “Amount to clear”.
+
+
Clear Advance
+
+Go to Expenses > My Expense Report.
+Search for the Advance you want to clear, or use filter “Advance (not cleared)” to see all uncleared advance.
+Open an Advance which is now in paid status with some Amount to be cleared.
+Click button “Clear Advance”, system will create new Expense Report with reference to the previous step Advance.
+Add or create Expense line(s) as normal.
+As normal, do Submit to Manager > Approve > Post Journal Entries
+
+
Note:
+
+If the total expense amount less than or equal to the advance amount, the status will be set to Paid right after post journal entries.
+If the total expense amount more than the advance amount, Register Payment will pay the extra amount then set state to Paid.
+
+
Return Advance
+
+Go to Expenses > My Expense Report.
+Search for the Advance you want to clear, or use filter “Advance (not cleared)” to see all uncleared advance.
+Open an Advance which is now in paid status with some Amount to be cleared.
+Click button “Return Advance” will open Register Payment wizard with Amount to clear.
+Click button “Validate” to return that amount back
+All returned, Amount to clear is now equal to 0.0
+
+
+
+
+
Bugs are tracked on GitHub Issues .
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+feedback .
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
+
+
+
+
+
This module is maintained by the OCA.
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
This module is part of the OCA/hr project on GitHub.
+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute .
+
+
+
+
+
diff --git a/hr_expense_advance_clearing/tests/__init__.py b/hr_expense_advance_clearing/tests/__init__.py
new file mode 100644
index 000000000..6867ba88a
--- /dev/null
+++ b/hr_expense_advance_clearing/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_hr_expense_advance_clearing
diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
new file mode 100644
index 000000000..5b99ff158
--- /dev/null
+++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
@@ -0,0 +1,205 @@
+# Copyright 2019 Kitti Upariphutthiphong
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo.tests import common
+from odoo.tests.common import Form
+from odoo.exceptions import ValidationError
+
+
+class TestHrExpenseAdvanceClearing(common.SavepointCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestHrExpenseAdvanceClearing, cls).setUpClass()
+ company = cls.env.ref('base.main_company')
+ cls.journal_bank = cls.env['account.journal'].search(
+ [('type', '=', 'bank')], limit=1)
+ cls.product = cls.env['product.product'].create({
+ 'name': 'Service 1',
+ 'type': 'service',
+ })
+ tax_account = cls.env['account.account'].search([
+ ('company_id', '=', company.id),
+ ('user_type_id', '=', cls.env.ref(
+ 'account.data_account_type_non_current_liabilities').id)
+ ], limit=1)
+ tax_group = cls.env['account.tax.group'].create({
+ 'name': 'Tax Group 1', 'sequence': 1, })
+ cls.tax = cls.env['account.tax'].create({
+ 'name': 'Tax 10.0%',
+ 'amount': 10.0,
+ 'amount_type': 'percent',
+ 'type_tax_use': 'purchase',
+ 'account_id': tax_account.id,
+ 'company_id': company.id,
+ 'refund_account_id': tax_account.id,
+ 'tax_group_id': tax_group.id,
+ })
+ employee_home = cls.env['res.partner'].create({
+ 'name': 'Employee Home Address',
+ })
+ cls.employee = cls.env['hr.employee'].create({
+ 'name': 'Employee A',
+ 'address_home_id': employee_home.id,
+ })
+ advance_account = cls.env['account.account'].create({
+ 'code': '154000',
+ 'name': 'Employee Advance',
+ 'user_type_id': cls.env.ref(
+ 'account.data_account_type_current_assets').id,
+ 'reconcile': True,
+ })
+ cls.emp_advance = cls.env.ref('hr_expense_advance_clearing.'
+ 'product_emp_advance')
+ cls.emp_advance.property_account_expense_id = advance_account
+ # Create advance expense 1,000
+ cls.advance = cls._create_expense_sheet(
+ cls, 'Advance 1,000', cls.employee, cls.emp_advance, 1000.0,
+ advance=True)
+ # Create clearing expense 1,000
+ cls.clearing_equal = cls._create_expense_sheet(
+ cls, 'Buy service 1,000', cls.employee, cls.product, 1000.0)
+ # Create clearing expense 1,200
+ cls.clearing_more = cls._create_expense_sheet(
+ cls, 'Buy service 1,200', cls.employee, cls.product, 1200.0)
+ # Create clearing expense 800
+ cls.clearing_less = cls._create_expense_sheet(
+ cls, 'Buy service 800', cls.employee, cls.product, 800.0)
+
+ def _create_expense(self, description, employee,
+ product, amount, advance=False,
+ payment_mode='own_account'):
+ with Form(self.env['hr.expense']) as expense:
+ expense.advance = advance
+ expense.name = description
+ expense.employee_id = employee
+ expense.product_id = product
+ expense.unit_amount = amount
+ expense.payment_mode = payment_mode
+ expense = expense.save()
+ expense.tax_ids = False # Test no vat
+ return expense
+
+ def _create_expense_sheet(self, description, employee,
+ product, amount, advance=False):
+ expense = self._create_expense(self, description, employee,
+ product, amount, advance)
+ # Add expense to expense sheet
+ expense_sheet = self.env['hr.expense.sheet'].create({
+ 'name': description,
+ 'employee_id': expense.employee_id.id,
+ 'expense_line_ids': [(6, 0, [expense.id])],
+ })
+ return expense_sheet
+
+ def _register_payment(self, expense_sheet, hr_return_advance=False):
+ ctx = {'active_ids': [expense_sheet.id],
+ 'active_id': expense_sheet.id,
+ 'hr_return_advance': hr_return_advance, }
+ PaymentWizard = self.env['hr.expense.sheet.register.payment.wizard']
+ with Form(PaymentWizard.with_context(ctx)) as f:
+ f.journal_id = self.journal_bank
+ payment_wizard = f.save()
+ payment_wizard.expense_post_payment()
+
+ def test_0_test_constraints(self):
+ """ Test some constraints """
+ # Advance Sheet can't be clearing at the same time.
+ with self.assertRaises(ValidationError):
+ self.advance.advance_sheet_id = self.advance
+ # Advance Sheet should not have > 1 expense lines
+ with self.assertRaises(ValidationError):
+ expense = self._create_expense(
+ 'Buy service 1,000', self.employee, self.product, 1.0)
+ self.advance.write({'expense_line_ids': [(4, expense.id)]})
+ # Advance Expense's product must be employee advance
+ with self.assertRaises(ValidationError):
+ expense = self._create_expense(
+ 'Advance 1,000', self.employee, self.product, 1.0,
+ advance=True)
+ # Advance Expense's product, must not has tax involved
+ with self.assertRaises(ValidationError):
+ self.emp_advance.supplier_taxes_id |= self.tax
+ expense = self._create_expense(
+ 'Advance 1,000', self.employee, self.emp_advance, 1.0,
+ advance=True)
+ self.emp_advance.supplier_taxes_id = False # Remove tax bf proceed
+ # Advance Expense, must not paid by company
+ with self.assertRaises(ValidationError):
+ expense = self._create_expense(
+ 'Advance 1,000', self.employee, self.emp_advance, 1.0,
+ advance=True, payment_mode='company_account')
+ # Advance Expense's product must have account configured
+ with self.assertRaises(ValidationError):
+ self.emp_advance.property_account_expense_id = False
+ expense = self._create_expense(
+ 'Advance 1,000', self.employee, self.emp_advance, 1.0,
+ advance=True)
+
+ def test_1_clear_equal_advance(self):
+ """ When clear equal advance, all set """
+ # ------------------ Advance --------------------------
+ self.advance.action_submit_sheet()
+ self.advance.approve_expense_sheets()
+ self.advance.action_sheet_move_create()
+ self.assertEqual(self.advance.residual, 1000.0)
+ self._register_payment(self.advance)
+ self.assertEqual(self.advance.state, 'done')
+ # ------------------ Clearing --------------------------
+ # Clear this with previous advance
+ vals = self.advance.open_clear_advance() # Test Clear Advance button
+ ctx = vals.get('context')
+ self.assertEqual(ctx['default_advance_sheet_id'], self.advance.id)
+ self.clearing_equal.advance_sheet_id = self.advance
+ self.assertEqual(self.clearing_equal.advance_sheet_residual, 1000.0)
+ self.clearing_equal.action_submit_sheet()
+ self.clearing_equal.approve_expense_sheets()
+ self.clearing_equal.action_sheet_move_create()
+ # Equal amount, state change to Paid and advance is cleared
+ self.assertEqual(self.clearing_equal.state, 'done')
+ self.assertEqual(self.clearing_equal.advance_sheet_residual, 0.0)
+
+ def test_2_clear_more_than_advance(self):
+ """ When clear more than advance, do pay more """
+ # ------------------ Advance --------------------------
+ self.advance.action_submit_sheet()
+ self.advance.approve_expense_sheets()
+ self.advance.action_sheet_move_create()
+ self.assertEqual(self.advance.residual, 1000.0)
+ self._register_payment(self.advance)
+ self.assertEqual(self.advance.state, 'done')
+ # ------------------ Clearing --------------------------
+ # Clear this with previous advance
+ self.clearing_more.advance_sheet_id = self.advance
+ self.assertEqual(self.clearing_more.advance_sheet_residual, 1000.0)
+ self.clearing_more.action_submit_sheet()
+ self.clearing_more.approve_expense_sheets()
+ self.clearing_more.action_sheet_move_create()
+ # More amount, state not changed to paid, and has to pay 200 more
+ self.assertEqual(self.clearing_more.state, 'post')
+ self.assertEqual(self.clearing_more.amount_payable, 200.0)
+ self._register_payment(self.clearing_more)
+ self.assertEqual(self.clearing_more.state, 'done')
+
+ def test_3_clear_less_than_advance(self):
+ """ When clear less than advance, do return advance """
+ # ------------------ Advance --------------------------
+ self.advance.action_submit_sheet()
+ self.advance.approve_expense_sheets()
+ self.advance.action_sheet_move_create()
+ self.assertEqual(self.advance.residual, 1000.0)
+ self._register_payment(self.advance)
+ self.assertEqual(self.advance.state, 'done')
+ # ------------------ Clearing --------------------------
+ # Clear this with previous advance
+ self.clearing_less.advance_sheet_id = self.advance
+ self.assertEqual(self.clearing_less.advance_sheet_residual, 1000.0)
+ self.clearing_less.action_submit_sheet()
+ self.clearing_less.approve_expense_sheets()
+ self.clearing_less.action_sheet_move_create()
+ # Less amount, state set to done. Still remain 200 to be returned
+ self.assertEqual(self.clearing_less.state, 'done')
+ self.assertEqual(self.clearing_less.advance_sheet_residual, 200.0)
+ # Back to advance and do return advance, residual become 0.0
+ self._register_payment(self.advance, hr_return_advance=True)
+ self.assertEqual(self.advance.residual, 0.0)
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
new file mode 100644
index 000000000..0d5802d88
--- /dev/null
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -0,0 +1,70 @@
+
+
+
+ hr.expense.sheet.filter
+ hr.expense.sheet
+
+
+
+
+
+
+
+
+
+
+
+ Advance Clearing
+ hr.expense.sheet
+ form
+
+ [('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')]
+ {'search_default_my_reports': 1}
+
+
+ Create a new expense report
+
+ Once you have created your expense, submit it to your manager who will validate it.
+
+
+
+
+
+ hr.expense.form
+ hr.expense
+
+
+
+
+
+
+
+
+
+ view.hr.expense.sheet.form
+ hr.expense.sheet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hr_expense_advance_clearing/wizard/__init__.py b/hr_expense_advance_clearing/wizard/__init__.py
new file mode 100644
index 000000000..f41f99dba
--- /dev/null
+++ b/hr_expense_advance_clearing/wizard/__init__.py
@@ -0,0 +1 @@
+from . import hr_expense_sheet_register_payment
diff --git a/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py b/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
new file mode 100644
index 000000000..a847130e5
--- /dev/null
+++ b/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
@@ -0,0 +1,65 @@
+# Copyright 2019 Kitti Upariphutthiphong
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import api, models, _
+from werkzeug.urls import url_encode
+
+
+class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):
+ _inherit = 'hr.expense.sheet.register.payment.wizard'
+
+ @api.model
+ def default_get(self, field_list):
+ res = super().default_get(field_list)
+ ctx = self._context.copy()
+ sheet = self.env['hr.expense.sheet'].browse(ctx.get('active_id'))
+ if ctx.get('hr_return_advance', False):
+ res.update({'amount': sheet.residual})
+ else:
+ res.update({'amount': sheet.amount_payable})
+ return res
+
+ @api.multi
+ def expense_post_payment(self):
+ if self._context.get('hr_return_advance', False):
+ return self.expense_post_return_advance()
+ return super().expense_post_payment()
+
+ @api.multi
+ def expense_post_return_advance(self):
+ """ This is opposite operation of expense_post_payment(),
+ it return remaining advance from employee back to company
+ """
+ self.ensure_one()
+ context = dict(self._context or {})
+ active_ids = context.get('active_ids', [])
+ expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)
+
+ # Create return advance and post it
+ payment = self.env['account.payment'].create(self._get_payment_vals())
+ # Set new payment_type and payment entry to be Dr Bank, Cr Advance
+ payment.payment_type = 'inbound'
+ payment.with_context(hr_return_advance=True).post()
+
+ # Log the return advance in the chatter
+ body = (
+ _("A remaining advance return of %s %s with the reference "
+ "%s related to your expense %s "
+ "has been made.") %
+ (payment.amount, payment.currency_id.symbol,
+ url_encode({'model': 'account.payment', 'res_id': payment.id}),
+ payment.name, expense_sheet.name))
+ expense_sheet.message_post(body=body)
+
+ # Reconcile the return advance and the advance,
+ # i.e. lookup on the advance account on move lines
+ emp_advance = self.env.ref('hr_expense_advance_clearing.'
+ 'product_emp_advance')
+ account_move_lines_to_reconcile = self.env['account.move.line']
+ for line in \
+ payment.move_line_ids + expense_sheet.account_move_id.line_ids:
+ if line.account_id == emp_advance.property_account_expense_id:
+ account_move_lines_to_reconcile |= line
+ account_move_lines_to_reconcile.reconcile()
+
+ return {'type': 'ir.actions.act_window_close'}
From d5b269cee138995bfe3a5a82ddba097db95bc174 Mon Sep 17 00:00:00 2001
From: kittiu
Date: Mon, 9 Dec 2019 10:48:37 +0700
Subject: [PATCH 33/75] [12.0][FIX] hr_expense_advance_clearing
Fix install error when there are already some expense in system.
[UPD] README.rst
hr_expense_advance_clearing 12.0.1.0.1
(cherry picked from commit 1f3ec1e43be03fd6610c6adbfea454cc54d6d6b8)
---
hr_expense_advance_clearing/README.rst | 8 ++++++++
hr_expense_advance_clearing/__manifest__.py | 3 ++-
hr_expense_advance_clearing/models/hr_expense_sheet.py | 9 +++++----
.../static/description/index.html | 2 ++
4 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/hr_expense_advance_clearing/README.rst b/hr_expense_advance_clearing/README.rst
index 35059b9fd..56b1e127e 100644
--- a/hr_expense_advance_clearing/README.rst
+++ b/hr_expense_advance_clearing/README.rst
@@ -133,6 +133,14 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
+.. |maintainer-kittiu| image:: https://github.com/kittiu.png?size=40px
+ :target: https://github.com/kittiu
+ :alt: kittiu
+
+Current `maintainer `__:
+
+|maintainer-kittiu|
+
This module is part of the `OCA/hr `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/hr_expense_advance_clearing/__manifest__.py b/hr_expense_advance_clearing/__manifest__.py
index ffceed8a1..4b0563dbe 100644
--- a/hr_expense_advance_clearing/__manifest__.py
+++ b/hr_expense_advance_clearing/__manifest__.py
@@ -3,7 +3,7 @@
{
'name': 'Employee Advance and Clearing',
- 'version': '12.0.1.0.0',
+ 'version': '12.0.1.0.1',
'category': 'Human Resources',
'author': 'Ecosoft, '
'Odoo Community Association (OCA)',
@@ -17,4 +17,5 @@
'views/hr_expense_views.xml',
],
'installable': True,
+ 'maintainers': ['kittiu'],
}
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index 47585a110..96465b012 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -68,10 +68,11 @@ def _check_advance_expense(self):
def _compute_residual(self):
residual_company = 0.0
emp_advance = self.env.ref('hr_expense_advance_clearing.'
- 'product_emp_advance')
- for line in self.sudo().account_move_id.line_ids:
- if line.account_id == emp_advance.property_account_expense_id:
- residual_company += line.amount_residual
+ 'product_emp_advance', False)
+ if emp_advance:
+ for line in self.sudo().account_move_id.line_ids:
+ if line.account_id == emp_advance.property_account_expense_id:
+ residual_company += line.amount_residual
self.residual = residual_company
@api.multi
diff --git a/hr_expense_advance_clearing/static/description/index.html b/hr_expense_advance_clearing/static/description/index.html
index 9da747210..4bf9479ba 100644
--- a/hr_expense_advance_clearing/static/description/index.html
+++ b/hr_expense_advance_clearing/static/description/index.html
@@ -478,6 +478,8 @@
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
+Current maintainer :
+
This module is part of the OCA/hr project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute .
From db2b074781123580a103cb22b7b11a8db94d8ace Mon Sep 17 00:00:00 2001
From: kittiu
Date: Fri, 20 Mar 2020 18:33:41 +0700
Subject: [PATCH 34/75] [IMP] : black, isort
(cherry picked from commit df4f68f889f28ccf34d6d407b3f997dd613e1dcb)
---
hr_expense_advance_clearing/__manifest__.py | 26 +--
.../data/advance_product.xml | 8 +-
.../models/account_payment.py | 9 +-
.../models/hr_expense.py | 59 +++---
.../models/hr_expense_sheet.py | 100 +++++----
.../tests/test_hr_expense_advance_clearing.py | 198 ++++++++++--------
.../views/hr_expense_views.xml | 87 +++++---
.../hr_expense_sheet_register_payment.py | 51 ++---
8 files changed, 304 insertions(+), 234 deletions(-)
diff --git a/hr_expense_advance_clearing/__manifest__.py b/hr_expense_advance_clearing/__manifest__.py
index 4b0563dbe..17aae78ca 100644
--- a/hr_expense_advance_clearing/__manifest__.py
+++ b/hr_expense_advance_clearing/__manifest__.py
@@ -2,20 +2,14 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
- 'name': 'Employee Advance and Clearing',
- 'version': '12.0.1.0.1',
- 'category': 'Human Resources',
- 'author': 'Ecosoft, '
- 'Odoo Community Association (OCA)',
- 'license': 'AGPL-3',
- 'website': 'https://github.com/OCA/hr',
- 'depends': [
- 'hr_expense',
- ],
- 'data': [
- 'data/advance_product.xml',
- 'views/hr_expense_views.xml',
- ],
- 'installable': True,
- 'maintainers': ['kittiu'],
+ "name": "Employee Advance and Clearing",
+ "version": "12.0.1.0.1",
+ "category": "Human Resources",
+ "author": "Ecosoft, " "Odoo Community Association (OCA)",
+ "license": "AGPL-3",
+ "website": "https://github.com/OCA/hr",
+ "depends": ["hr_expense",],
+ "data": ["data/advance_product.xml", "views/hr_expense_views.xml",],
+ "installable": True,
+ "maintainers": ["kittiu"],
}
diff --git a/hr_expense_advance_clearing/data/advance_product.xml b/hr_expense_advance_clearing/data/advance_product.xml
index 3784ef122..cca45ffa2 100644
--- a/hr_expense_advance_clearing/data/advance_product.xml
+++ b/hr_expense_advance_clearing/data/advance_product.xml
@@ -1,11 +1,11 @@
Employee Advance
-
+
service
-
-
-
+
+
+
False
False
diff --git a/hr_expense_advance_clearing/models/account_payment.py b/hr_expense_advance_clearing/models/account_payment.py
index 165563790..b7275bb29 100644
--- a/hr_expense_advance_clearing/models/account_payment.py
+++ b/hr_expense_advance_clearing/models/account_payment.py
@@ -5,12 +5,11 @@
class AccountPayment(models.Model):
- _inherit = 'account.payment'
+ _inherit = "account.payment"
def _get_counterpart_move_line_vals(self, invoice=False):
- emp_advance = self.env.ref('hr_expense_advance_clearing.'
- 'product_emp_advance')
+ emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance")
res = super()._get_counterpart_move_line_vals(invoice=invoice)
- if self._context.get('hr_return_advance', False):
- res['account_id'] = emp_advance.property_account_expense_id.id
+ if self._context.get("hr_return_advance", False):
+ res["account_id"] = emp_advance.property_account_expense_id.id
return res
diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py
index 58a9ab8ff..0995eadcd 100644
--- a/hr_expense_advance_clearing/models/hr_expense.py
+++ b/hr_expense_advance_clearing/models/hr_expense.py
@@ -1,45 +1,44 @@
# Copyright 2019 Kitti Upariphutthiphong
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import api, fields, models, _
+from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class HrExpense(models.Model):
- _inherit = 'hr.expense'
+ _inherit = "hr.expense"
- advance = fields.Boolean(
- string='Employee Advance',
- default=False,
- )
+ advance = fields.Boolean(string="Employee Advance", default=False,)
@api.multi
- @api.constrains('advance')
+ @api.constrains("advance")
def _check_advance(self):
- for expense in self.filtered('advance'):
- emp_advance = self.env.ref('hr_expense_advance_clearing.'
- 'product_emp_advance')
+ for expense in self.filtered("advance"):
+ emp_advance = self.env.ref(
+ "hr_expense_advance_clearing." "product_emp_advance"
+ )
if not emp_advance.property_account_expense_id:
raise ValidationError(
- _('Employee advance product has no payable account'))
+ _("Employee advance product has no payable account")
+ )
if expense.product_id != emp_advance:
raise ValidationError(
- _('Employee advance, selected product is not valid'))
+ _("Employee advance, selected product is not valid")
+ )
if expense.tax_ids:
- raise ValidationError(
- _('Employee advance, all taxes must be removed'))
- if expense.payment_mode != 'own_account':
- raise ValidationError(
- _('Employee advance, paid by must be employee'))
+ raise ValidationError(_("Employee advance, all taxes must be removed"))
+ if expense.payment_mode != "own_account":
+ raise ValidationError(_("Employee advance, paid by must be employee"))
return True
- @api.onchange('advance')
+ @api.onchange("advance")
def onchange_advance(self):
self.tax_ids = False
- self.payment_mode = 'own_account'
+ self.payment_mode = "own_account"
if self.advance:
self.product_id = self.env.ref(
- 'hr_expense_advance_clearing.product_emp_advance')
+ "hr_expense_advance_clearing.product_emp_advance"
+ )
else:
self.product_id = False
@@ -47,35 +46,35 @@ def onchange_advance(self):
def _get_account_move_line_values(self):
move_line_values_by_expense = super()._get_account_move_line_values()
# Only when do the clearing, change cr payable to cr advance
- emp_advance = self.env.ref('hr_expense_advance_clearing.'
- 'product_emp_advance')
- sheets = self.mapped('sheet_id').filtered('advance_sheet_id')
+ emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance")
+ sheets = self.mapped("sheet_id").filtered("advance_sheet_id")
sheets_x = sheets.filtered(lambda x: x.advance_sheet_residual <= 0.0)
if sheets_x: # Advance Sheets with no residual left
- raise ValidationError(_('Advance: %s has no amount to clear') %
- ', '.join(sheets_x.mapped('name')))
+ raise ValidationError(
+ _("Advance: %s has no amount to clear")
+ % ", ".join(sheets_x.mapped("name"))
+ )
for sheet in sheets:
advance_to_clear = sheet.advance_sheet_residual
for expense_id, move_lines in move_line_values_by_expense.items():
payable_move_line = False
for move_line in move_lines:
- credit = move_line['credit']
+ credit = move_line["credit"]
if not credit:
continue
# cr payable -> cr advance
remain_payable = 0.0
if credit > advance_to_clear:
remain_payable = credit - advance_to_clear
- move_line['credit'] = advance_to_clear
+ move_line["credit"] = advance_to_clear
advance_to_clear = 0.0
# extra payable line
payable_move_line = move_line.copy()
- payable_move_line['credit'] = remain_payable
+ payable_move_line["credit"] = remain_payable
else:
advance_to_clear -= credit
# advance line
- move_line['account_id'] = \
- emp_advance.property_account_expense_id.id
+ move_line["account_id"] = emp_advance.property_account_expense_id.id
if payable_move_line:
move_lines.append(payable_move_line)
return move_line_values_by_expense
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index 96465b012..f77b216cb 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -1,74 +1,79 @@
# Copyright 2019 Kitti Upariphutthiphong
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import api, models, fields, _
-from odoo.tools.safe_eval import safe_eval
-from odoo.tools import pycompat
+from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
+from odoo.tools import pycompat
+from odoo.tools.safe_eval import safe_eval
class HrExpenseSheet(models.Model):
- _inherit = 'hr.expense.sheet'
+ _inherit = "hr.expense.sheet"
advance = fields.Boolean(
- string='Employee Advance',
- compute='_compute_advance',
- store=True,
+ string="Employee Advance", compute="_compute_advance", store=True,
)
advance_sheet_id = fields.Many2one(
- comodel_name='hr.expense.sheet',
- string='Clear Advance',
+ comodel_name="hr.expense.sheet",
+ string="Clear Advance",
domain="[('advance', '=', True), ('employee_id', '=', employee_id),"
- " ('residual', '>', 0.0)]",
+ " ('residual', '>', 0.0)]",
readonly=True,
- states={'draft': [('readonly', False)],
- 'submit': [('readonly', False)],
- 'approve': [('readonly', False)]},
+ states={
+ "draft": [("readonly", False)],
+ "submit": [("readonly", False)],
+ "approve": [("readonly", False)],
+ },
help="Show remaining advance of this employee",
)
residual = fields.Monetary(
- string='Amount to clear',
- compute='_compute_residual',
+ string="Amount to clear",
+ compute="_compute_residual",
store=True,
help="Amount to clear of this expense sheet in company currency",
)
advance_sheet_residual = fields.Monetary(
- string='Advance Remaining',
- related='advance_sheet_id.residual',
+ string="Advance Remaining",
+ related="advance_sheet_id.residual",
store=True,
help="Remaining amount to clear the selected advance sheet",
)
amount_payable = fields.Monetary(
- string='Payable Amount',
- compute='_compute_amount_payable',
+ string="Payable Amount",
+ compute="_compute_amount_payable",
help="Final regiter payment amount even after advance clearing",
)
@api.multi
- @api.depends('expense_line_ids')
+ @api.depends("expense_line_ids")
def _compute_advance(self):
for sheet in self:
- sheet.advance = bool(sheet.expense_line_ids.filtered('advance') and
- len(sheet.expense_line_ids) == 1)
+ sheet.advance = bool(
+ sheet.expense_line_ids.filtered("advance")
+ and len(sheet.expense_line_ids) == 1
+ )
return
@api.one
- @api.constrains('advance_sheet_id', 'expense_line_ids')
+ @api.constrains("advance_sheet_id", "expense_line_ids")
def _check_advance_expense(self):
- advance_lines = self.expense_line_ids.filtered('advance')
+ advance_lines = self.expense_line_ids.filtered("advance")
if self.advance_sheet_id and advance_lines:
- raise ValidationError(_('Advance clearing must not contain any '
- 'advance expense line'))
+ raise ValidationError(
+ _("Advance clearing must not contain any " "advance expense line")
+ )
if advance_lines and len(self.expense_line_ids) != 1:
- raise ValidationError(_('Advance must contain only 1 '
- 'advance expense line'))
+ raise ValidationError(
+ _("Advance must contain only 1 " "advance expense line")
+ )
@api.one
- @api.depends('account_move_id.line_ids.amount_residual')
+ @api.depends("account_move_id.line_ids.amount_residual")
def _compute_residual(self):
residual_company = 0.0
- emp_advance = self.env.ref('hr_expense_advance_clearing.'
- 'product_emp_advance', False)
+ emp_advance = self.env.ref(
+ "hr_expense_advance_clearing." "product_emp_advance", False
+ )
if emp_advance:
for line in self.sudo().account_move_id.line_ids:
if line.account_id == emp_advance.property_account_expense_id:
@@ -79,34 +84,39 @@ def _compute_residual(self):
def _compute_amount_payable(self):
for sheet in self:
rec_lines = sheet.account_move_id.line_ids.filtered(
- lambda x:
- x.credit and x.account_id.reconcile and not x.reconciled)
- sheet.amount_payable = -sum(rec_lines.mapped('amount_residual'))
+ lambda x: x.credit and x.account_id.reconcile and not x.reconciled
+ )
+ sheet.amount_payable = -sum(rec_lines.mapped("amount_residual"))
@api.multi
def action_sheet_move_create(self):
res = super(HrExpenseSheet, self).action_sheet_move_create()
# Reconcile advance of this sheet with the advance_sheet
- emp_advance = self.env.ref('hr_expense_advance_clearing.'
- 'product_emp_advance')
+ emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance")
for sheet in self:
- move_lines = sheet.account_move_id.line_ids | \
- sheet.advance_sheet_id.account_move_id.line_ids
+ move_lines = (
+ sheet.account_move_id.line_ids
+ | sheet.advance_sheet_id.account_move_id.line_ids
+ )
account_id = emp_advance.property_account_expense_id.id
- adv_move_lines = self.env['account.move.line'].sudo().search([
- ('id', 'in', move_lines.ids), ('account_id', '=', account_id)])
+ adv_move_lines = (
+ self.env["account.move.line"]
+ .sudo()
+ .search([("id", "in", move_lines.ids), ("account_id", "=", account_id)])
+ )
adv_move_lines.reconcile()
return res
@api.multi
def open_clear_advance(self):
self.ensure_one()
- action = self.env.ref('hr_expense_advance_clearing.'
- 'action_hr_expense_sheet_advance_clearing')
+ action = self.env.ref(
+ "hr_expense_advance_clearing." "action_hr_expense_sheet_advance_clearing"
+ )
vals = action.read()[0]
- context1 = vals.get('context', {})
+ context1 = vals.get("context", {})
if isinstance(context1, pycompat.string_types):
context1 = safe_eval(context1)
- context1['default_advance_sheet_id'] = self.id
- vals['context'] = context1
+ context1["default_advance_sheet_id"] = self.id
+ vals["context"] = context1
return vals
diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
index 5b99ff158..a15a61358 100644
--- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
+++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
@@ -1,75 +1,95 @@
# Copyright 2019 Kitti Upariphutthiphong
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+from odoo.exceptions import ValidationError
from odoo.tests import common
from odoo.tests.common import Form
-from odoo.exceptions import ValidationError
class TestHrExpenseAdvanceClearing(common.SavepointCase):
-
@classmethod
def setUpClass(cls):
super(TestHrExpenseAdvanceClearing, cls).setUpClass()
- company = cls.env.ref('base.main_company')
- cls.journal_bank = cls.env['account.journal'].search(
- [('type', '=', 'bank')], limit=1)
- cls.product = cls.env['product.product'].create({
- 'name': 'Service 1',
- 'type': 'service',
- })
- tax_account = cls.env['account.account'].search([
- ('company_id', '=', company.id),
- ('user_type_id', '=', cls.env.ref(
- 'account.data_account_type_non_current_liabilities').id)
- ], limit=1)
- tax_group = cls.env['account.tax.group'].create({
- 'name': 'Tax Group 1', 'sequence': 1, })
- cls.tax = cls.env['account.tax'].create({
- 'name': 'Tax 10.0%',
- 'amount': 10.0,
- 'amount_type': 'percent',
- 'type_tax_use': 'purchase',
- 'account_id': tax_account.id,
- 'company_id': company.id,
- 'refund_account_id': tax_account.id,
- 'tax_group_id': tax_group.id,
- })
- employee_home = cls.env['res.partner'].create({
- 'name': 'Employee Home Address',
- })
- cls.employee = cls.env['hr.employee'].create({
- 'name': 'Employee A',
- 'address_home_id': employee_home.id,
- })
- advance_account = cls.env['account.account'].create({
- 'code': '154000',
- 'name': 'Employee Advance',
- 'user_type_id': cls.env.ref(
- 'account.data_account_type_current_assets').id,
- 'reconcile': True,
- })
- cls.emp_advance = cls.env.ref('hr_expense_advance_clearing.'
- 'product_emp_advance')
+ company = cls.env.ref("base.main_company")
+ cls.journal_bank = cls.env["account.journal"].search(
+ [("type", "=", "bank")], limit=1
+ )
+ cls.product = cls.env["product.product"].create(
+ {"name": "Service 1", "type": "service",}
+ )
+ tax_account = cls.env["account.account"].search(
+ [
+ ("company_id", "=", company.id),
+ (
+ "user_type_id",
+ "=",
+ cls.env.ref("account.data_account_type_non_current_liabilities").id,
+ ),
+ ],
+ limit=1,
+ )
+ tax_group = cls.env["account.tax.group"].create(
+ {"name": "Tax Group 1", "sequence": 1,}
+ )
+ cls.tax = cls.env["account.tax"].create(
+ {
+ "name": "Tax 10.0%",
+ "amount": 10.0,
+ "amount_type": "percent",
+ "type_tax_use": "purchase",
+ "account_id": tax_account.id,
+ "company_id": company.id,
+ "refund_account_id": tax_account.id,
+ "tax_group_id": tax_group.id,
+ }
+ )
+ employee_home = cls.env["res.partner"].create(
+ {"name": "Employee Home Address",}
+ )
+ cls.employee = cls.env["hr.employee"].create(
+ {"name": "Employee A", "address_home_id": employee_home.id,}
+ )
+ advance_account = cls.env["account.account"].create(
+ {
+ "code": "154000",
+ "name": "Employee Advance",
+ "user_type_id": cls.env.ref(
+ "account.data_account_type_current_assets"
+ ).id,
+ "reconcile": True,
+ }
+ )
+ cls.emp_advance = cls.env.ref(
+ "hr_expense_advance_clearing." "product_emp_advance"
+ )
cls.emp_advance.property_account_expense_id = advance_account
# Create advance expense 1,000
cls.advance = cls._create_expense_sheet(
- cls, 'Advance 1,000', cls.employee, cls.emp_advance, 1000.0,
- advance=True)
+ cls, "Advance 1,000", cls.employee, cls.emp_advance, 1000.0, advance=True
+ )
# Create clearing expense 1,000
cls.clearing_equal = cls._create_expense_sheet(
- cls, 'Buy service 1,000', cls.employee, cls.product, 1000.0)
+ cls, "Buy service 1,000", cls.employee, cls.product, 1000.0
+ )
# Create clearing expense 1,200
cls.clearing_more = cls._create_expense_sheet(
- cls, 'Buy service 1,200', cls.employee, cls.product, 1200.0)
+ cls, "Buy service 1,200", cls.employee, cls.product, 1200.0
+ )
# Create clearing expense 800
cls.clearing_less = cls._create_expense_sheet(
- cls, 'Buy service 800', cls.employee, cls.product, 800.0)
+ cls, "Buy service 800", cls.employee, cls.product, 800.0
+ )
- def _create_expense(self, description, employee,
- product, amount, advance=False,
- payment_mode='own_account'):
- with Form(self.env['hr.expense']) as expense:
+ def _create_expense(
+ self,
+ description,
+ employee,
+ product,
+ amount,
+ advance=False,
+ payment_mode="own_account",
+ ):
+ with Form(self.env["hr.expense"]) as expense:
expense.advance = advance
expense.name = description
expense.employee_id = employee
@@ -80,23 +100,29 @@ def _create_expense(self, description, employee,
expense.tax_ids = False # Test no vat
return expense
- def _create_expense_sheet(self, description, employee,
- product, amount, advance=False):
- expense = self._create_expense(self, description, employee,
- product, amount, advance)
+ def _create_expense_sheet(
+ self, description, employee, product, amount, advance=False
+ ):
+ expense = self._create_expense(
+ self, description, employee, product, amount, advance
+ )
# Add expense to expense sheet
- expense_sheet = self.env['hr.expense.sheet'].create({
- 'name': description,
- 'employee_id': expense.employee_id.id,
- 'expense_line_ids': [(6, 0, [expense.id])],
- })
+ expense_sheet = self.env["hr.expense.sheet"].create(
+ {
+ "name": description,
+ "employee_id": expense.employee_id.id,
+ "expense_line_ids": [(6, 0, [expense.id])],
+ }
+ )
return expense_sheet
def _register_payment(self, expense_sheet, hr_return_advance=False):
- ctx = {'active_ids': [expense_sheet.id],
- 'active_id': expense_sheet.id,
- 'hr_return_advance': hr_return_advance, }
- PaymentWizard = self.env['hr.expense.sheet.register.payment.wizard']
+ ctx = {
+ "active_ids": [expense_sheet.id],
+ "active_id": expense_sheet.id,
+ "hr_return_advance": hr_return_advance,
+ }
+ PaymentWizard = self.env["hr.expense.sheet.register.payment.wizard"]
with Form(PaymentWizard.with_context(ctx)) as f:
f.journal_id = self.journal_bank
payment_wizard = f.save()
@@ -110,31 +136,37 @@ def test_0_test_constraints(self):
# Advance Sheet should not have > 1 expense lines
with self.assertRaises(ValidationError):
expense = self._create_expense(
- 'Buy service 1,000', self.employee, self.product, 1.0)
- self.advance.write({'expense_line_ids': [(4, expense.id)]})
+ "Buy service 1,000", self.employee, self.product, 1.0
+ )
+ self.advance.write({"expense_line_ids": [(4, expense.id)]})
# Advance Expense's product must be employee advance
with self.assertRaises(ValidationError):
expense = self._create_expense(
- 'Advance 1,000', self.employee, self.product, 1.0,
- advance=True)
+ "Advance 1,000", self.employee, self.product, 1.0, advance=True
+ )
# Advance Expense's product, must not has tax involved
with self.assertRaises(ValidationError):
self.emp_advance.supplier_taxes_id |= self.tax
expense = self._create_expense(
- 'Advance 1,000', self.employee, self.emp_advance, 1.0,
- advance=True)
+ "Advance 1,000", self.employee, self.emp_advance, 1.0, advance=True
+ )
self.emp_advance.supplier_taxes_id = False # Remove tax bf proceed
# Advance Expense, must not paid by company
with self.assertRaises(ValidationError):
expense = self._create_expense(
- 'Advance 1,000', self.employee, self.emp_advance, 1.0,
- advance=True, payment_mode='company_account')
+ "Advance 1,000",
+ self.employee,
+ self.emp_advance,
+ 1.0,
+ advance=True,
+ payment_mode="company_account",
+ )
# Advance Expense's product must have account configured
with self.assertRaises(ValidationError):
self.emp_advance.property_account_expense_id = False
expense = self._create_expense(
- 'Advance 1,000', self.employee, self.emp_advance, 1.0,
- advance=True)
+ "Advance 1,000", self.employee, self.emp_advance, 1.0, advance=True
+ )
def test_1_clear_equal_advance(self):
""" When clear equal advance, all set """
@@ -144,19 +176,19 @@ def test_1_clear_equal_advance(self):
self.advance.action_sheet_move_create()
self.assertEqual(self.advance.residual, 1000.0)
self._register_payment(self.advance)
- self.assertEqual(self.advance.state, 'done')
+ self.assertEqual(self.advance.state, "done")
# ------------------ Clearing --------------------------
# Clear this with previous advance
vals = self.advance.open_clear_advance() # Test Clear Advance button
- ctx = vals.get('context')
- self.assertEqual(ctx['default_advance_sheet_id'], self.advance.id)
+ ctx = vals.get("context")
+ self.assertEqual(ctx["default_advance_sheet_id"], self.advance.id)
self.clearing_equal.advance_sheet_id = self.advance
self.assertEqual(self.clearing_equal.advance_sheet_residual, 1000.0)
self.clearing_equal.action_submit_sheet()
self.clearing_equal.approve_expense_sheets()
self.clearing_equal.action_sheet_move_create()
# Equal amount, state change to Paid and advance is cleared
- self.assertEqual(self.clearing_equal.state, 'done')
+ self.assertEqual(self.clearing_equal.state, "done")
self.assertEqual(self.clearing_equal.advance_sheet_residual, 0.0)
def test_2_clear_more_than_advance(self):
@@ -167,7 +199,7 @@ def test_2_clear_more_than_advance(self):
self.advance.action_sheet_move_create()
self.assertEqual(self.advance.residual, 1000.0)
self._register_payment(self.advance)
- self.assertEqual(self.advance.state, 'done')
+ self.assertEqual(self.advance.state, "done")
# ------------------ Clearing --------------------------
# Clear this with previous advance
self.clearing_more.advance_sheet_id = self.advance
@@ -176,10 +208,10 @@ def test_2_clear_more_than_advance(self):
self.clearing_more.approve_expense_sheets()
self.clearing_more.action_sheet_move_create()
# More amount, state not changed to paid, and has to pay 200 more
- self.assertEqual(self.clearing_more.state, 'post')
+ self.assertEqual(self.clearing_more.state, "post")
self.assertEqual(self.clearing_more.amount_payable, 200.0)
self._register_payment(self.clearing_more)
- self.assertEqual(self.clearing_more.state, 'done')
+ self.assertEqual(self.clearing_more.state, "done")
def test_3_clear_less_than_advance(self):
""" When clear less than advance, do return advance """
@@ -189,7 +221,7 @@ def test_3_clear_less_than_advance(self):
self.advance.action_sheet_move_create()
self.assertEqual(self.advance.residual, 1000.0)
self._register_payment(self.advance)
- self.assertEqual(self.advance.state, 'done')
+ self.assertEqual(self.advance.state, "done")
# ------------------ Clearing --------------------------
# Clear this with previous advance
self.clearing_less.advance_sheet_id = self.advance
@@ -198,7 +230,7 @@ def test_3_clear_less_than_advance(self):
self.clearing_less.approve_expense_sheets()
self.clearing_less.action_sheet_move_create()
# Less amount, state set to done. Still remain 200 to be returned
- self.assertEqual(self.clearing_less.state, 'done')
+ self.assertEqual(self.clearing_less.state, "done")
self.assertEqual(self.clearing_less.advance_sheet_residual, 200.0)
# Back to advance and do return advance, residual become 0.0
self._register_payment(self.advance, hr_return_advance=True)
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 0d5802d88..5852779d6 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -1,70 +1,103 @@
-
hr.expense.sheet.filter
hr.expense.sheet
-
+
-
-
-
+
+
+
-
Advance Clearing
hr.expense.sheet
form
-
- [('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')]
+
+ [('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')]
{'search_default_my_reports': 1}
-
+
Create a new expense report
-
+
+
Once you have created your expense, submit it to your manager who will validate it.
-
hr.expense.form
hr.expense
-
+
-
+
+
-
view.hr.expense.sheet.form
hr.expense.sheet
-
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
diff --git a/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py b/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
index a847130e5..15c0b8bd1 100644
--- a/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
+++ b/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
@@ -1,27 +1,28 @@
# Copyright 2019 Kitti Upariphutthiphong
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import api, models, _
from werkzeug.urls import url_encode
+from odoo import _, api, models
+
class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):
- _inherit = 'hr.expense.sheet.register.payment.wizard'
+ _inherit = "hr.expense.sheet.register.payment.wizard"
@api.model
def default_get(self, field_list):
res = super().default_get(field_list)
ctx = self._context.copy()
- sheet = self.env['hr.expense.sheet'].browse(ctx.get('active_id'))
- if ctx.get('hr_return_advance', False):
- res.update({'amount': sheet.residual})
+ sheet = self.env["hr.expense.sheet"].browse(ctx.get("active_id"))
+ if ctx.get("hr_return_advance", False):
+ res.update({"amount": sheet.residual})
else:
- res.update({'amount': sheet.amount_payable})
+ res.update({"amount": sheet.amount_payable})
return res
@api.multi
def expense_post_payment(self):
- if self._context.get('hr_return_advance', False):
+ if self._context.get("hr_return_advance", False):
return self.expense_post_return_advance()
return super().expense_post_payment()
@@ -32,34 +33,36 @@ def expense_post_return_advance(self):
"""
self.ensure_one()
context = dict(self._context or {})
- active_ids = context.get('active_ids', [])
- expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)
+ active_ids = context.get("active_ids", [])
+ expense_sheet = self.env["hr.expense.sheet"].browse(active_ids)
# Create return advance and post it
- payment = self.env['account.payment'].create(self._get_payment_vals())
+ payment = self.env["account.payment"].create(self._get_payment_vals())
# Set new payment_type and payment entry to be Dr Bank, Cr Advance
- payment.payment_type = 'inbound'
+ payment.payment_type = "inbound"
payment.with_context(hr_return_advance=True).post()
# Log the return advance in the chatter
- body = (
- _("A remaining advance return of %s %s with the reference "
- "%s related to your expense %s "
- "has been made.") %
- (payment.amount, payment.currency_id.symbol,
- url_encode({'model': 'account.payment', 'res_id': payment.id}),
- payment.name, expense_sheet.name))
+ body = _(
+ "A remaining advance return of %s %s with the reference "
+ "%s related to your expense %s "
+ "has been made."
+ ) % (
+ payment.amount,
+ payment.currency_id.symbol,
+ url_encode({"model": "account.payment", "res_id": payment.id}),
+ payment.name,
+ expense_sheet.name,
+ )
expense_sheet.message_post(body=body)
# Reconcile the return advance and the advance,
# i.e. lookup on the advance account on move lines
- emp_advance = self.env.ref('hr_expense_advance_clearing.'
- 'product_emp_advance')
- account_move_lines_to_reconcile = self.env['account.move.line']
- for line in \
- payment.move_line_ids + expense_sheet.account_move_id.line_ids:
+ emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance")
+ account_move_lines_to_reconcile = self.env["account.move.line"]
+ for line in payment.move_line_ids + expense_sheet.account_move_id.line_ids:
if line.account_id == emp_advance.property_account_expense_id:
account_move_lines_to_reconcile |= line
account_move_lines_to_reconcile.reconcile()
- return {'type': 'ir.actions.act_window_close'}
+ return {"type": "ir.actions.act_window_close"}
From 7a74eea38cff5ac1aeb23777e3324a07f16ad1d4 Mon Sep 17 00:00:00 2001
From: kittiu
Date: Fri, 20 Mar 2020 18:37:39 +0700
Subject: [PATCH 35/75] [13.0][MIG] hr_expense_advance_clearing
(cherry picked from commit 00f8f4ac51a2e18bf4e6a01ed812a88b60500dba)
---
hr_expense_advance_clearing/README.rst | 146 ------------------
hr_expense_advance_clearing/__manifest__.py | 10 +-
.../data/advance_product.xml | 4 +-
.../models/__init__.py | 1 -
.../models/account_payment.py | 15 --
.../models/hr_expense.py | 10 +-
.../models/hr_expense_sheet.py | 34 ++--
.../static/description/index.html | 8 +-
.../tests/test_hr_expense_advance_clearing.py | 24 +--
.../views/hr_expense_views.xml | 14 +-
.../hr_expense_sheet_register_payment.py | 11 +-
11 files changed, 45 insertions(+), 232 deletions(-)
delete mode 100644 hr_expense_advance_clearing/README.rst
delete mode 100644 hr_expense_advance_clearing/models/account_payment.py
diff --git a/hr_expense_advance_clearing/README.rst b/hr_expense_advance_clearing/README.rst
deleted file mode 100644
index 56b1e127e..000000000
--- a/hr_expense_advance_clearing/README.rst
+++ /dev/null
@@ -1,146 +0,0 @@
-=============================
-Employee Advance and Clearing
-=============================
-
-.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! This file is generated by oca-gen-addon-readme !!
- !! changes will be overwritten. !!
- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
-.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
- :target: https://odoo-community.org/page/development-status
- :alt: Beta
-.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
- :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
- :alt: License: AGPL-3
-.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr-lightgray.png?logo=github
- :target: https://github.com/OCA/hr/tree/12.0/hr_expense_advance_clearing
- :alt: OCA/hr
-.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/hr-12-0/hr-12-0-hr_expense_advance_clearing
- :alt: Translate me on Weblate
-.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/116/12.0
- :alt: Try me on Runbot
-
-|badge1| |badge2| |badge3| |badge4| |badge5|
-
-Standard Expenses module allow employee to do the expense reimbursement only after the expense has been made.
-In other world, employee will need to pay first and reimburse later.
-
-This module, allow company to advance an amount to the employee.
-Employee can then use that advance amount to purchase product/service first, then back to company and do the clearing.
-
-There can be 3 scenarios for advance and clearing
-
-* When clearing amount = advance amount, no other operation is required.
-* When clearing amount > advance amount, company will pay the extra to employee.
-* When clearing amount < advance amount, employee will return the remain to company.
-
-**Table of contents**
-
-.. contents::
- :local:
-
-Installation
-============
-
-* No specific installation required
-
-Configuration
-=============
-
-This module will create a new product "Employee Advance" automatically.
-You will need to setup the Expense Account of this product to your Employee Advance account manually.
-
-* Open Product window and search for "Employee Advance"
-* On Accounting tab, select appropriate employee advance account from your chart of account
-
-Note:
-
-* You will need the "Show Full Accounting Features" to see accounting data
-* Employee Advance account code, if not already exists, you can create one. Use type = Current Asset and check Allow Reconciliation.
-
-Usage
-=====
-
-**Create an Employee Advance**
-
-#. Go to Expenses > My Expenses
-#. Create a new Expense as normal, but also check "Employee Advance" checkbox
-#. Product = Employee Advance will be set automatically, do not change.
-#. Set the unit price to advance amount
-#. Click Create Report as normal. Please note that, this expense report will also has flag "Employee Advance" checked.
-#. As normal, do Submit to Manager > Approve > Post Journal Entries > Register Payment.
-#. As this is Advance, you will see new field "Amount to clear".
-
-**Clear Advance**
-
-#. Go to Expenses > My Expense Report.
-#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance.
-#. Open an Advance which is now in paid status with some Amount to be cleared.
-#. Click button "Clear Advance", system will create new Expense Report with reference to the previous step Advance.
-#. Add or create Expense line(s) as normal.
-#. As normal, do Submit to Manager > Approve > Post Journal Entries
-
-Note:
-
-* If the total expense amount less than or equal to the advance amount, the status will be set to Paid right after post journal entries.
-* If the total expense amount more than the advance amount, Register Payment will pay the extra amount then set state to Paid.
-
-**Return Advance**
-
-#. Go to Expenses > My Expense Report.
-#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance.
-#. Open an Advance which is now in paid status with some Amount to be cleared.
-#. Click button "Return Advance" will open Register Payment wizard with Amount to clear.
-#. Click button "Validate" to return that amount back
-#. All returned, Amount to clear is now equal to 0.0
-
-Bug Tracker
-===========
-
-Bugs are tracked on `GitHub Issues `_.
-In case of trouble, please check there if your issue has already been reported.
-If you spotted it first, help us smashing it by providing a detailed and welcomed
-`feedback `_.
-
-Do not contact contributors directly about support or help with technical issues.
-
-Credits
-=======
-
-Authors
-~~~~~~~
-
-* Ecosoft
-
-Contributors
-~~~~~~~~~~~~
-
-* Kitti Upariphutthiphong
-
-Maintainers
-~~~~~~~~~~~
-
-This module is maintained by the OCA.
-
-.. image:: https://odoo-community.org/logo.png
- :alt: Odoo Community Association
- :target: https://odoo-community.org
-
-OCA, or the Odoo Community Association, is a nonprofit organization whose
-mission is to support the collaborative development of Odoo features and
-promote its widespread use.
-
-.. |maintainer-kittiu| image:: https://github.com/kittiu.png?size=40px
- :target: https://github.com/kittiu
- :alt: kittiu
-
-Current `maintainer `__:
-
-|maintainer-kittiu|
-
-This module is part of the `OCA/hr `_ project on GitHub.
-
-You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/hr_expense_advance_clearing/__manifest__.py b/hr_expense_advance_clearing/__manifest__.py
index 17aae78ca..2781513ef 100644
--- a/hr_expense_advance_clearing/__manifest__.py
+++ b/hr_expense_advance_clearing/__manifest__.py
@@ -3,13 +3,13 @@
{
"name": "Employee Advance and Clearing",
- "version": "12.0.1.0.1",
+ "version": "13.0.1.0.0",
"category": "Human Resources",
- "author": "Ecosoft, " "Odoo Community Association (OCA)",
+ "author": "Ecosoft,Odoo Community Association (OCA)",
"license": "AGPL-3",
- "website": "https://github.com/OCA/hr",
- "depends": ["hr_expense",],
- "data": ["data/advance_product.xml", "views/hr_expense_views.xml",],
+ "website": "https://github.com/OCA/hr-expense",
+ "depends": ["hr_expense"],
+ "data": ["data/advance_product.xml", "views/hr_expense_views.xml"],
"installable": True,
"maintainers": ["kittiu"],
}
diff --git a/hr_expense_advance_clearing/data/advance_product.xml b/hr_expense_advance_clearing/data/advance_product.xml
index cca45ffa2..0a69a9fcb 100644
--- a/hr_expense_advance_clearing/data/advance_product.xml
+++ b/hr_expense_advance_clearing/data/advance_product.xml
@@ -6,7 +6,7 @@
- False
- False
+
+
diff --git a/hr_expense_advance_clearing/models/__init__.py b/hr_expense_advance_clearing/models/__init__.py
index 01f905ef5..945a814c7 100644
--- a/hr_expense_advance_clearing/models/__init__.py
+++ b/hr_expense_advance_clearing/models/__init__.py
@@ -1,3 +1,2 @@
from . import hr_expense
from . import hr_expense_sheet
-from . import account_payment
diff --git a/hr_expense_advance_clearing/models/account_payment.py b/hr_expense_advance_clearing/models/account_payment.py
deleted file mode 100644
index b7275bb29..000000000
--- a/hr_expense_advance_clearing/models/account_payment.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2019 Kitti Upariphutthiphong
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-from odoo import models
-
-
-class AccountPayment(models.Model):
- _inherit = "account.payment"
-
- def _get_counterpart_move_line_vals(self, invoice=False):
- emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance")
- res = super()._get_counterpart_move_line_vals(invoice=invoice)
- if self._context.get("hr_return_advance", False):
- res["account_id"] = emp_advance.property_account_expense_id.id
- return res
diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py
index 0995eadcd..aa86cc18d 100644
--- a/hr_expense_advance_clearing/models/hr_expense.py
+++ b/hr_expense_advance_clearing/models/hr_expense.py
@@ -8,14 +8,13 @@
class HrExpense(models.Model):
_inherit = "hr.expense"
- advance = fields.Boolean(string="Employee Advance", default=False,)
+ advance = fields.Boolean(string="Employee Advance", default=False)
- @api.multi
@api.constrains("advance")
def _check_advance(self):
for expense in self.filtered("advance"):
emp_advance = self.env.ref(
- "hr_expense_advance_clearing." "product_emp_advance"
+ "hr_expense_advance_clearing.product_emp_advance"
)
if not emp_advance.property_account_expense_id:
raise ValidationError(
@@ -42,11 +41,10 @@ def onchange_advance(self):
else:
self.product_id = False
- @api.multi
def _get_account_move_line_values(self):
move_line_values_by_expense = super()._get_account_move_line_values()
# Only when do the clearing, change cr payable to cr advance
- emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance")
+ emp_advance = self.env.ref("hr_expense_advance_clearing.product_emp_advance")
sheets = self.mapped("sheet_id").filtered("advance_sheet_id")
sheets_x = sheets.filtered(lambda x: x.advance_sheet_residual <= 0.0)
if sheets_x: # Advance Sheets with no residual left
@@ -56,7 +54,7 @@ def _get_account_move_line_values(self):
)
for sheet in sheets:
advance_to_clear = sheet.advance_sheet_residual
- for expense_id, move_lines in move_line_values_by_expense.items():
+ for move_lines in move_line_values_by_expense.values():
payable_move_line = False
for move_line in move_lines:
credit = move_line["credit"]
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index f77b216cb..bb2ca2099 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -3,7 +3,6 @@
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
-from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
@@ -11,7 +10,7 @@ class HrExpenseSheet(models.Model):
_inherit = "hr.expense.sheet"
advance = fields.Boolean(
- string="Employee Advance", compute="_compute_advance", store=True,
+ string="Employee Advance", compute="_compute_advance", store=True
)
advance_sheet_id = fields.Many2one(
comodel_name="hr.expense.sheet",
@@ -44,7 +43,6 @@ class HrExpenseSheet(models.Model):
help="Final regiter payment amount even after advance clearing",
)
- @api.multi
@api.depends("expense_line_ids")
def _compute_advance(self):
for sheet in self:
@@ -54,33 +52,29 @@ def _compute_advance(self):
)
return
- @api.one
@api.constrains("advance_sheet_id", "expense_line_ids")
def _check_advance_expense(self):
advance_lines = self.expense_line_ids.filtered("advance")
if self.advance_sheet_id and advance_lines:
raise ValidationError(
- _("Advance clearing must not contain any " "advance expense line")
+ _("Advance clearing must not contain any advance expense line")
)
if advance_lines and len(self.expense_line_ids) != 1:
- raise ValidationError(
- _("Advance must contain only 1 " "advance expense line")
- )
+ raise ValidationError(_("Advance must contain only 1 advance expense line"))
- @api.one
@api.depends("account_move_id.line_ids.amount_residual")
def _compute_residual(self):
- residual_company = 0.0
emp_advance = self.env.ref(
- "hr_expense_advance_clearing." "product_emp_advance", False
+ "hr_expense_advance_clearing.product_emp_advance", False
)
- if emp_advance:
- for line in self.sudo().account_move_id.line_ids:
- if line.account_id == emp_advance.property_account_expense_id:
- residual_company += line.amount_residual
- self.residual = residual_company
+ for sheet in self:
+ residual_company = 0.0
+ if emp_advance:
+ for line in sheet.sudo().account_move_id.line_ids:
+ if line.account_id == emp_advance.property_account_expense_id:
+ residual_company += line.amount_residual
+ sheet.residual = residual_company
- @api.multi
def _compute_amount_payable(self):
for sheet in self:
rec_lines = sheet.account_move_id.line_ids.filtered(
@@ -88,11 +82,10 @@ def _compute_amount_payable(self):
)
sheet.amount_payable = -sum(rec_lines.mapped("amount_residual"))
- @api.multi
def action_sheet_move_create(self):
res = super(HrExpenseSheet, self).action_sheet_move_create()
# Reconcile advance of this sheet with the advance_sheet
- emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance")
+ emp_advance = self.env.ref("hr_expense_advance_clearing.product_emp_advance")
for sheet in self:
move_lines = (
sheet.account_move_id.line_ids
@@ -107,7 +100,6 @@ def action_sheet_move_create(self):
adv_move_lines.reconcile()
return res
- @api.multi
def open_clear_advance(self):
self.ensure_one()
action = self.env.ref(
@@ -115,7 +107,7 @@ def open_clear_advance(self):
)
vals = action.read()[0]
context1 = vals.get("context", {})
- if isinstance(context1, pycompat.string_types):
+ if context1:
context1 = safe_eval(context1)
context1["default_advance_sheet_id"] = self.id
vals["context"] = context1
diff --git a/hr_expense_advance_clearing/static/description/index.html b/hr_expense_advance_clearing/static/description/index.html
index 4bf9479ba..1f3c52b12 100644
--- a/hr_expense_advance_clearing/static/description/index.html
+++ b/hr_expense_advance_clearing/static/description/index.html
@@ -367,7 +367,7 @@ Employee Advance and Clearing
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-
+
Standard Expenses module allow employee to do the expense reimbursement only after the expense has been made.
In other world, employee will need to pay first and reimburse later.
This module, allow company to advance an amount to the employee.
@@ -454,7 +454,7 @@
Bugs are tracked on GitHub Issues .
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-feedback .
+feedback .
Do not contact contributors directly about support or help with technical issues.
@@ -478,9 +478,7 @@
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-
Current maintainer :
-
-
This module is part of the OCA/hr project on GitHub.
+
This module is part of the OCA/hr project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute .
diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
index a15a61358..d7ca65a9a 100644
--- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
+++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
@@ -15,21 +15,10 @@ def setUpClass(cls):
[("type", "=", "bank")], limit=1
)
cls.product = cls.env["product.product"].create(
- {"name": "Service 1", "type": "service",}
- )
- tax_account = cls.env["account.account"].search(
- [
- ("company_id", "=", company.id),
- (
- "user_type_id",
- "=",
- cls.env.ref("account.data_account_type_non_current_liabilities").id,
- ),
- ],
- limit=1,
+ {"name": "Service 1", "type": "service"}
)
tax_group = cls.env["account.tax.group"].create(
- {"name": "Tax Group 1", "sequence": 1,}
+ {"name": "Tax Group 1", "sequence": 1}
)
cls.tax = cls.env["account.tax"].create(
{
@@ -37,17 +26,13 @@ def setUpClass(cls):
"amount": 10.0,
"amount_type": "percent",
"type_tax_use": "purchase",
- "account_id": tax_account.id,
"company_id": company.id,
- "refund_account_id": tax_account.id,
"tax_group_id": tax_group.id,
}
)
- employee_home = cls.env["res.partner"].create(
- {"name": "Employee Home Address",}
- )
+ employee_home = cls.env["res.partner"].create({"name": "Employee Home Address"})
cls.employee = cls.env["hr.employee"].create(
- {"name": "Employee A", "address_home_id": employee_home.id,}
+ {"name": "Employee A", "address_home_id": employee_home.id}
)
advance_account = cls.env["account.account"].create(
{
@@ -121,6 +106,7 @@ def _register_payment(self, expense_sheet, hr_return_advance=False):
"active_ids": [expense_sheet.id],
"active_id": expense_sheet.id,
"hr_return_advance": hr_return_advance,
+ "active_model": "hr.expense.sheet",
}
PaymentWizard = self.env["hr.expense.sheet.register.payment.wizard"]
with Form(PaymentWizard.with_context(ctx)) as f:
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 5852779d6..9dd868a90 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -1,8 +1,8 @@
-
- hr.expense.sheet.filter
+
+ hr.expense.sheet.view.search
hr.expense.sheet
-
+
Advance Clearing
hr.expense.sheet
form
-
- [('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')]
+
+
+ [('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')]
+
{'search_default_my_reports': 1}
diff --git a/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py b/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
index 15c0b8bd1..3a8633a1d 100644
--- a/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
+++ b/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
@@ -20,13 +20,11 @@ def default_get(self, field_list):
res.update({"amount": sheet.amount_payable})
return res
- @api.multi
def expense_post_payment(self):
if self._context.get("hr_return_advance", False):
return self.expense_post_return_advance()
return super().expense_post_payment()
- @api.multi
def expense_post_return_advance(self):
""" This is opposite operation of expense_post_payment(),
it return remaining advance from employee back to company
@@ -35,12 +33,16 @@ def expense_post_return_advance(self):
context = dict(self._context or {})
active_ids = context.get("active_ids", [])
expense_sheet = self.env["hr.expense.sheet"].browse(active_ids)
+ emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance")
+ advance_account = emp_advance.property_account_expense_id
# Create return advance and post it
payment = self.env["account.payment"].create(self._get_payment_vals())
# Set new payment_type and payment entry to be Dr Bank, Cr Advance
payment.payment_type = "inbound"
- payment.with_context(hr_return_advance=True).post()
+ payment.partner_type = "customer"
+ payment.destination_account_id = advance_account
+ payment.post()
# Log the return advance in the chatter
body = _(
@@ -58,10 +60,9 @@ def expense_post_return_advance(self):
# Reconcile the return advance and the advance,
# i.e. lookup on the advance account on move lines
- emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance")
account_move_lines_to_reconcile = self.env["account.move.line"]
for line in payment.move_line_ids + expense_sheet.account_move_id.line_ids:
- if line.account_id == emp_advance.property_account_expense_id:
+ if line.account_id == advance_account:
account_move_lines_to_reconcile |= line
account_move_lines_to_reconcile.reconcile()
From 817787a1272a007ec1b8b58c08767f414d9b8716 Mon Sep 17 00:00:00 2001
From: david
Date: Thu, 24 Sep 2020 16:49:50 +0200
Subject: [PATCH 36/75] [FIX] hr_expense_advance_clearing: analytic account
visibility
We should prevent setting the analytic account in expenses used as
employees advance. Otherwise, it can lead to errors charging against an
analytic account that later won't be compensated when all is reconcile.
Anyway, the advance is not an expense, but money of the company moving
from one place to another, that later will be used for real expenses
that will be imputed to the corresponding analytic account
(cherry picked from commit 2723fe7bb1f104f753fdf81048d752fa9466616b)
---
hr_expense_advance_clearing/views/hr_expense_views.xml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 9dd868a90..b092da090 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -50,6 +50,11 @@
+
+ {'invisible': [('advance', '!=', False)]}
+
From 4e4e5f86d3452e9f2210765b8253d7a07078ea65 Mon Sep 17 00:00:00 2001
From: david
Date: Thu, 5 Nov 2020 11:43:26 +0100
Subject: [PATCH 37/75] [IMP] hr_expense_advance_clearing: hide analytics tags
as well
(cherry picked from commit 3b75338581ef9bd1637c3c5f2f177771e5a92fc2)
---
hr_expense_advance_clearing/views/hr_expense_views.xml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index b092da090..06f24a4bc 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -55,6 +55,11 @@
name="attrs"
>{'invisible': [('advance', '!=', False)]}
+
+ {'invisible': [('advance', '!=', False)]}
+
From f638c91f9cf6ff68afac4f32f38f8f4b23769c1c Mon Sep 17 00:00:00 2001
From: Saran440
Date: Thu, 7 Jan 2021 16:43:09 +0700
Subject: [PATCH 38/75] [FIX] rename fields residual
(cherry picked from commit c77a2bc2b928768063d8c2e7ece59e7056d2723f)
---
.../models/hr_expense_sheet.py | 12 ++++++------
.../tests/test_hr_expense_advance_clearing.py | 10 +++++-----
.../views/hr_expense_views.xml | 8 ++++----
.../wizard/hr_expense_sheet_register_payment.py | 2 +-
4 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index bb2ca2099..955802199 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -16,7 +16,7 @@ class HrExpenseSheet(models.Model):
comodel_name="hr.expense.sheet",
string="Clear Advance",
domain="[('advance', '=', True), ('employee_id', '=', employee_id),"
- " ('residual', '>', 0.0)]",
+ " ('clearing_residual', '>', 0.0)]",
readonly=True,
states={
"draft": [("readonly", False)],
@@ -25,15 +25,15 @@ class HrExpenseSheet(models.Model):
},
help="Show remaining advance of this employee",
)
- residual = fields.Monetary(
+ clearing_residual = fields.Monetary(
string="Amount to clear",
- compute="_compute_residual",
+ compute="_compute_clearing_residual",
store=True,
help="Amount to clear of this expense sheet in company currency",
)
advance_sheet_residual = fields.Monetary(
string="Advance Remaining",
- related="advance_sheet_id.residual",
+ related="advance_sheet_id.clearing_residual",
store=True,
help="Remaining amount to clear the selected advance sheet",
)
@@ -63,7 +63,7 @@ def _check_advance_expense(self):
raise ValidationError(_("Advance must contain only 1 advance expense line"))
@api.depends("account_move_id.line_ids.amount_residual")
- def _compute_residual(self):
+ def _compute_clearing_residual(self):
emp_advance = self.env.ref(
"hr_expense_advance_clearing.product_emp_advance", False
)
@@ -73,7 +73,7 @@ def _compute_residual(self):
for line in sheet.sudo().account_move_id.line_ids:
if line.account_id == emp_advance.property_account_expense_id:
residual_company += line.amount_residual
- sheet.residual = residual_company
+ sheet.clearing_residual = residual_company
def _compute_amount_payable(self):
for sheet in self:
diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
index d7ca65a9a..e5986b507 100644
--- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
+++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
@@ -160,7 +160,7 @@ def test_1_clear_equal_advance(self):
self.advance.action_submit_sheet()
self.advance.approve_expense_sheets()
self.advance.action_sheet_move_create()
- self.assertEqual(self.advance.residual, 1000.0)
+ self.assertEqual(self.advance.clearing_residual, 1000.0)
self._register_payment(self.advance)
self.assertEqual(self.advance.state, "done")
# ------------------ Clearing --------------------------
@@ -183,7 +183,7 @@ def test_2_clear_more_than_advance(self):
self.advance.action_submit_sheet()
self.advance.approve_expense_sheets()
self.advance.action_sheet_move_create()
- self.assertEqual(self.advance.residual, 1000.0)
+ self.assertEqual(self.advance.clearing_residual, 1000.0)
self._register_payment(self.advance)
self.assertEqual(self.advance.state, "done")
# ------------------ Clearing --------------------------
@@ -205,7 +205,7 @@ def test_3_clear_less_than_advance(self):
self.advance.action_submit_sheet()
self.advance.approve_expense_sheets()
self.advance.action_sheet_move_create()
- self.assertEqual(self.advance.residual, 1000.0)
+ self.assertEqual(self.advance.clearing_residual, 1000.0)
self._register_payment(self.advance)
self.assertEqual(self.advance.state, "done")
# ------------------ Clearing --------------------------
@@ -218,6 +218,6 @@ def test_3_clear_less_than_advance(self):
# Less amount, state set to done. Still remain 200 to be returned
self.assertEqual(self.clearing_less.state, "done")
self.assertEqual(self.clearing_less.advance_sheet_residual, 200.0)
- # Back to advance and do return advance, residual become 0.0
+ # Back to advance and do return advance, clearing residual become 0.0
self._register_payment(self.advance, hr_return_advance=True)
- self.assertEqual(self.advance.residual, 0.0)
+ self.assertEqual(self.advance.clearing_residual, 0.0)
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 06f24a4bc..5594a47af 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -13,7 +13,7 @@
@@ -99,7 +99,7 @@
attrs="{'invisible': [('advance', '=', True)]}"
/>
Date: Tue, 20 Jul 2021 16:32:09 +0700
Subject: [PATCH 39/75] [14.0][IMP] hr_expense_advance_clearing, add invisible
fields
(cherry picked from commit 3b9851c77b7f67aacad3946198e160d1fab52787)
---
hr_expense_advance_clearing/views/hr_expense_views.xml | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 5594a47af..fcf6bebc0 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -108,6 +108,13 @@
/>
+
+
+
+
From 95c6a3f85c640f446807408bd9e4565dc9da6a17 Mon Sep 17 00:00:00 2001
From: Kitti U
Date: Wed, 21 Jul 2021 01:10:56 +0700
Subject: [PATCH 40/75] [14.0][IMP] hr_expense_advance_clearing, allow
predefined clearing product
(cherry picked from commit c159aff1a91475e6a79954e50b3763633c95eeb6)
---
.../models/hr_expense.py | 17 +++++
.../models/hr_expense_sheet.py | 65 +++++++++++++++++--
hr_expense_advance_clearing/readme/USAGE.rst | 1 +
.../tests/test_hr_expense_advance_clearing.py | 8 +--
.../views/hr_expense_views.xml | 29 ++++++++-
5 files changed, 106 insertions(+), 14 deletions(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py
index aa86cc18d..848a60e7d 100644
--- a/hr_expense_advance_clearing/models/hr_expense.py
+++ b/hr_expense_advance_clearing/models/hr_expense.py
@@ -9,6 +9,22 @@ class HrExpense(models.Model):
_inherit = "hr.expense"
advance = fields.Boolean(string="Employee Advance", default=False)
+ clearing_product_id = fields.Many2one(
+ comodel_name="product.product",
+ string="Clearing Product",
+ tracking=True,
+ domain="[('can_be_expensed', '=', True),"
+ "'|', ('company_id', '=', False), ('company_id', '=', company_id)]",
+ ondelete="restrict",
+ help="Optional: On the clear advance, the clearing "
+ "product will create default product line.",
+ )
+ av_line_id = fields.Many2one(
+ comodel_name="hr.expense",
+ string="Ref: Advance",
+ ondelete="set null",
+ help="Expense created from this advance expense line",
+ )
@api.constrains("advance")
def _check_advance(self):
@@ -40,6 +56,7 @@ def onchange_advance(self):
)
else:
self.product_id = False
+ self.clearing_product_id = False
def _get_account_move_line_values(self):
move_line_values_by_expense = super()._get_account_move_line_values()
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index 955802199..62a4fe5c6 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -46,9 +46,8 @@ class HrExpenseSheet(models.Model):
@api.depends("expense_line_ids")
def _compute_advance(self):
for sheet in self:
- sheet.advance = bool(
- sheet.expense_line_ids.filtered("advance")
- and len(sheet.expense_line_ids) == 1
+ sheet.advance = sheet.expense_line_ids and all(
+ sheet.expense_line_ids.mapped("advance")
)
return
@@ -59,8 +58,8 @@ def _check_advance_expense(self):
raise ValidationError(
_("Advance clearing must not contain any advance expense line")
)
- if advance_lines and len(self.expense_line_ids) != 1:
- raise ValidationError(_("Advance must contain only 1 advance expense line"))
+ if advance_lines and len(advance_lines) != len(self.expense_line_ids):
+ raise ValidationError(_("Advance must contain only advance expense line"))
@api.depends("account_move_id.line_ids.amount_residual")
def _compute_clearing_residual(self):
@@ -95,7 +94,13 @@ def action_sheet_move_create(self):
adv_move_lines = (
self.env["account.move.line"]
.sudo()
- .search([("id", "in", move_lines.ids), ("account_id", "=", account_id)])
+ .search(
+ [
+ ("id", "in", move_lines.ids),
+ ("account_id", "=", account_id),
+ ("reconciled", "=", False),
+ ]
+ )
)
adv_move_lines.reconcile()
return res
@@ -112,3 +117,51 @@ def open_clear_advance(self):
context1["default_advance_sheet_id"] = self.id
vals["context"] = context1
return vals
+
+ @api.onchange("advance_sheet_id")
+ def _onchange_advance_sheet_id(self):
+ self.expense_line_ids -= self.expense_line_ids.filtered("av_line_id")
+ self.advance_sheet_id.expense_line_ids.read()
+ for line in self.advance_sheet_id.expense_line_ids.filtered(
+ "clearing_product_id"
+ ):
+ clear_advance = self._prepare_clear_advance(line)
+ self.expense_line_ids += self.env["hr.expense"].new(clear_advance)
+
+ def _prepare_clear_advance(self, line):
+ # Prepare the clearing expense
+ clear_line_dict = {
+ "advance": False,
+ "name": False,
+ "product_id": line.clearing_product_id.id,
+ "clearing_product_id": False,
+ "date": fields.Date.context_today(self),
+ "account_id": False,
+ "state": "draft",
+ "product_uom_id": False,
+ "av_line_id": line.id,
+ }
+ clear_line = self.env["hr.expense"].new(clear_line_dict)
+ clear_line._compute_from_product_id_company_id() # Set some vals
+ # Prepare the original advance line
+ adv_dict = line._convert_to_write(line._cache)
+ # remove no update columns
+ _fields = line._fields
+ del_cols = [k for k in _fields.keys() if _fields[k].type == "one2many"]
+ del_cols += list(self.env["mail.thread"]._fields.keys())
+ del_cols += list(self.env["mail.activity.mixin"]._fields.keys())
+ del_cols += list(clear_line_dict.keys())
+ del_cols = list(set(del_cols))
+ adv_dict = {k: v for k, v in adv_dict.items() if k not in del_cols}
+ # Assign the known value from original advance line
+ clear_line.update(adv_dict)
+ clearing_dict = clear_line._convert_to_write(clear_line._cache)
+ # Convert list of int to [(6, 0, list)]
+ clearing_dict = {
+ k: isinstance(v, list)
+ and all(isinstance(x, int) for x in v)
+ and [(6, 0, v)]
+ or v
+ for k, v in clearing_dict.items()
+ }
+ return clearing_dict
diff --git a/hr_expense_advance_clearing/readme/USAGE.rst b/hr_expense_advance_clearing/readme/USAGE.rst
index 6f9499b71..1eae4b858 100644
--- a/hr_expense_advance_clearing/readme/USAGE.rst
+++ b/hr_expense_advance_clearing/readme/USAGE.rst
@@ -3,6 +3,7 @@
#. Go to Expenses > My Expenses
#. Create a new Expense as normal, but also check "Employee Advance" checkbox
#. Product = Employee Advance will be set automatically, do not change.
+#. As an option, user can also set the "Clearing Product". If this is set, on the clear advance step, the clearing product will create a default product line.
#. Set the unit price to advance amount
#. Click Create Report as normal. Please note that, this expense report will also has flag "Employee Advance" checked.
#. As normal, do Submit to Manager > Approve > Post Journal Entries > Register Payment.
diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
index e5986b507..aa11b086b 100644
--- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
+++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
@@ -78,7 +78,8 @@ def _create_expense(
expense.advance = advance
expense.name = description
expense.employee_id = employee
- expense.product_id = product
+ if not advance:
+ expense.product_id = product
expense.unit_amount = amount
expense.payment_mode = payment_mode
expense = expense.save()
@@ -125,11 +126,6 @@ def test_0_test_constraints(self):
"Buy service 1,000", self.employee, self.product, 1.0
)
self.advance.write({"expense_line_ids": [(4, expense.id)]})
- # Advance Expense's product must be employee advance
- with self.assertRaises(ValidationError):
- expense = self._create_expense(
- "Advance 1,000", self.employee, self.product, 1.0, advance=True
- )
# Advance Expense's product, must not has tax involved
with self.assertRaises(ValidationError):
self.emp_advance.supplier_taxes_id |= self.tax
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index fcf6bebc0..daf2dffbc 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -50,6 +50,19 @@
+
+ {'readonly': [('advance', '=', True)]}
+ 1
+
+
+
+
+
+
+
+
+
+
+ {'default_advance': advance, 'form_view_ref' : 'hr_expense.hr_expense_view_form_without_header', 'default_company_id': company_id, 'default_employee_id': employee_id}
From 895d647ab24f169ea0c5216348edb91366c23056 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Wed, 21 Jul 2021 13:29:49 +0700
Subject: [PATCH 41/75] [IMP] clearing tab on advance sheet
(cherry picked from commit 26e93831784312f6a928648d72f3bbb8c9d99cbb)
---
.../models/hr_expense_sheet.py | 7 +++++++
.../views/hr_expense_views.xml | 11 ++++++++++-
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index 62a4fe5c6..d17d7e5da 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -25,6 +25,13 @@ class HrExpenseSheet(models.Model):
},
help="Show remaining advance of this employee",
)
+ clearing_sheet_ids = fields.One2many(
+ comodel_name="hr.expense.sheet",
+ inverse_name="advance_sheet_id",
+ string="Clearing Sheet",
+ readonly=True,
+ help="Show reference clearing on advance",
+ )
clearing_residual = fields.Monetary(
string="Amount to clear",
compute="_compute_clearing_residual",
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index daf2dffbc..297d46805 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -131,7 +131,7 @@
@@ -140,6 +140,15 @@
name="context"
>{'default_advance': advance, 'form_view_ref' : 'hr_expense.hr_expense_view_form_without_header', 'default_company_id': company_id, 'default_employee_id': employee_id}
+
+
+
+
+
From 410a025423376c87a550362f6db42203c1e58eb5 Mon Sep 17 00:00:00 2001
From: Kitti U
Date: Sun, 25 Jul 2021 13:04:53 +0700
Subject: [PATCH 42/75] [14.0][IMP] hr_expense_advance_clearing, add invisible
fields on tree view
(cherry picked from commit 7ada587d3e37c80a6b4bfab312570814e235f688)
---
hr_expense_advance_clearing/views/hr_expense_views.xml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 297d46805..4aae2f8e9 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -125,6 +125,8 @@
expr="//field[@name='expense_line_ids']/tree/field[@name='name']"
position="after"
>
+
+
From 50b5cada8edb5107653cbf2bcda60069aa6516f9 Mon Sep 17 00:00:00 2001
From: Kitti U
Date: Fri, 30 Jul 2021 00:16:42 +0700
Subject: [PATCH 43/75] [14.0][FIX] hr_expense_advance_clearing, fix access
right on .read()
(cherry picked from commit 397249a6cd8e60f55de5a078866fa8fa9e63970a)
---
hr_expense_advance_clearing/models/hr_expense_sheet.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index d17d7e5da..ebda34a78 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -117,7 +117,7 @@ def open_clear_advance(self):
action = self.env.ref(
"hr_expense_advance_clearing." "action_hr_expense_sheet_advance_clearing"
)
- vals = action.read()[0]
+ vals = action.sudo().read()[0]
context1 = vals.get("context", {})
if context1:
context1 = safe_eval(context1)
@@ -128,7 +128,7 @@ def open_clear_advance(self):
@api.onchange("advance_sheet_id")
def _onchange_advance_sheet_id(self):
self.expense_line_ids -= self.expense_line_ids.filtered("av_line_id")
- self.advance_sheet_id.expense_line_ids.read()
+ self.advance_sheet_id.expense_line_ids.sudo().read() # prefetch
for line in self.advance_sheet_id.expense_line_ids.filtered(
"clearing_product_id"
):
From f5b5cb3ea6f8df839ed9438f0ab9a2da0918b274 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Wed, 18 Aug 2021 11:07:33 +0700
Subject: [PATCH 44/75] [IMP] hr_expense_advance_cleaing: show advance on
employee view
(cherry picked from commit 2d47f02560ea32d41388ebd09ac173133243f730)
---
hr_expense_advance_clearing/__manifest__.py | 7 +++-
.../models/__init__.py | 1 +
.../models/hr_employee_base.py | 33 +++++++++++++++++++
.../views/hr_employee_public_views.xml | 19 +++++++++++
.../views/hr_employee_views.xml | 20 +++++++++++
.../views/hr_expense_views.xml | 10 ++++++
6 files changed, 89 insertions(+), 1 deletion(-)
create mode 100644 hr_expense_advance_clearing/models/hr_employee_base.py
create mode 100644 hr_expense_advance_clearing/views/hr_employee_public_views.xml
create mode 100644 hr_expense_advance_clearing/views/hr_employee_views.xml
diff --git a/hr_expense_advance_clearing/__manifest__.py b/hr_expense_advance_clearing/__manifest__.py
index 2781513ef..99bfc5909 100644
--- a/hr_expense_advance_clearing/__manifest__.py
+++ b/hr_expense_advance_clearing/__manifest__.py
@@ -9,7 +9,12 @@
"license": "AGPL-3",
"website": "https://github.com/OCA/hr-expense",
"depends": ["hr_expense"],
- "data": ["data/advance_product.xml", "views/hr_expense_views.xml"],
+ "data": [
+ "data/advance_product.xml",
+ "views/hr_expense_views.xml",
+ "views/hr_employee_views.xml",
+ "views/hr_employee_public_views.xml",
+ ],
"installable": True,
"maintainers": ["kittiu"],
}
diff --git a/hr_expense_advance_clearing/models/__init__.py b/hr_expense_advance_clearing/models/__init__.py
index 945a814c7..be323457c 100644
--- a/hr_expense_advance_clearing/models/__init__.py
+++ b/hr_expense_advance_clearing/models/__init__.py
@@ -1,2 +1,3 @@
from . import hr_expense
from . import hr_expense_sheet
+from . import hr_employee_base
diff --git a/hr_expense_advance_clearing/models/hr_employee_base.py b/hr_expense_advance_clearing/models/hr_employee_base.py
new file mode 100644
index 000000000..28a058091
--- /dev/null
+++ b/hr_expense_advance_clearing/models/hr_employee_base.py
@@ -0,0 +1,33 @@
+# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import _, fields, models
+
+
+class HrEmployeeBase(models.AbstractModel):
+ _inherit = "hr.employee.base"
+
+ advance_sheet_ids = fields.One2many(
+ comodel_name="hr.expense.sheet",
+ inverse_name="employee_id",
+ domain=[("advance", "=", True)],
+ readonly=True,
+ )
+ advance_count = fields.Integer(
+ string="# of Advance",
+ compute="_compute_advance_count",
+ help="Count advance sheet in expense report",
+ )
+
+ def _compute_advance_count(self):
+ self.advance_count = len(self.advance_sheet_ids)
+
+ def action_open_advance_clearing(self):
+ self.ensure_one()
+ return {
+ "name": _("Advance Sheet"),
+ "type": "ir.actions.act_window",
+ "res_model": "hr.expense.sheet",
+ "view_mode": "tree,form",
+ "domain": [("id", "in", self.advance_sheet_ids.ids)],
+ }
diff --git a/hr_expense_advance_clearing/views/hr_employee_public_views.xml b/hr_expense_advance_clearing/views/hr_employee_public_views.xml
new file mode 100644
index 000000000..3d9b0e8d5
--- /dev/null
+++ b/hr_expense_advance_clearing/views/hr_employee_public_views.xml
@@ -0,0 +1,19 @@
+
+
+
+ hr.employee.public.form
+ hr.employee.public
+
+
+
+
+
+
+
+
+
+
diff --git a/hr_expense_advance_clearing/views/hr_employee_views.xml b/hr_expense_advance_clearing/views/hr_employee_views.xml
new file mode 100644
index 000000000..b558ac1f2
--- /dev/null
+++ b/hr_expense_advance_clearing/views/hr_employee_views.xml
@@ -0,0 +1,20 @@
+
+
+
+ hr.employee.form
+ hr.employee
+
+
+
+
+
+
+
+
+
+
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 4aae2f8e9..2cb011acd 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -75,6 +75,16 @@
+
+ hr.expense.sheet.tree
+ hr.expense.sheet
+
+
+
+
+
+
+
view.hr.expense.sheet.form
hr.expense.sheet
From 5bf2cc61a0a19bca1f9285f65f247ed87a04d097 Mon Sep 17 00:00:00 2001
From: Kitti U
Date: Tue, 2 Nov 2021 11:33:52 +0700
Subject: [PATCH 45/75] [14.0][IMP] hr_expense_advance_clearing, add menus
Advance/Expense
(cherry picked from commit e6c8719d3b391aabff4ac8ccad6508ddf46e73ca)
---
.../models/hr_expense.py | 3 -
.../models/hr_expense_sheet.py | 6 +-
.../tests/test_hr_expense_advance_clearing.py | 5 +-
.../views/hr_expense_views.xml | 84 ++++++++++++++++++-
4 files changed, 88 insertions(+), 10 deletions(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py
index 848a60e7d..a9c599416 100644
--- a/hr_expense_advance_clearing/models/hr_expense.py
+++ b/hr_expense_advance_clearing/models/hr_expense.py
@@ -54,9 +54,6 @@ def onchange_advance(self):
self.product_id = self.env.ref(
"hr_expense_advance_clearing.product_emp_advance"
)
- else:
- self.product_id = False
- self.clearing_product_id = False
def _get_account_move_line_values(self):
move_line_values_by_expense = super()._get_account_move_line_values()
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index ebda34a78..ea80aba13 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -53,8 +53,10 @@ class HrExpenseSheet(models.Model):
@api.depends("expense_line_ids")
def _compute_advance(self):
for sheet in self:
- sheet.advance = sheet.expense_line_ids and all(
- sheet.expense_line_ids.mapped("advance")
+ sheet.advance = (
+ sheet.expense_line_ids
+ and all(sheet.expense_line_ids.mapped("advance"))
+ or self.env.context.get("default_advance")
)
return
diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
index aa11b086b..d276f9dc6 100644
--- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
+++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
@@ -74,8 +74,9 @@ def _create_expense(
advance=False,
payment_mode="own_account",
):
- with Form(self.env["hr.expense"]) as expense:
- expense.advance = advance
+ with Form(
+ self.env["hr.expense"].with_context(default_advance=advance)
+ ) as expense:
expense.name = description
expense.employee_id = employee
if not advance:
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 2cb011acd..2235bca63 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -1,4 +1,77 @@
+
+
+
+
+ Advances
+ hr.expense.sheet
+ tree,kanban,form,pivot,graph,activity
+
+ [('state', '!=', 'cancel'), ('advance', '=', True)]
+ {'search_default_my_reports': 1, 'default_advance': True}
+
+
+ No advance report found. Let's create one!
+
+ Once you have created your advance, submit it to your manager who will validate it.
+
+
+
+
+ Expenses
+ hr.expense.sheet
+ tree,kanban,form,pivot,graph,activity
+
+ [('state', '!=', 'cancel'), ('advance', '=', False)]
+ {'search_default_my_reports': 1}
+
+
+ No expense report found. Let's create one!
+
+ Once you have created your expense, submit it to your manager who will validate it.
+
+
+
+
+
+
+
+
+
+ hr.expense.view.search
+ hr.expense
+
+
+
+
+
+
+
+
hr.expense.sheet.view.search
hr.expense.sheet
@@ -47,8 +120,13 @@
-
-
+
+
{'default_advance': advance, 'form_view_ref' : 'hr_expense.hr_expense_view_form_without_header', 'default_company_id': company_id, 'default_employee_id': employee_id}
+ >{'default_advance': advance, 'search_default_is_advance': advance, 'search_default_is_expense': not advance, 'form_view_ref' : 'hr_expense.hr_expense_view_form_without_header', 'default_company_id': company_id, 'default_employee_id': employee_id}
Date: Tue, 9 Nov 2021 14:48:22 +0700
Subject: [PATCH 46/75] [FIX] constrains with advance clearing
(cherry picked from commit 963dff4a82b22ab9dc303082b43254c71b8875ed)
---
.../models/hr_expense.py | 4 ++++
.../tests/test_hr_expense_advance_clearing.py | 23 +++++++++++++++++--
2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py
index a9c599416..d13139113 100644
--- a/hr_expense_advance_clearing/models/hr_expense.py
+++ b/hr_expense_advance_clearing/models/hr_expense.py
@@ -40,6 +40,10 @@ def _check_advance(self):
raise ValidationError(
_("Employee advance, selected product is not valid")
)
+ if expense.account_id != emp_advance.property_account_expense_id:
+ raise ValidationError(
+ _("Employee advance, account must be the same payable account")
+ )
if expense.tax_ids:
raise ValidationError(_("Employee advance, all taxes must be removed"))
if expense.payment_mode != "own_account":
diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
index d276f9dc6..9d16ca150 100644
--- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
+++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
@@ -44,9 +44,14 @@ def setUpClass(cls):
"reconcile": True,
}
)
- cls.emp_advance = cls.env.ref(
- "hr_expense_advance_clearing." "product_emp_advance"
+ cls.account_sales = cls.env["account.account"].create(
+ {
+ "code": "X1020",
+ "name": "Product Sales - (test)",
+ "user_type_id": cls.env.ref("account.data_account_type_revenue").id,
+ }
)
+ cls.emp_advance = cls.env.ref("hr_expense_advance_clearing.product_emp_advance")
cls.emp_advance.property_account_expense_id = advance_account
# Create advance expense 1,000
cls.advance = cls._create_expense_sheet(
@@ -73,6 +78,7 @@ def _create_expense(
amount,
advance=False,
payment_mode="own_account",
+ account=False,
):
with Form(
self.env["hr.expense"].with_context(default_advance=advance)
@@ -83,6 +89,8 @@ def _create_expense(
expense.product_id = product
expense.unit_amount = amount
expense.payment_mode = payment_mode
+ if account:
+ expense.account_id = account
expense = expense.save()
expense.tax_ids = False # Test no vat
return expense
@@ -121,6 +129,17 @@ def test_0_test_constraints(self):
# Advance Sheet can't be clearing at the same time.
with self.assertRaises(ValidationError):
self.advance.advance_sheet_id = self.advance
+ # Advance Sheet can't change account is not the equal
+ # Account on Advance Expense's product.
+ with self.assertRaises(ValidationError):
+ expense = self._create_expense(
+ "Advance 1,000",
+ self.employee,
+ self.emp_advance,
+ 1.0,
+ advance=True,
+ account=self.account_sales,
+ )
# Advance Sheet should not have > 1 expense lines
with self.assertRaises(ValidationError):
expense = self._create_expense(
From 2ddd3816f5a8a91e30b9b9542069ed2d9c2bc447 Mon Sep 17 00:00:00 2001
From: ps-tubtim
Date: Thu, 11 Nov 2021 16:59:36 +0700
Subject: [PATCH 47/75] [FIX] hr_expense_advance_clearing: add Clear Advance on
tree view
(cherry picked from commit e68024181272c370f6410e6b7ae44187b3e9b5f7)
---
hr_expense_advance_clearing/views/hr_expense_views.xml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 2235bca63..218eb9e78 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -158,6 +158,9 @@
hr.expense.sheet
+
+
+
From b6671b4d6270b592c756227de2eb5b204d42d2f4 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Thu, 18 Nov 2021 11:10:17 +0700
Subject: [PATCH 48/75] [FIX] skip_account_move_synchronization on post journal
entries
(cherry picked from commit e6d6691bb90ddbda303ad9bd74a52f924851e02c)
---
hr_expense_advance_clearing/models/hr_expense_sheet.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index ea80aba13..378b564ca 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -94,6 +94,8 @@ def action_sheet_move_create(self):
res = super(HrExpenseSheet, self).action_sheet_move_create()
# Reconcile advance of this sheet with the advance_sheet
emp_advance = self.env.ref("hr_expense_advance_clearing.product_emp_advance")
+ ctx = self._context.copy()
+ ctx.update({"skip_account_move_synchronization": True})
for sheet in self:
move_lines = (
sheet.account_move_id.line_ids
@@ -111,7 +113,10 @@ def action_sheet_move_create(self):
]
)
)
- adv_move_lines.reconcile()
+ adv_move_lines.with_context(ctx).reconcile()
+ # Update state on clearing advance when advance residual > total amount
+ if sheet.advance_sheet_id and advance_residual != -1:
+ sheet.write({"state": "done"})
return res
def open_clear_advance(self):
From 80315ce724e8470e9fea0e82743fa8a913b9d325 Mon Sep 17 00:00:00 2001
From: Kitti U
Date: Wed, 10 Nov 2021 22:20:25 +0700
Subject: [PATCH 49/75] [14.0][FIX] hr_expense_advance_clearing, make
expense_line_ids readonly on new clearing
(cherry picked from commit fbbf3038974f37be2a8367bfbece0eef5349d28e)
---
hr_expense_advance_clearing/models/hr_expense_sheet.py | 9 ++++-----
hr_expense_advance_clearing/views/hr_expense_views.xml | 8 +++++++-
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index 378b564ca..bc02810f6 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -53,11 +53,10 @@ class HrExpenseSheet(models.Model):
@api.depends("expense_line_ids")
def _compute_advance(self):
for sheet in self:
- sheet.advance = (
- sheet.expense_line_ids
- and all(sheet.expense_line_ids.mapped("advance"))
- or self.env.context.get("default_advance")
- )
+ if sheet.expense_line_ids:
+ sheet.advance = all(sheet.expense_line_ids.mapped("advance"))
+ else:
+ sheet.advance = self.env.context.get("default_advance", sheet.advance)
return
@api.constrains("advance_sheet_id", "expense_line_ids")
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 218eb9e78..3d8b13786 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -198,9 +198,11 @@
+
{'default_advance': advance, 'search_default_is_advance': advance, 'search_default_is_expense': not advance, 'form_view_ref' : 'hr_expense.hr_expense_view_form_without_header', 'default_company_id': company_id, 'default_employee_id': employee_id}
+ {'readonly': [('id', '=', False), ('advance_sheet_id', '!=', False)]}
+ True
Date: Mon, 22 Nov 2021 17:20:01 +0700
Subject: [PATCH 50/75] [FIX] onchange clearing advance for hooks
(cherry picked from commit 9728fd37b5165e83ba5ae59203ba49ed65117062)
---
.../models/hr_expense_sheet.py | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index bc02810f6..dd89cdb4e 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -131,15 +131,21 @@ def open_clear_advance(self):
vals["context"] = context1
return vals
+ def get_domain_advance_sheet_expense_line(self):
+ return self.advance_sheet_id.expense_line_ids.filtered("clearing_product_id")
+
+ def create_clearing_expense_line(self, line):
+ clear_advance = self._prepare_clear_advance(line)
+ clearing_line = self.env["hr.expense"].new(clear_advance)
+ return clearing_line
+
@api.onchange("advance_sheet_id")
def _onchange_advance_sheet_id(self):
self.expense_line_ids -= self.expense_line_ids.filtered("av_line_id")
self.advance_sheet_id.expense_line_ids.sudo().read() # prefetch
- for line in self.advance_sheet_id.expense_line_ids.filtered(
- "clearing_product_id"
- ):
- clear_advance = self._prepare_clear_advance(line)
- self.expense_line_ids += self.env["hr.expense"].new(clear_advance)
+ lines = self.get_domain_advance_sheet_expense_line()
+ for line in lines:
+ self.expense_line_ids += self.create_clearing_expense_line(line)
def _prepare_clear_advance(self, line):
# Prepare the clearing expense
From 4d3e926cf01f23962ac6d96e6d5a4ee965b1c79b Mon Sep 17 00:00:00 2001
From: ps-tubtim
Date: Fri, 26 Nov 2021 10:12:22 +0700
Subject: [PATCH 51/75] [FIX] hr_expense_advance_clearing: add context for
clearing
(cherry picked from commit c9ad5bc01c15c3a96633e67093977d5368c70421)
---
hr_expense_advance_clearing/models/hr_expense_sheet.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index dd89cdb4e..28479c785 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -128,6 +128,7 @@ def open_clear_advance(self):
if context1:
context1 = safe_eval(context1)
context1["default_advance_sheet_id"] = self.id
+ context1["default_employee_id"] = self.employee_id.id
vals["context"] = context1
return vals
From f5edf57524ac0361af478de831418acc9a81490f Mon Sep 17 00:00:00 2001
From: Kitti U
Date: Thu, 3 Mar 2022 12:39:17 +0700
Subject: [PATCH 52/75] [15.0][MIG] hr_expense_advance_clearing]
(cherry picked from commit ee44183a291c0b376a94a4a542a8aee20b7e745a)
---
.../tests/test_hr_expense_advance_clearing.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
index 9d16ca150..a17f842b9 100644
--- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
+++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
@@ -6,7 +6,7 @@
from odoo.tests.common import Form
-class TestHrExpenseAdvanceClearing(common.SavepointCase):
+class TestHrExpenseAdvanceClearing(common.TransactionCase):
@classmethod
def setUpClass(cls):
super(TestHrExpenseAdvanceClearing, cls).setUpClass()
From 07cfa99b2da143a4cae4bcc1bc0d92661b7768aa Mon Sep 17 00:00:00 2001
From: Saran440
Date: Sat, 26 Mar 2022 15:59:31 +0700
Subject: [PATCH 53/75] [FIX] do not allow cancel PV related clearing or return
(cherry picked from commit 6f13bc852935fa13b4ee86138d53997d4bd2fa58)
---
.../models/__init__.py | 1 +
.../models/account_move.py | 38 +++++++++++++++++++
.../models/hr_expense.py | 1 -
3 files changed, 39 insertions(+), 1 deletion(-)
create mode 100644 hr_expense_advance_clearing/models/account_move.py
diff --git a/hr_expense_advance_clearing/models/__init__.py b/hr_expense_advance_clearing/models/__init__.py
index be323457c..154043cdd 100644
--- a/hr_expense_advance_clearing/models/__init__.py
+++ b/hr_expense_advance_clearing/models/__init__.py
@@ -1,3 +1,4 @@
from . import hr_expense
from . import hr_expense_sheet
from . import hr_employee_base
+from . import account_move
diff --git a/hr_expense_advance_clearing/models/account_move.py b/hr_expense_advance_clearing/models/account_move.py
new file mode 100644
index 000000000..636aea513
--- /dev/null
+++ b/hr_expense_advance_clearing/models/account_move.py
@@ -0,0 +1,38 @@
+# Copyright 2022 Ecosoft Co., Ltd. (https://ecosoft.co.th)
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from odoo import _, models
+from odoo.exceptions import UserError
+
+
+class AccountMove(models.Model):
+ _inherit = "account.move"
+
+ def _check_hr_advance_move_reconciled(self):
+ """Check if the advance move lines already cleard/returned"""
+ av_moves = self.filtered("line_ids.expense_id.sheet_id.advance")
+ emp_advance = self.env.ref("hr_expense_advance_clearing.product_emp_advance")
+ reconciled_av_move_lines = av_moves.mapped("line_ids").filtered(
+ lambda l: l.product_id == emp_advance and l.matching_number
+ )
+ if reconciled_av_move_lines:
+ raise UserError(
+ _(
+ "This operation is not allowed as some advance amount was already "
+ "cleared/returned.\nPlease cancel those documents first."
+ )
+ )
+
+ def button_draft(self):
+ self._check_hr_advance_move_reconciled()
+ return super().button_draft()
+
+ def button_cancel(self):
+ self._check_hr_advance_move_reconciled()
+ return super().button_cancel()
+
+ def _reverse_moves(self, default_values_list=None, cancel=False):
+ self._check_hr_advance_move_reconciled()
+ return super()._reverse_moves(
+ default_values_list=default_values_list, cancel=cancel
+ )
diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py
index d13139113..1e0e3476c 100644
--- a/hr_expense_advance_clearing/models/hr_expense.py
+++ b/hr_expense_advance_clearing/models/hr_expense.py
@@ -53,7 +53,6 @@ def _check_advance(self):
@api.onchange("advance")
def onchange_advance(self):
self.tax_ids = False
- self.payment_mode = "own_account"
if self.advance:
self.product_id = self.env.ref(
"hr_expense_advance_clearing.product_emp_advance"
From 8272c9b8a28cf604ae860ada7ad953afdd3aef9f Mon Sep 17 00:00:00 2001
From: ps-tubtim
Date: Mon, 14 Nov 2022 17:31:50 +0700
Subject: [PATCH 54/75] [14.0][FIX] hr_expense_advance_clearing: allow general
user to create clearing
(cherry picked from commit 8d5a3969ead1d1bb9b560ab9328b7f88340ace6f)
---
hr_expense_advance_clearing/views/hr_expense_views.xml | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 3d8b13786..0a447ef69 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -178,15 +178,14 @@
string="Clear Advance"
class="oe_highlight"
attrs="{'invisible': ['|', '|', ('advance', '=', False), ('state', '!=', 'done'), ('clearing_residual', '=', 0.0)]}"
- groups="account.group_account_manager"
/>
From 6dca52872fd3eb95cc6d9c626283db74ea9c2af4 Mon Sep 17 00:00:00 2001
From: ps-tubtim
Date: Thu, 16 Feb 2023 17:20:10 +0700
Subject: [PATCH 55/75] [15.0][FIX] hr_expense_advance_clearing: zero unit
price
(cherry picked from commit e6bc12d4143a7b68f94a8a6dc1d700e19050d7df)
---
.../views/hr_expense_views.xml | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 0a447ef69..631777dfc 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -238,10 +238,18 @@
>{'readonly': [('id', '=', False), ('advance_sheet_id', '!=', False)]}
True
-
-
+ True
+
+
+
From 8a7ef80db1df8556a8376c7d9b7a7cff194ed783 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Thu, 16 Mar 2023 16:02:37 +0700
Subject: [PATCH 56/75] [FIX] hr_expense_advance_clearing: change field not
compute advance
(cherry picked from commit c328cfc4e718cad65d70869700dc786d789bdef7)
---
.../models/hr_expense_sheet.py | 11 +----------
.../tests/test_hr_expense_advance_clearing.py | 1 +
.../views/hr_expense_views.xml | 2 ++
3 files changed, 4 insertions(+), 10 deletions(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index 28479c785..945a03f77 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -10,7 +10,7 @@ class HrExpenseSheet(models.Model):
_inherit = "hr.expense.sheet"
advance = fields.Boolean(
- string="Employee Advance", compute="_compute_advance", store=True
+ string="Employee Advance",
)
advance_sheet_id = fields.Many2one(
comodel_name="hr.expense.sheet",
@@ -50,15 +50,6 @@ class HrExpenseSheet(models.Model):
help="Final regiter payment amount even after advance clearing",
)
- @api.depends("expense_line_ids")
- def _compute_advance(self):
- for sheet in self:
- if sheet.expense_line_ids:
- sheet.advance = all(sheet.expense_line_ids.mapped("advance"))
- else:
- sheet.advance = self.env.context.get("default_advance", sheet.advance)
- return
-
@api.constrains("advance_sheet_id", "expense_line_ids")
def _check_advance_expense(self):
advance_lines = self.expense_line_ids.filtered("advance")
diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
index a17f842b9..32600b99d 100644
--- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
+++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
@@ -107,6 +107,7 @@ def _create_expense_sheet(
"name": description,
"employee_id": expense.employee_id.id,
"expense_line_ids": [(6, 0, [expense.id])],
+ "advance": advance,
}
)
return expense_sheet
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 631777dfc..1fe1dc6ff 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -192,6 +192,8 @@
From ed1cef70537f6c2c45e2b68c4175f40f7fb2f112 Mon Sep 17 00:00:00 2001
From: mymage
Date: Fri, 30 Jun 2023 10:15:36 +0000
Subject: [PATCH 57/75] Added translation using Weblate (Italian)
(cherry picked from commit 0004a0e42cfac9e88ec7005fb4ee1d49ffe45690)
---
hr_expense_advance_clearing/i18n/it.po | 354 +++++++++++++++++++++++++
1 file changed, 354 insertions(+)
create mode 100644 hr_expense_advance_clearing/i18n/it.po
diff --git a/hr_expense_advance_clearing/i18n/it.po b/hr_expense_advance_clearing/i18n/it.po
new file mode 100644
index 000000000..f9f6f3f15
--- /dev/null
+++ b/hr_expense_advance_clearing/i18n/it.po
@@ -0,0 +1,354 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * hr_expense_advance_clearing
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 15.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+#, python-format
+msgid ""
+"\n"
+"Note: pending amount clearing is {}{}"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_count
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_count
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_count
+msgid "# of Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+#, python-format
+msgid ""
+"A remaining advance return of {} {} with the reference {} related to your "
+"expense {} has been made."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_account_payment__advance_id
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_employee_public_view_form
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_employee_form
+msgid "Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
+msgid "Advance (not cleared)"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid "Advance Clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
+msgid "Advance Remaining"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_employee_base.py:0
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_sheet_ids
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_sheet_ids
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_sheet_ids
+#, python-format
+msgid "Advance Sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+#, python-format
+msgid "Advance clearing must not contain any advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+#, python-format
+msgid "Advance must contain only advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+#, python-format
+msgid "Advance: %s has no amount to clear"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet
+#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_advance_sheet
+msgid "Advances"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual
+msgid "Amount to clear"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual
+msgid "Amount to clear of this expense sheet in company currency"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_employee_base
+msgid "Basic Employee"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Clear Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
+msgid "Clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_count
+msgid "Clearing Count"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__clearing_product_id
+msgid "Clearing Product"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids
+msgid "Clearing Sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+#, python-format
+msgid "Clearing Sheets"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Clearings"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee__advance_count
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_base__advance_count
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_public__advance_count
+msgid "Count advance sheet in expense report"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid "Create a new expense report"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance
+#: model:product.product,name:hr_expense_advance_clearing.product_emp_advance
+#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template
+msgid "Employee Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+#, python-format
+msgid "Employee advance product has no payable account"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+#, python-format
+msgid "Employee advance, account must be the same payable account"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+#, python-format
+msgid "Employee advance, all taxes must be removed"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+#, python-format
+msgid "Employee advance, paid by must be employee"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+#, python-format
+msgid "Employee advance, selected product is not valid"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search
+msgid "Expense"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet
+msgid "Expense Report"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__av_line_id
+msgid "Expense created from this advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_expense_sheet
+#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_expense_sheet
+msgid "Expenses"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable
+msgid "Final regiter payment amount even after advance clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_move
+msgid "Journal Entry"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet
+msgid "No advance report found. Let's create one!"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet
+msgid "No expense report found. Let's create one!"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet
+msgid ""
+"Once you have created your advance, submit it to your manager who will "
+"validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid ""
+"Once you have created your expense, submit it to your manager who will\n"
+" validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet
+msgid ""
+"Once you have created your expense, submit it to your manager who will "
+"validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_form
+msgid "Optional clearing product is used during clear advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__clearing_product_id
+msgid ""
+"Optional: On the clear advance, the clearing product will create default "
+"product line."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable
+msgid "Payable Amount"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment
+msgid "Payments"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__av_line_id
+msgid "Ref: Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment_register
+msgid "Register Payment"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
+msgid "Remaining amount to clear the selected advance sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_account_payment_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Return Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids
+msgid "Show reference clearing on advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
+msgid "Show remaining advance of this employee"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/models/account_move.py:0
+#, python-format
+msgid ""
+"This operation is not allowed as some advance amount was already cleared/returned.\n"
+"Please cancel those documents first."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:product.product,uom_name:hr_expense_advance_clearing.product_emp_advance
+#: model:product.template,uom_name:hr_expense_advance_clearing.product_emp_advance_product_template
+msgid "Units"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+#, python-format
+msgid "You can only register payment for posted journal entries."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+#, python-format
+msgid ""
+"You can't create payments for entries belonging to different companies."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+#, python-format
+msgid ""
+"You can't register a payment because there is nothing left to pay on the "
+"selected journal items."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+#, python-format
+msgid "You cannot return advance more than actual remaining ({}{}){}"
+msgstr ""
From c8216ebbbdd1f945085fe3d93559cf09c745765e Mon Sep 17 00:00:00 2001
From: mymage
Date: Tue, 4 Jul 2023 06:29:12 +0000
Subject: [PATCH 58/75] Translated using Weblate (Italian)
Currently translated at 100.0% (57 of 57 strings)
Translation: hr-expense-15.0/hr-expense-15.0-hr_expense_advance_clearing
Translate-URL: https://translation.odoo-community.org/projects/hr-expense-15-0/hr-expense-15-0-hr_expense_advance_clearing/it/
(cherry picked from commit 3a273f0834c964ca7f752384cdac6db63a53af04)
---
hr_expense_advance_clearing/i18n/it.po | 116 +++++++++++++++----------
1 file changed, 70 insertions(+), 46 deletions(-)
diff --git a/hr_expense_advance_clearing/i18n/it.po b/hr_expense_advance_clearing/i18n/it.po
index f9f6f3f15..f33b49763 100644
--- a/hr_expense_advance_clearing/i18n/it.po
+++ b/hr_expense_advance_clearing/i18n/it.po
@@ -6,13 +6,15 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: Automatically generated\n"
+"PO-Revision-Date: 2023-07-04 09:09+0000\n"
+"Last-Translator: mymage \n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.17\n"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
@@ -21,13 +23,15 @@ msgid ""
"\n"
"Note: pending amount clearing is {}{}"
msgstr ""
+"\n"
+"Nota: il valore da saldare in attesa è {}{}"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_count
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_count
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_count
msgid "# of Advance"
-msgstr ""
+msgstr "N° di anticipo"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
@@ -36,6 +40,8 @@ msgid ""
"A remaining advance return of {} {} with the reference {} related to your "
"expense {} has been made."
msgstr ""
+"È stato eseguito un rimborso anticipo residuo di {} {} con riferimento {} "
+"relativo alla sua spesa {}."
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_account_payment__advance_id
@@ -44,22 +50,22 @@ msgstr ""
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_employee_form
msgid "Advance"
-msgstr ""
+msgstr "Anticipo"
#. module: hr_expense_advance_clearing
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
msgid "Advance (not cleared)"
-msgstr ""
+msgstr "Anticipo (non liquidato)"
#. module: hr_expense_advance_clearing
#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
msgid "Advance Clearing"
-msgstr ""
+msgstr "Liquidazione anticipo"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
msgid "Advance Remaining"
-msgstr ""
+msgstr "Residuo anticipo"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/models/hr_employee_base.py:0
@@ -68,95 +74,95 @@ msgstr ""
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_sheet_ids
#, python-format
msgid "Advance Sheet"
-msgstr ""
+msgstr "Foglio anticipo"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
#, python-format
msgid "Advance clearing must not contain any advance expense line"
-msgstr ""
+msgstr "La liquidazione anticipo non deve contenere nessuna riga anticipo spesa"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
#, python-format
msgid "Advance must contain only advance expense line"
-msgstr ""
+msgstr "L'anticipo deve contenere solo una riga anticipo spesa"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
#, python-format
msgid "Advance: %s has no amount to clear"
-msgstr ""
+msgstr "Anticipo: %s non ha un valore da compensare"
#. module: hr_expense_advance_clearing
#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet
#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_advance_sheet
msgid "Advances"
-msgstr ""
+msgstr "Anticipi"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual
msgid "Amount to clear"
-msgstr ""
+msgstr "Valore da liquidare"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual
msgid "Amount to clear of this expense sheet in company currency"
-msgstr ""
+msgstr "Valore da liquidare di questo foglio spese nella valuta dell'azienda"
#. module: hr_expense_advance_clearing
#: model:ir.model,name:hr_expense_advance_clearing.model_hr_employee_base
msgid "Basic Employee"
-msgstr ""
+msgstr "Dipendente base"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
msgid "Clear Advance"
-msgstr ""
+msgstr "Liquida anticipo"
#. module: hr_expense_advance_clearing
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
msgid "Clearing"
-msgstr ""
+msgstr "Liquidazione"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_count
msgid "Clearing Count"
-msgstr ""
+msgstr "Conteggio liquidazione"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__clearing_product_id
msgid "Clearing Product"
-msgstr ""
+msgstr "Prodotto liquidazione"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids
msgid "Clearing Sheet"
-msgstr ""
+msgstr "Foglio liquidazione"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
#, python-format
msgid "Clearing Sheets"
-msgstr ""
+msgstr "Fogli liquidazioni"
#. module: hr_expense_advance_clearing
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
msgid "Clearings"
-msgstr ""
+msgstr "Liquidazioni"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee__advance_count
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_base__advance_count
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_public__advance_count
msgid "Count advance sheet in expense report"
-msgstr ""
+msgstr "Conteggio foglio anticipo nella nota spese"
#. module: hr_expense_advance_clearing
#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
msgid "Create a new expense report"
-msgstr ""
+msgstr "Crea una nuova nota spese"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance
@@ -164,79 +170,80 @@ msgstr ""
#: model:product.product,name:hr_expense_advance_clearing.product_emp_advance
#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template
msgid "Employee Advance"
-msgstr ""
+msgstr "Anticipo dipendente"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
#, python-format
msgid "Employee advance product has no payable account"
-msgstr ""
+msgstr "Il prodotto anticipo dipendente non ha un conto pagabile"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
#, python-format
msgid "Employee advance, account must be the same payable account"
-msgstr ""
+msgstr "Anticipo dipendente, il conto deve essere lo stesso conto pagabile"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
#, python-format
msgid "Employee advance, all taxes must be removed"
-msgstr ""
+msgstr "Anticipo dipendente, devono essere rimosse tutte le tasse"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
#, python-format
msgid "Employee advance, paid by must be employee"
-msgstr ""
+msgstr "Anticipo dipendente, \"pagato da\" deve essere un dipendente"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
#, python-format
msgid "Employee advance, selected product is not valid"
-msgstr ""
+msgstr "Anticipo dipendente, il prodotto selezionato non è valido"
#. module: hr_expense_advance_clearing
#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search
msgid "Expense"
-msgstr ""
+msgstr "Spesa"
#. module: hr_expense_advance_clearing
#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet
msgid "Expense Report"
-msgstr ""
+msgstr "Nota spese"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__av_line_id
msgid "Expense created from this advance expense line"
-msgstr ""
+msgstr "Spesa creata da questa riga anticipo spesa"
#. module: hr_expense_advance_clearing
#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_expense_sheet
#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_expense_sheet
msgid "Expenses"
-msgstr ""
+msgstr "Spese"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable
msgid "Final regiter payment amount even after advance clearing"
msgstr ""
+"Registrazione finale importo pagamento anche dopo la liquidazione anticipata"
#. module: hr_expense_advance_clearing
#: model:ir.model,name:hr_expense_advance_clearing.model_account_move
msgid "Journal Entry"
-msgstr ""
+msgstr "Registrazione contabile"
#. module: hr_expense_advance_clearing
#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet
msgid "No advance report found. Let's create one!"
-msgstr ""
+msgstr "Nessuna nota spese trovata. Crearne una!"
#. module: hr_expense_advance_clearing
#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet
msgid "No expense report found. Let's create one!"
-msgstr ""
+msgstr "Nessuna nota spese trovata. Crearne una!"
#. module: hr_expense_advance_clearing
#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet
@@ -244,6 +251,8 @@ msgid ""
"Once you have created your advance, submit it to your manager who will "
"validate it."
msgstr ""
+"Una volta creato l'anticipo, inviarlo al proprio responsabile che lo "
+"validerà."
#. module: hr_expense_advance_clearing
#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
@@ -251,6 +260,8 @@ msgid ""
"Once you have created your expense, submit it to your manager who will\n"
" validate it."
msgstr ""
+"Una volta calcolata le spesa, inviarla al proprio responsabile che\n"
+" la validerà."
#. module: hr_expense_advance_clearing
#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet
@@ -258,11 +269,15 @@ msgid ""
"Once you have created your expense, submit it to your manager who will "
"validate it."
msgstr ""
+"Una volta creata la spesa, inviarla al proprio responsabile per la "
+"validazione."
#. module: hr_expense_advance_clearing
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_form
msgid "Optional clearing product is used during clear advance"
msgstr ""
+"Il prodotto liquidazione viene usato nella liquidazione anticipo "
+"opzionalmente"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__clearing_product_id
@@ -270,47 +285,49 @@ msgid ""
"Optional: On the clear advance, the clearing product will create default "
"product line."
msgstr ""
+"Opzionale: nella liquidazione anticipo, il prodotto liquidazione creerà la "
+"riga prodotto predefinito."
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable
msgid "Payable Amount"
-msgstr ""
+msgstr "Valore pagabile"
#. module: hr_expense_advance_clearing
#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment
msgid "Payments"
-msgstr ""
+msgstr "Pagamenti"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__av_line_id
msgid "Ref: Advance"
-msgstr ""
+msgstr "Rif: anticipo"
#. module: hr_expense_advance_clearing
#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment_register
msgid "Register Payment"
-msgstr ""
+msgstr "Registra pagamento"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
msgid "Remaining amount to clear the selected advance sheet"
-msgstr ""
+msgstr "Valore residuo per liquidare il foglio anticipo selezionato"
#. module: hr_expense_advance_clearing
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_account_payment_search
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
msgid "Return Advance"
-msgstr ""
+msgstr "Rimborso anticipo"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids
msgid "Show reference clearing on advance"
-msgstr ""
+msgstr "Visualizza riferimento liquidazione sull'anticipo"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
msgid "Show remaining advance of this employee"
-msgstr ""
+msgstr "Visualizza residuo anticipo di questo dipendente"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/models/account_move.py:0
@@ -319,18 +336,22 @@ msgid ""
"This operation is not allowed as some advance amount was already cleared/returned.\n"
"Please cancel those documents first."
msgstr ""
+"Questa operazione non è consentita perché una parte del valore anticipato è "
+"già stato liquidato/reso.\n"
+"Cancellare quella lista documenti."
#. module: hr_expense_advance_clearing
#: model:product.product,uom_name:hr_expense_advance_clearing.product_emp_advance
#: model:product.template,uom_name:hr_expense_advance_clearing.product_emp_advance_product_template
msgid "Units"
-msgstr ""
+msgstr "Unità"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
#, python-format
msgid "You can only register payment for posted journal entries."
msgstr ""
+"Si possono registrare pagamenti solo per registrazioni contabili assegnate."
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
@@ -338,6 +359,7 @@ msgstr ""
msgid ""
"You can't create payments for entries belonging to different companies."
msgstr ""
+"Non si possono creare pagamenti per entrate appartenenti a diverse aziende."
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
@@ -346,9 +368,11 @@ msgid ""
"You can't register a payment because there is nothing left to pay on the "
"selected journal items."
msgstr ""
+"Non si può registrare il pagamento perché non c'è niente da pagare nei "
+"movimenti contabili selezionati."
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
#, python-format
msgid "You cannot return advance more than actual remaining ({}{}){}"
-msgstr ""
+msgstr "Non si può rimborsare un anticipo oltre il restante effettivo ({}{}){}"
From bd05208d12117a23d9177f0240cbe733179cf14a Mon Sep 17 00:00:00 2001
From: mymage
Date: Thu, 30 Nov 2023 12:52:19 +0000
Subject: [PATCH 59/75] Translated using Weblate (Italian)
Currently translated at 100.0% (56 of 56 strings)
Translation: hr-expense-16.0/hr-expense-16.0-hr_expense_advance_clearing
Translate-URL: https://translation.odoo-community.org/projects/hr-expense-16-0/hr-expense-16-0-hr_expense_advance_clearing/it/
(cherry picked from commit 1d625012a7f70bad1e5d0371304b0fb28527f40f)
---
hr_expense_advance_clearing/i18n/it.po | 40 ++++++++++++++++++++++----
1 file changed, 34 insertions(+), 6 deletions(-)
diff --git a/hr_expense_advance_clearing/i18n/it.po b/hr_expense_advance_clearing/i18n/it.po
index f33b49763..76a42e54c 100644
--- a/hr_expense_advance_clearing/i18n/it.po
+++ b/hr_expense_advance_clearing/i18n/it.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2023-07-04 09:09+0000\n"
+"PO-Revision-Date: 2023-11-30 15:35+0000\n"
"Last-Translator: mymage \n"
"Language-Team: none\n"
"Language: it\n"
@@ -24,7 +24,7 @@ msgid ""
"Note: pending amount clearing is {}{}"
msgstr ""
"\n"
-"Nota: il valore da saldare in attesa è {}{}"
+"Nota: valore liquidazione in attesa è %(symbol)s%(amount)s"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_count
@@ -40,8 +40,8 @@ msgid ""
"A remaining advance return of {} {} with the reference {} related to your "
"expense {} has been made."
msgstr ""
-"È stato eseguito un rimborso anticipo residuo di {} {} con riferimento {} "
-"relativo alla sua spesa {}."
+"È stato fatto un residuo reso anticipo di %(amount)s %(symbol)s con "
+"riferimento %(ref)s relativo alla sua spesa %(name)s."
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_account_payment__advance_id
@@ -374,5 +374,33 @@ msgstr ""
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
#, python-format
-msgid "You cannot return advance more than actual remaining ({}{}){}"
-msgstr "Non si può rimborsare un anticipo oltre il restante effettivo ({}{}){}"
+msgid ""
+"You cannot return advance more than actual remaining (%(symbol)s"
+"%(amount)s)%(more_info)s"
+msgstr ""
+"Non è possibile restituire un anticipo superiore al rimanente effettivo "
+"(%(symbol)s%(amount)s)%(more_info)s"
+
+#, python-format
+#~ msgid ""
+#~ "\n"
+#~ "Note: pending amount clearing is {}{}"
+#~ msgstr ""
+#~ "\n"
+#~ "Nota: il valore da saldare in attesa è {}{}"
+
+#, python-format
+#~ msgid ""
+#~ "A remaining advance return of {} {} with the reference {} related to your "
+#~ "expense {} has been made."
+#~ msgstr ""
+#~ "È stato eseguito un rimborso anticipo residuo di {} {} con riferimento {} "
+#~ "relativo alla sua spesa {}."
+
+#~ msgid "Units"
+#~ msgstr "Unità"
+
+#, python-format
+#~ msgid "You cannot return advance more than actual remaining ({}{}){}"
+#~ msgstr ""
+#~ "Non si può rimborsare un anticipo oltre il restante effettivo ({}{}){}"
From f5600845ad9b99d6689f955c862238b15f0b5d33 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Mon, 24 Jun 2024 18:25:29 +0700
Subject: [PATCH 60/75] [FIX] hr_expense_advance_clearing: clear advance
partial
(cherry picked from commit b91cc902d26f792c28b9f5728ad900c05bc55507)
---
.../models/account_move.py | 43 ++++++++++++++++++-
.../models/hr_expense_sheet.py | 18 ++++++++
2 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/hr_expense_advance_clearing/models/account_move.py b/hr_expense_advance_clearing/models/account_move.py
index 636aea513..777fdf0ab 100644
--- a/hr_expense_advance_clearing/models/account_move.py
+++ b/hr_expense_advance_clearing/models/account_move.py
@@ -1,7 +1,7 @@
# Copyright 2022 Ecosoft Co., Ltd. (https://ecosoft.co.th)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
-from odoo import _, models
+from odoo import _, api, models
from odoo.exceptions import UserError
@@ -36,3 +36,44 @@ def _reverse_moves(self, default_values_list=None, cancel=False):
return super()._reverse_moves(
default_values_list=default_values_list, cancel=cancel
)
+
+ @api.depends(
+ "line_ids.matched_debit_ids.debit_move_id.move_id.payment_id.is_matched",
+ "line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual",
+ "line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual_currency",
+ "line_ids.matched_credit_ids.credit_move_id.move_id.payment_id.is_matched",
+ "line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual",
+ "line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual_currency",
+ "line_ids.balance",
+ "line_ids.currency_id",
+ "line_ids.amount_currency",
+ "line_ids.amount_residual",
+ "line_ids.amount_residual_currency",
+ "line_ids.payment_id.state",
+ "line_ids.full_reconcile_id",
+ "state",
+ )
+ def _compute_amount(self):
+ """Compute amount residual for advance clearing case."""
+ res = super()._compute_amount()
+ for move in self:
+ total_residual = 0.0
+ total_residual_currency = 0.0
+ for line in move.line_ids:
+ if line.account_type not in ("asset_receivable", "liability_payable"):
+ continue
+ # Line residual amount.
+ clearing = line.expense_id.sheet_id.filtered(
+ lambda sheet: sheet.advance_sheet_id
+ )
+ if clearing:
+ # Residual amount.
+ total_residual += line.amount_residual
+ total_residual_currency += line.amount_residual_currency
+
+ # Update amount residual for case clearing
+ if total_residual and total_residual_currency:
+ sign = move.direction_sign
+ move.amount_residual = -sign * total_residual
+ move.amount_residual_signed = total_residual_currency
+ return res
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index 945a03f77..a027d0bed 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -60,6 +60,24 @@ def _check_advance_expense(self):
if advance_lines and len(advance_lines) != len(self.expense_line_ids):
raise ValidationError(_("Advance must contain only advance expense line"))
+ @api.depends("account_move_id.payment_state")
+ def _compute_payment_state(self):
+ """After clear advance.
+ if amount residual is zero, payment state will change to 'paid'
+ """
+ res = super()._compute_payment_state()
+ for sheet in self:
+ if (
+ sheet.advance_sheet_id
+ and sheet.account_move_id.state == "posted"
+ and not sheet.amount_residual
+ ):
+ sheet.payment_state = "paid"
+ return res
+
+ def _get_product_advance(self):
+ return self.env.ref("hr_expense_advance_clearing.product_emp_advance", False)
+
@api.depends("account_move_id.line_ids.amount_residual")
def _compute_clearing_residual(self):
emp_advance = self.env.ref(
From 4ec954f2db1699ee02bec52bfb17831815a8e7fe Mon Sep 17 00:00:00 2001
From: Marcel Savegnago
Date: Fri, 2 Aug 2024 12:09:06 -0300
Subject: [PATCH 61/75] [FIX] hr_expense_advance_clearing: fetch the product
considering the company so that the correct property can be retrieved
(cherry picked from commit 213cb2ff7f64c20889e79fadd467bfe95ac7fda1)
---
hr_expense_advance_clearing/models/hr_expense_sheet.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index a027d0bed..5b5c6df79 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -86,8 +86,11 @@ def _compute_clearing_residual(self):
for sheet in self:
residual_company = 0.0
if emp_advance:
+ property_account_expense_id = emp_advance.with_company(
+ sheet.company_id
+ ).property_account_expense_id
for line in sheet.sudo().account_move_id.line_ids:
- if line.account_id == emp_advance.property_account_expense_id:
+ if line.account_id == property_account_expense_id:
residual_company += line.amount_residual
sheet.clearing_residual = residual_company
From 7e74c7e8e5f9f2b0ecc0bdc313633819f2ab3b59 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?=
Date: Wed, 27 Nov 2024 09:47:41 +0100
Subject: [PATCH 62/75] [FIX] hr_expense_advance_clearing: Fix + improve tests
Related to https://github.com/odoo/odoo/commit/063e224c17c0e0194e5ade6acbe82ff38a669b54
(cherry picked from commit e174e022273524d9f704242cde0fd9e3aa54c276)
---
.../tests/test_hr_expense_advance_clearing.py | 194 ++++++++++++------
1 file changed, 127 insertions(+), 67 deletions(-)
diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
index 32600b99d..02ba48948 100644
--- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
+++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
@@ -1,73 +1,45 @@
# Copyright 2019 Kitti Upariphutthiphong
+# Copyright 2024 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo.exceptions import ValidationError
-from odoo.tests import common
-from odoo.tests.common import Form
+from odoo import fields
+from odoo.exceptions import UserError, ValidationError
+from odoo.tests.common import Form, tagged
+from odoo.tools import mute_logger
+from odoo.addons.hr_expense.tests.common import TestExpenseCommon
-class TestHrExpenseAdvanceClearing(common.TransactionCase):
+
+@tagged("-at_install", "post_install")
+class TestHrExpenseAdvanceClearing(TestExpenseCommon):
@classmethod
- def setUpClass(cls):
- super(TestHrExpenseAdvanceClearing, cls).setUpClass()
- company = cls.env.ref("base.main_company")
- cls.journal_bank = cls.env["account.journal"].search(
- [("type", "=", "bank")], limit=1
- )
- cls.product = cls.env["product.product"].create(
- {"name": "Service 1", "type": "service"}
- )
- tax_group = cls.env["account.tax.group"].create(
- {"name": "Tax Group 1", "sequence": 1}
- )
- cls.tax = cls.env["account.tax"].create(
- {
- "name": "Tax 10.0%",
- "amount": 10.0,
- "amount_type": "percent",
- "type_tax_use": "purchase",
- "company_id": company.id,
- "tax_group_id": tax_group.id,
- }
- )
- employee_home = cls.env["res.partner"].create({"name": "Employee Home Address"})
- cls.employee = cls.env["hr.employee"].create(
- {"name": "Employee A", "address_home_id": employee_home.id}
- )
- advance_account = cls.env["account.account"].create(
- {
- "code": "154000",
- "name": "Employee Advance",
- "user_type_id": cls.env.ref(
- "account.data_account_type_current_assets"
- ).id,
- "reconcile": True,
- }
- )
- cls.account_sales = cls.env["account.account"].create(
- {
- "code": "X1020",
- "name": "Product Sales - (test)",
- "user_type_id": cls.env.ref("account.data_account_type_revenue").id,
- }
- )
+ def setUpClass(cls, chart_template_ref=None):
+ super().setUpClass(chart_template_ref=chart_template_ref)
+ advance_account = cls.company_data["default_account_assets"]
+ advance_account.reconcile = True
cls.emp_advance = cls.env.ref("hr_expense_advance_clearing.product_emp_advance")
cls.emp_advance.property_account_expense_id = advance_account
+ cls.product_a.standard_price = 0
# Create advance expense 1,000
cls.advance = cls._create_expense_sheet(
- cls, "Advance 1,000", cls.employee, cls.emp_advance, 1000.0, advance=True
+ cls,
+ "Advance 1,000",
+ cls.expense_employee,
+ cls.emp_advance,
+ 1000.0,
+ advance=True,
)
# Create clearing expense 1,000
cls.clearing_equal = cls._create_expense_sheet(
- cls, "Buy service 1,000", cls.employee, cls.product, 1000.0
+ cls, "Buy service 1,000", cls.expense_employee, cls.product_a, 1000.0
)
# Create clearing expense 1,200
cls.clearing_more = cls._create_expense_sheet(
- cls, "Buy service 1,200", cls.employee, cls.product, 1200.0
+ cls, "Buy service 1,200", cls.expense_employee, cls.product_a, 1200.0
)
# Create clearing expense 800
cls.clearing_less = cls._create_expense_sheet(
- cls, "Buy service 800", cls.employee, cls.product, 800.0
+ cls, "Buy service 800", cls.expense_employee, cls.product_a, 800.0
)
def _create_expense(
@@ -112,16 +84,19 @@ def _create_expense_sheet(
)
return expense_sheet
- def _register_payment(self, expense_sheet, hr_return_advance=False):
- ctx = {
- "active_ids": [expense_sheet.id],
- "active_id": expense_sheet.id,
- "hr_return_advance": hr_return_advance,
- "active_model": "hr.expense.sheet",
- }
- PaymentWizard = self.env["hr.expense.sheet.register.payment.wizard"]
- with Form(PaymentWizard.with_context(ctx)) as f:
- f.journal_id = self.journal_bank
+ def _register_payment(self, move_id, amount, ctx=False, hr_return_advance=False):
+ if not ctx:
+ ctx = {
+ "active_ids": [move_id.id],
+ "active_id": move_id.id,
+ "active_model": "account.move",
+ }
+ ctx["hr_return_advance"] = hr_return_advance
+ PaymentWizard = self.env["account.payment.register"]
+ with Form(PaymentWizard.with_context(**ctx)) as f:
+ f.journal_id = self.company_data["default_journal_bank"]
+ f.payment_date = fields.Date.today()
+ f.amount = amount
payment_wizard = f.save()
payment_wizard.expense_post_payment()
@@ -135,42 +110,66 @@ def test_0_test_constraints(self):
with self.assertRaises(ValidationError):
expense = self._create_expense(
"Advance 1,000",
- self.employee,
+ self.expense_employee,
self.emp_advance,
1.0,
advance=True,
account=self.account_sales,
)
+ expense.account_id = self.company_data["default_account_payable"]
+ expense._check_advance()
# Advance Sheet should not have > 1 expense lines
with self.assertRaises(ValidationError):
expense = self._create_expense(
- "Buy service 1,000", self.employee, self.product, 1.0
+ "Buy service 1,000", self.expense_employee, self.product_a, 1.0
)
self.advance.write({"expense_line_ids": [(4, expense.id)]})
# Advance Expense's product, must not has tax involved
with self.assertRaises(ValidationError):
- self.emp_advance.supplier_taxes_id |= self.tax
+ self.emp_advance.supplier_taxes_id |= self.company_data[
+ "default_tax_purchase"
+ ]
expense = self._create_expense(
- "Advance 1,000", self.employee, self.emp_advance, 1.0, advance=True
+ "Advance 1,000",
+ self.expense_employee,
+ self.emp_advance,
+ 1.0,
+ advance=True,
)
self.emp_advance.supplier_taxes_id = False # Remove tax bf proceed
# Advance Expense, must not paid by company
with self.assertRaises(ValidationError):
expense = self._create_expense(
"Advance 1,000",
- self.employee,
+ self.expense_employee,
self.emp_advance,
1.0,
advance=True,
payment_mode="company_account",
)
+ # Advance Expense, must be product advance only
+ with self.assertRaises(ValidationError):
+ expense = self._create_expense(
+ "Advance 1,000",
+ self.expense_employee,
+ self.emp_advance,
+ 1.0,
+ advance=True,
+ )
+ expense.product_id = self.product_a
+ expense._check_advance()
# Advance Expense's product must have account configured
with self.assertRaises(ValidationError):
self.emp_advance.property_account_expense_id = False
expense = self._create_expense(
- "Advance 1,000", self.employee, self.emp_advance, 1.0, advance=True
+ "Advance 1,000",
+ self.expense_employee,
+ self.emp_advance,
+ 1.0,
+ advance=True,
)
+ @mute_logger("odoo.models.unlink")
def test_1_clear_equal_advance(self):
""" When clear equal advance, all set """
# ------------------ Advance --------------------------
@@ -193,7 +192,34 @@ def test_1_clear_equal_advance(self):
# Equal amount, state change to Paid and advance is cleared
self.assertEqual(self.clearing_equal.state, "done")
self.assertEqual(self.clearing_equal.advance_sheet_residual, 0.0)
+ # Clear this with previous advance is done
+ self.clearing_more.advance_sheet_id = self.advance
+ self.clearing_more.action_submit_sheet()
+ self.clearing_more.approve_expense_sheets()
+ with self.assertRaises(ValidationError):
+ self.clearing_more.action_sheet_move_create()
+ # There are 2 clearing in advance
+ self.assertEqual(self.advance.clearing_count, 2)
+ # Check link clearing in advance must be equal clearing count
+ clearing_dict = self.advance.action_open_clearings()
+ self.assertEqual(
+ len(clearing_dict["domain"][0][2]), self.advance.clearing_count
+ )
+ # Check advance from employee
+ self.assertEqual(self.expense_employee.advance_count, 1)
+ clearing_document = self.expense_employee.action_open_advance_clearing()
+ self.assertEqual(
+ len(clearing_document["domain"][0][2]), self.expense_employee.advance_count
+ )
+ # Check back state move in advance after create clearing
+ with self.assertRaises(UserError):
+ self.advance.account_move_id.button_draft()
+ with self.assertRaises(UserError):
+ self.advance.account_move_id.button_cancel()
+ with self.assertRaises(UserError):
+ self.advance.account_move_id._reverse_moves()
+ @mute_logger("odoo.models.unlink")
def test_2_clear_more_than_advance(self):
""" When clear more than advance, do pay more """
# ------------------ Advance --------------------------
@@ -216,6 +242,7 @@ def test_2_clear_more_than_advance(self):
self._register_payment(self.clearing_more)
self.assertEqual(self.clearing_more.state, "done")
+ @mute_logger("odoo.models.unlink")
def test_3_clear_less_than_advance(self):
""" When clear less than advance, do return advance """
# ------------------ Advance --------------------------
@@ -238,3 +265,36 @@ def test_3_clear_less_than_advance(self):
# Back to advance and do return advance, clearing residual become 0.0
self._register_payment(self.advance, hr_return_advance=True)
self.assertEqual(self.advance.clearing_residual, 0.0)
+ # Check payment of return advance
+ payment = self.env["account.payment"].search(
+ [("advance_id", "=", self.advance.id)]
+ )
+ self.assertEqual(len(payment), 1)
+
+ @mute_logger("odoo.models.unlink")
+ def test_4_clearing_product_advance(self):
+ """When select clearing product on advance"""
+ # ------------------ Advance --------------------------
+ self.advance.expense_line_ids.clearing_product_id = self.product_a
+ self.advance.action_submit_sheet()
+ self.advance.approve_expense_sheets()
+ self.advance.action_sheet_move_create()
+ self.assertEqual(self.advance.clearing_residual, 1000.0)
+ self._register_payment(self.advance.account_move_id, 1000.0)
+ self.assertEqual(self.advance.state, "done")
+ # ------------------ Clearing --------------------------
+ with Form(self.env["hr.expense.sheet"]) as sheet:
+ sheet.name = "Test Clearing"
+ sheet.employee_id = self.expense_employee
+ ex_sheet = sheet.save()
+ ex_sheet.advance_sheet_id = self.advance
+ self.assertEqual(len(ex_sheet.expense_line_ids), 0)
+ ex_sheet._onchange_advance_sheet_id()
+ self.assertEqual(len(ex_sheet.expense_line_ids), 1)
+ reverse_move = self.advance.account_move_id._reverse_moves(
+ default_values_list=[
+ {"invoice_date": self.advance.account_move_id.date, "ref": False}
+ ],
+ cancel=True,
+ )
+ self.assertNotEqual(reverse_move, self.advance.account_move_id)
From 07c9b9235b5f424ee4662b7beb5b9cbfe3691c57 Mon Sep 17 00:00:00 2001
From: "Saran @ Ecosoft"
Date: Wed, 21 May 2025 03:13:05 +0000
Subject: [PATCH 63/75] Added translation using Weblate (Thai)
(cherry picked from commit 32354d4e8e9ebadeed54dab9d8be42181f40348b)
---
hr_expense_advance_clearing/i18n/th.po | 328 +++++++++++++++++++++++++
1 file changed, 328 insertions(+)
create mode 100644 hr_expense_advance_clearing/i18n/th.po
diff --git a/hr_expense_advance_clearing/i18n/th.po b/hr_expense_advance_clearing/i18n/th.po
new file mode 100644
index 000000000..33d3c4d6f
--- /dev/null
+++ b/hr_expense_advance_clearing/i18n/th.po
@@ -0,0 +1,328 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * hr_expense_advance_clearing
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: th\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+msgid ""
+"\n"
+"Note: pending amount clearing is %(symbol)s%(amount)s"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_count
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_count
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_count
+msgid "# of Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_account_payment__advance_id
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_employee_public_view_form
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_employee_form
+msgid "Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
+msgid "Advance (not cleared)"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid "Advance Clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
+msgid "Advance Remaining"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_employee_base.py:0
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_sheet_ids
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_sheet_ids
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_sheet_ids
+msgid "Advance Sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Advance clearing must not contain any advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Advance must contain only advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet
+#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_advance_sheet
+msgid "Advances"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual
+msgid "Amount to clear"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual
+msgid "Amount to clear of this expense sheet in company currency"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_employee_base
+msgid "Basic Employee"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Clear Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
+msgid "Clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_count
+msgid "Clearing Count"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__clearing_product_id
+msgid "Clearing Product"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids
+msgid "Clearing Sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Clearings"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee__advance_count
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_base__advance_count
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_public__advance_count
+msgid "Count advance sheet in expense report"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid "Create a new expense report"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance
+#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template
+msgid "Employee Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+msgid "Employee advance product has no payable account"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+msgid "Employee advance, account must be the same payable account"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+msgid "Employee advance, all taxes must be removed"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+msgid "Employee advance, paid by must be employee"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+msgid "Employee advance, selected product is not valid"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search
+msgid "Expense"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet
+msgid "Expense Report"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__av_line_id
+msgid "Expense created from this advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_expense_sheet
+#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_expense_sheet
+msgid "Expenses"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable
+msgid "Final regiter payment amount even after advance clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_move
+msgid "Journal Entry"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet
+msgid "No advance report found. Let's create one!"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet
+msgid "No expense report found. Let's create one!"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet
+msgid ""
+"Once you have created your advance, submit it to your manager who will "
+"validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid ""
+"Once you have created your expense, submit it to your manager who will\n"
+" validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet
+msgid ""
+"Once you have created your expense, submit it to your manager who will "
+"validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_form
+msgid "Optional clearing product is used during clear advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__clearing_product_id
+msgid ""
+"Optional: On the clear advance, the clearing product will create default "
+"product line."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment_register
+msgid "Pay"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable
+msgid "Payable Amount"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids
+msgid "Payment Return"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment
+msgid "Payments"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__av_line_id
+msgid "Ref: Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
+msgid "Remaining amount to clear the selected advance sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Return"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_account_payment_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Return Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__return_count
+msgid "Return Count"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids
+msgid "Show reference clearing on advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids
+msgid "Show reference return advance on advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
+msgid "Show remaining advance of this employee"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/account_move.py:0
+msgid ""
+"This operation is not allowed as some advance amount was already cleared/returned.\n"
+"Please cancel those documents first."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+msgid ""
+"You cannot return advance more than actual remaining "
+"(%(symbol)s%(amount)s)%(more_info)s"
+msgstr ""
From b8c0fb47bddc0f9b5786481a1e6302963481d463 Mon Sep 17 00:00:00 2001
From: "Saran @ Ecosoft"
Date: Fri, 23 May 2025 03:55:42 +0000
Subject: [PATCH 64/75] Added translation using Weblate (Thai (th_TH))
(cherry picked from commit 9831b13a268154dc783d931f51123f76ecf79052)
---
hr_expense_advance_clearing/i18n/th_TH.po | 328 ++++++++++++++++++++++
1 file changed, 328 insertions(+)
create mode 100644 hr_expense_advance_clearing/i18n/th_TH.po
diff --git a/hr_expense_advance_clearing/i18n/th_TH.po b/hr_expense_advance_clearing/i18n/th_TH.po
new file mode 100644
index 000000000..6904c01c9
--- /dev/null
+++ b/hr_expense_advance_clearing/i18n/th_TH.po
@@ -0,0 +1,328 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * hr_expense_advance_clearing
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: th_TH\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+msgid ""
+"\n"
+"Note: pending amount clearing is %(symbol)s%(amount)s"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_count
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_count
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_count
+msgid "# of Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_account_payment__advance_id
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_employee_public_view_form
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_employee_form
+msgid "Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
+msgid "Advance (not cleared)"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid "Advance Clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
+msgid "Advance Remaining"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_employee_base.py:0
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_sheet_ids
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_sheet_ids
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_sheet_ids
+msgid "Advance Sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Advance clearing must not contain any advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Advance must contain only advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet
+#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_advance_sheet
+msgid "Advances"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual
+msgid "Amount to clear"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual
+msgid "Amount to clear of this expense sheet in company currency"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_employee_base
+msgid "Basic Employee"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Clear Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
+msgid "Clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_count
+msgid "Clearing Count"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__clearing_product_id
+msgid "Clearing Product"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids
+msgid "Clearing Sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Clearings"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee__advance_count
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_base__advance_count
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_public__advance_count
+msgid "Count advance sheet in expense report"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid "Create a new expense report"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance
+#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template
+msgid "Employee Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+msgid "Employee advance product has no payable account"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+msgid "Employee advance, account must be the same payable account"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+msgid "Employee advance, all taxes must be removed"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+msgid "Employee advance, paid by must be employee"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+msgid "Employee advance, selected product is not valid"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search
+msgid "Expense"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet
+msgid "Expense Report"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__av_line_id
+msgid "Expense created from this advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_expense_sheet
+#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_expense_sheet
+msgid "Expenses"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable
+msgid "Final regiter payment amount even after advance clearing"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_move
+msgid "Journal Entry"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet
+msgid "No advance report found. Let's create one!"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet
+msgid "No expense report found. Let's create one!"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet
+msgid ""
+"Once you have created your advance, submit it to your manager who will "
+"validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
+msgid ""
+"Once you have created your expense, submit it to your manager who will\n"
+" validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet
+msgid ""
+"Once you have created your expense, submit it to your manager who will "
+"validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_form
+msgid "Optional clearing product is used during clear advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__clearing_product_id
+msgid ""
+"Optional: On the clear advance, the clearing product will create default "
+"product line."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment_register
+msgid "Pay"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable
+msgid "Payable Amount"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids
+msgid "Payment Return"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment
+msgid "Payments"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__av_line_id
+msgid "Ref: Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
+msgid "Remaining amount to clear the selected advance sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Return"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_account_payment_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Return Advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__return_count
+msgid "Return Count"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids
+msgid "Show reference clearing on advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids
+msgid "Show reference return advance on advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
+msgid "Show remaining advance of this employee"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/account_move.py:0
+msgid ""
+"This operation is not allowed as some advance amount was already cleared/returned.\n"
+"Please cancel those documents first."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+msgid ""
+"You cannot return advance more than actual remaining "
+"(%(symbol)s%(amount)s)%(more_info)s"
+msgstr ""
From 8cc55f9248c16183c4c36c20cca99f280259d526 Mon Sep 17 00:00:00 2001
From: Christopher Rogos
Date: Fri, 23 May 2025 07:00:23 +0000
Subject: [PATCH 65/75] [IMP] hr_expense_advance_clearing: post migration
cleanup
(cherry picked from commit 4dcae4dcb350b9a388ff07b86af4564561006aa2)
---
.../models/account_move.py | 4 +-
.../models/hr_employee_base.py | 6 +--
.../models/hr_expense.py | 18 ++++++---
.../models/hr_expense_sheet.py | 40 +++++++++++++++++--
4 files changed, 54 insertions(+), 14 deletions(-)
diff --git a/hr_expense_advance_clearing/models/account_move.py b/hr_expense_advance_clearing/models/account_move.py
index 777fdf0ab..0b796e081 100644
--- a/hr_expense_advance_clearing/models/account_move.py
+++ b/hr_expense_advance_clearing/models/account_move.py
@@ -1,7 +1,7 @@
# Copyright 2022 Ecosoft Co., Ltd. (https://ecosoft.co.th)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
-from odoo import _, api, models
+from odoo import models
from odoo.exceptions import UserError
@@ -17,7 +17,7 @@ def _check_hr_advance_move_reconciled(self):
)
if reconciled_av_move_lines:
raise UserError(
- _(
+ self.env._(
"This operation is not allowed as some advance amount was already "
"cleared/returned.\nPlease cancel those documents first."
)
diff --git a/hr_expense_advance_clearing/models/hr_employee_base.py b/hr_expense_advance_clearing/models/hr_employee_base.py
index 28a058091..a625ea0d2 100644
--- a/hr_expense_advance_clearing/models/hr_employee_base.py
+++ b/hr_expense_advance_clearing/models/hr_employee_base.py
@@ -1,7 +1,7 @@
# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import _, fields, models
+from odoo import fields, models
class HrEmployeeBase(models.AbstractModel):
@@ -25,9 +25,9 @@ def _compute_advance_count(self):
def action_open_advance_clearing(self):
self.ensure_one()
return {
- "name": _("Advance Sheet"),
+ "name": self.env._("Advance Sheet"),
"type": "ir.actions.act_window",
"res_model": "hr.expense.sheet",
- "view_mode": "tree,form",
+ "view_mode": "list,form",
"domain": [("id", "in", self.advance_sheet_ids.ids)],
}
diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py
index 1e0e3476c..b8e55c7dd 100644
--- a/hr_expense_advance_clearing/models/hr_expense.py
+++ b/hr_expense_advance_clearing/models/hr_expense.py
@@ -1,7 +1,7 @@
# Copyright 2019 Kitti Upariphutthiphong
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import _, api, fields, models
+from odoo import Command, api, fields, models
from odoo.exceptions import ValidationError
@@ -34,20 +34,26 @@ def _check_advance(self):
)
if not emp_advance.property_account_expense_id:
raise ValidationError(
- _("Employee advance product has no payable account")
+ self.env._("Employee advance product has no payable account")
)
if expense.product_id != emp_advance:
raise ValidationError(
- _("Employee advance, selected product is not valid")
+ self.env._("Employee advance, selected product is not valid")
)
if expense.account_id != emp_advance.property_account_expense_id:
raise ValidationError(
- _("Employee advance, account must be the same payable account")
+ self.env._(
+ "Employee advance, account must be the same payable account"
+ )
)
if expense.tax_ids:
- raise ValidationError(_("Employee advance, all taxes must be removed"))
+ raise ValidationError(
+ self.env._("Employee advance, all taxes must be removed")
+ )
if expense.payment_mode != "own_account":
- raise ValidationError(_("Employee advance, paid by must be employee"))
+ raise ValidationError(
+ self.env._("Employee advance, paid by must be employee")
+ )
return True
@api.onchange("advance")
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index 5b5c6df79..b5c2a404d 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -1,7 +1,9 @@
# Copyright 2019 Kitti Upariphutthiphong
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import _, api, fields, models
+import ast
+
+from odoo import Command, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools.safe_eval import safe_eval
@@ -55,10 +57,12 @@ def _check_advance_expense(self):
advance_lines = self.expense_line_ids.filtered("advance")
if self.advance_sheet_id and advance_lines:
raise ValidationError(
- _("Advance clearing must not contain any advance expense line")
+ self.env._("Advance clearing must not contain any advance expense line")
)
if advance_lines and len(advance_lines) != len(self.expense_line_ids):
- raise ValidationError(_("Advance must contain only advance expense line"))
+ raise ValidationError(
+ self.env._("Advance must contain only advance expense line")
+ )
@api.depends("account_move_id.payment_state")
def _compute_payment_state(self):
@@ -197,3 +201,33 @@ def _prepare_clear_advance(self, line):
for k, v in clearing_dict.items()
}
return clearing_dict
+
+ def action_open_clearings(self):
+ self.ensure_one()
+ return {
+ "name": self.env._("Clearing Sheets"),
+ "type": "ir.actions.act_window",
+ "res_model": "hr.expense.sheet",
+ "view_mode": "list,form",
+ "domain": [("id", "in", self.clearing_sheet_ids.ids)],
+ }
+
+ def action_open_payment_return(self):
+ self.ensure_one()
+ return {
+ "name": self.env._("Payment Return"),
+ "type": "ir.actions.act_window",
+ "res_model": "account.payment",
+ "view_mode": "list,form",
+ "domain": [("id", "in", self.payment_return_ids.ids)],
+ }
+
+ def action_register_payment(self):
+ action = super().action_register_payment()
+ if self.env.context.get("hr_return_advance"):
+ action["context"].update(
+ {
+ "clearing_sheet_ids": self.clearing_sheet_ids.ids,
+ }
+ )
+ return action
From 999347fd76153d57e762f6393594bd6bacbb53a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20Ba=C3=B1=C3=B3n?=
Date: Mon, 7 Jul 2025 17:07:28 +0200
Subject: [PATCH 66/75] [FIX] hr_expense_advance_clearing: Check approval
before other modules might intervene.
TT55529
(cherry picked from commit 4a89241b133479c03c1fce6be751b5bcca84b819)
---
.../models/hr_expense_sheet.py | 120 +++++++++++++++++-
1 file changed, 118 insertions(+), 2 deletions(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index b5c2a404d..365152abb 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -130,10 +130,126 @@ def action_sheet_move_create(self):
)
adv_move_lines.with_context(ctx).reconcile()
# Update state on clearing advance when advance residual > total amount
- if sheet.advance_sheet_id and advance_residual != -1:
- sheet.write({"state": "done"})
+ if advance_residual != -1:
+ sheet.write(
+ {
+ "state": "done",
+ }
+ )
+ # Update amount residual and state when advance residual < total amount
+ else:
+ sheet.write(
+ {
+ "state": "post",
+ "payment_state": "not_paid",
+ "amount_residual": sheet.total_amount
+ - amount_residual_bf_reconcile,
+ }
+ )
return res
+ def _get_move_line_vals(self):
+ self.ensure_one()
+ move_line_vals = []
+ advance_to_clear = self.advance_sheet_residual
+ emp_advance = self._get_product_advance()
+ account_advance = emp_advance.property_account_expense_id
+ for expense in self.expense_line_ids:
+ move_line_name = (
+ f"{expense.employee_id.name}: {expense.name.splitlines()[0][:64]}"
+ )
+ partner_id = expense.employee_id.sudo().work_contact_id.id
+
+ total_amount = -expense.total_amount
+ total_amount_currency = -expense.total_amount_currency
+
+ # Source move line
+ move_line_src = expense._get_move_line_src(move_line_name, partner_id)
+ move_line_values = [move_line_src]
+
+ # Destination move line
+ move_line_dst = expense._get_move_line_dst(
+ move_line_name,
+ partner_id,
+ total_amount,
+ total_amount_currency,
+ account_advance,
+ )
+
+ # Check clearing > advance, it will split line
+ credit = move_line_dst["credit"]
+ # cr payable -> cr advance
+ remain_payable = 0.0
+ payable_move_line = []
+ rounding = expense.currency_id.rounding
+ if (
+ float_compare(
+ credit,
+ advance_to_clear,
+ precision_rounding=rounding,
+ )
+ == 1
+ ):
+ remain_payable = credit - advance_to_clear
+ move_line_dst.update(
+ {"credit": advance_to_clear, "amount_currency": -advance_to_clear}
+ )
+ advance_to_clear = 0.0
+ # extra payable line
+ account_dest = expense.sheet_id._get_expense_account_destination()
+ payable_move_line = move_line_dst.copy()
+ payable_move_line.update(
+ {
+ "credit": remain_payable,
+ "amount_currency": -remain_payable,
+ "account_id": account_dest,
+ }
+ )
+ else:
+ advance_to_clear -= credit # Reduce remaining advance
+
+ # Add destination first (if credit is not zero)
+ if not float_is_zero(move_line_dst["credit"], precision_rounding=rounding):
+ move_line_values.append(move_line_dst)
+ if payable_move_line:
+ move_line_values.append(payable_move_line)
+ move_line_vals.extend(move_line_values)
+ return move_line_vals
+
+ def _prepare_bills_vals(self):
+ """create journal entry instead of bills when clearing document"""
+ self.ensure_one()
+ res = super()._prepare_bills_vals()
+ if self.advance_sheet_id and self.payment_mode == "own_account":
+ # Advance Sheets with no residual left
+ if self.advance_sheet_residual <= 0.0:
+ raise ValidationError(
+ self.env._(
+ "Advance: %(name)s has no amount to clear", name=self.name
+ )
+ )
+ res.update(
+ {
+ "move_type": "entry",
+ "line_ids": [
+ Command.create(vals) for vals in self._get_move_line_vals()
+ ],
+ }
+ )
+ return res
+
+ def _check_can_approve(self):
+ """Check advance residual before approval"""
+ for sheet in self.filtered("advance_sheet_id"):
+ if sheet.advance_sheet_residual <= 0.0:
+ raise ValidationError(
+ self.env._(
+ "Advance: %(name)s has no amount to clear",
+ name=sheet.advance_sheet_id.name,
+ )
+ )
+ return super()._check_can_approve()
+
def open_clear_advance(self):
self.ensure_one()
action = self.env.ref(
From 63264dccca2c12a4e90b2bf5215227910f7ed2cc Mon Sep 17 00:00:00 2001
From: mymage
Date: Fri, 11 Jul 2025 18:32:50 +0000
Subject: [PATCH 67/75] Translated using Weblate (Italian)
Currently translated at 100.0% (56 of 56 strings)
Translation: hr-expense-18.0/hr-expense-18.0-hr_expense_advance_clearing
Translate-URL: https://translation.odoo-community.org/projects/hr-expense-18-0/hr-expense-18-0-hr_expense_advance_clearing/it/
(cherry picked from commit 533941abaf6ce47617d7726bbe889e55b3deb374)
---
hr_expense_advance_clearing/i18n/it.po | 39 ++++++++++++++++++++++----
1 file changed, 33 insertions(+), 6 deletions(-)
diff --git a/hr_expense_advance_clearing/i18n/it.po b/hr_expense_advance_clearing/i18n/it.po
index 76a42e54c..22b0ec1c0 100644
--- a/hr_expense_advance_clearing/i18n/it.po
+++ b/hr_expense_advance_clearing/i18n/it.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2023-11-30 15:35+0000\n"
+"PO-Revision-Date: 2025-07-11 21:25+0000\n"
"Last-Translator: mymage \n"
"Language-Team: none\n"
"Language: it\n"
@@ -14,7 +14,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.17\n"
+"X-Generator: Weblate 5.10.4\n"
#. module: hr_expense_advance_clearing
#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
@@ -89,10 +89,10 @@ msgid "Advance must contain only advance expense line"
msgstr "L'anticipo deve contenere solo una riga anticipo spesa"
#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
-#, python-format
-msgid "Advance: %s has no amount to clear"
-msgstr "Anticipo: %s non ha un valore da compensare"
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Advance: %(name)s has no amount to clear"
+msgstr "Anticipo: %(name)s non ha alcun importo da saldare"
#. module: hr_expense_advance_clearing
#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet
@@ -288,11 +288,23 @@ msgstr ""
"Opzionale: nella liquidazione anticipo, il prodotto liquidazione creerà la "
"riga prodotto predefinito."
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment_register
+msgid "Pay"
+msgstr "Paga"
+
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable
msgid "Payable Amount"
msgstr "Valore pagabile"
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids
+msgid "Payment Return"
+msgstr "Rimborso pagamento"
+
#. module: hr_expense_advance_clearing
#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment
msgid "Payments"
@@ -313,17 +325,32 @@ msgstr "Registra pagamento"
msgid "Remaining amount to clear the selected advance sheet"
msgstr "Valore residuo per liquidare il foglio anticipo selezionato"
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Return"
+msgstr "Rimborso"
+
#. module: hr_expense_advance_clearing
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_account_payment_search
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
msgid "Return Advance"
msgstr "Rimborso anticipo"
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__return_count
+msgid "Return Count"
+msgstr "Conteggio rimborsi"
+
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids
msgid "Show reference clearing on advance"
msgstr "Visualizza riferimento liquidazione sull'anticipo"
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids
+msgid "Show reference return advance on advance"
+msgstr "Visualizza riferimento anticipo rimborso nell'anticipo"
+
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
msgid "Show remaining advance of this employee"
From b974bbdaa3fedc692dc9c5a15770e3dc4b858260 Mon Sep 17 00:00:00 2001
From: Eduardo de Miguel
Date: Wed, 3 Dec 2025 10:25:47 +0100
Subject: [PATCH 68/75] [REF] hr_expense_advance_clearing: Check if all moves
are posted (compatibility)
Since some other modules can introduce other moves to the expense sheet,
this refactor allow some compatibility between those modules without
changing behavior on this module.
(cherry picked from commit 2be9623d707bde24e344cd0c76bf150549a9fa8d)
---
hr_expense_advance_clearing/models/hr_expense_sheet.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
index 365152abb..fb9cdd1bd 100644
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py
@@ -73,7 +73,7 @@ def _compute_payment_state(self):
for sheet in self:
if (
sheet.advance_sheet_id
- and sheet.account_move_id.state == "posted"
+ and set(sheet.account_move_ids.mapped("state")) == {"posted"}
and not sheet.amount_residual
):
sheet.payment_state = "paid"
From fe88988596cfe26d3ac53b61adde76df7981888d Mon Sep 17 00:00:00 2001
From: Don Kendall
Date: Sat, 30 May 2026 10:57:07 -0400
Subject: [PATCH 69/75] [IMP] hr_expense_payment: pre-commit auto fixes
---
hr_expense_payment/readme/DESCRIPTION.md | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/hr_expense_payment/readme/DESCRIPTION.md b/hr_expense_payment/readme/DESCRIPTION.md
index 37b15d1c7..b86bafd0e 100644
--- a/hr_expense_payment/readme/DESCRIPTION.md
+++ b/hr_expense_payment/readme/DESCRIPTION.md
@@ -1,2 +1,13 @@
-This module allow payment link to expense. After you register payment on
-expense sheet, it will link between expense sheet and payment.
+This module records a bidirectional link between each `hr.expense` record
+and the `account.payment` records that paid it (via `hr.expense.payment_ids`
+and `account.payment.expense_ids`).
+
+When a user clicks **Register Payment** on a posted employee-paid expense
+(`payment_mode='own_account'`), the resulting payment back-links to the
+source expense. A `post_init_hook` backfills the link for payments that
+predate the module install by walking reconciliation chains.
+
+19.0 note: Odoo core's `hr.expense.action_pay()` already exists and
+launches the standard register-payment wizard. This module *adds the
+back-link* — core doesn't track which payment(s) paid which expense
+beyond the implicit reconciliation graph.
From 5f7aadb98e50dc6ea831e7a5fe970b7be9d24468 Mon Sep 17 00:00:00 2001
From: Don Kendall
Date: Sat, 30 May 2026 10:57:07 -0400
Subject: [PATCH 70/75] [MIG] hr_expense_payment: Migration to 19.0
hr.expense.sheet was removed in 19.0; retarget the payment register to hr.expense.
---
README.md | 6 +-
hr_expense_payment/hooks.py | 16 +++--
hr_expense_payment/models/__init__.py | 2 +-
hr_expense_payment/models/account_payment.py | 11 ++--
hr_expense_payment/models/hr_expense.py | 30 +++++++++
hr_expense_payment/models/hr_expense_sheet.py | 26 --------
.../tests/test_hr_expense_payment.py | 66 ++++++++++---------
.../wizard/account_payment_register.py | 23 +++++--
setup/_metapackage/pyproject.toml | 11 ----
9 files changed, 100 insertions(+), 91 deletions(-)
create mode 100644 hr_expense_payment/models/hr_expense.py
delete mode 100644 hr_expense_payment/models/hr_expense_sheet.py
delete mode 100644 setup/_metapackage/pyproject.toml
diff --git a/README.md b/README.md
index 9a16e2533..a19f4af88 100644
--- a/README.md
+++ b/README.md
@@ -17,11 +17,7 @@ hr-expense
[//]: # (addons)
-Available addons
-----------------
-addon | version | maintainers | summary
---- | --- | --- | ---
-[hr_expense_payment](hr_expense_payment/) | 18.0.1.0.0 | | HR Expense Payment
+This part will be replaced when running the oca-gen-addons-table script from OCA/maintainer-tools.
[//]: # (end addons)
diff --git a/hr_expense_payment/hooks.py b/hr_expense_payment/hooks.py
index cdd39fb54..06fe12a69 100644
--- a/hr_expense_payment/hooks.py
+++ b/hr_expense_payment/hooks.py
@@ -1,15 +1,21 @@
# Copyright 2019 Tecnativa - Ernesto Tejeda
+# Copyright 2026 Ledo
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
def post_init_hook(env):
- """Trying to fill the source expense sheet in payments"""
- sheets = env["hr.expense.sheet"].search([("payment_mode", "=", "own_account")])
- for sheet in sheets:
- amls = sheet.account_move_ids.mapped("line_ids")
+ """Backfill payment_ids on existing employee-paid expenses by walking
+ reconciliation: expense -> account_move_id.line_ids -> full_reconcile_id
+ -> reconciled_line_ids.payment_id."""
+ expenses = env["hr.expense"].search(
+ [("payment_mode", "=", "own_account"), ("account_move_id", "!=", False)]
+ )
+ for expense in expenses:
+ amls = expense.account_move_id.line_ids
reconcile = amls.mapped("full_reconcile_id")
aml_payment = reconcile.mapped("reconciled_line_ids").filtered(
lambda r, amls=amls: r not in amls
)
payment = aml_payment.mapped("payment_id")
- payment.write({"expense_sheet_ids": sheet.ids})
+ if payment:
+ payment.write({"expense_ids": [(4, expense.id)]})
diff --git a/hr_expense_payment/models/__init__.py b/hr_expense_payment/models/__init__.py
index c7667a3fa..82e1cb11c 100644
--- a/hr_expense_payment/models/__init__.py
+++ b/hr_expense_payment/models/__init__.py
@@ -1,4 +1,4 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import account_payment
-from . import hr_expense_sheet
+from . import hr_expense
diff --git a/hr_expense_payment/models/account_payment.py b/hr_expense_payment/models/account_payment.py
index 0edcaea31..a208ea815 100644
--- a/hr_expense_payment/models/account_payment.py
+++ b/hr_expense_payment/models/account_payment.py
@@ -1,5 +1,6 @@
# Copyright 2019 Tecnativa - Ernesto Tejeda
# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/)
+# Copyright 2026 Ledo
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
@@ -8,12 +9,12 @@
class AccountPayment(models.Model):
_inherit = "account.payment"
- expense_sheet_ids = fields.Many2many(
- comodel_name="hr.expense.sheet",
- relation="payment_expense_sheet_rel",
+ expense_ids = fields.Many2many(
+ comodel_name="hr.expense",
+ relation="payment_hr_expense_rel",
column1="payment_id",
- column2="sheet_id",
- string="Expense sheet",
+ column2="expense_id",
+ string="Expenses",
readonly=True,
copy=False,
)
diff --git a/hr_expense_payment/models/hr_expense.py b/hr_expense_payment/models/hr_expense.py
new file mode 100644
index 000000000..600c56ff9
--- /dev/null
+++ b/hr_expense_payment/models/hr_expense.py
@@ -0,0 +1,30 @@
+# Copyright 2019 Tecnativa - Ernesto Tejeda
+# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/)
+# Copyright 2026 Ledo
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo import fields, models
+
+
+class HrExpense(models.Model):
+ _inherit = "hr.expense"
+
+ payment_ids = fields.Many2many(
+ comodel_name="account.payment",
+ relation="payment_hr_expense_rel",
+ column1="expense_id",
+ column2="payment_id",
+ string="Payments",
+ readonly=True,
+ copy=False,
+ )
+
+ def action_pay(self):
+ """Thread expense ids through to the register-payment wizard so the
+ resulting account.payment records back-link to the source expenses."""
+ action = super().action_pay()
+ if action and isinstance(action, dict):
+ ctx = dict(action.get("context") or {})
+ ctx["hr_expense_ids"] = self.ids
+ action["context"] = ctx
+ return action
diff --git a/hr_expense_payment/models/hr_expense_sheet.py b/hr_expense_payment/models/hr_expense_sheet.py
deleted file mode 100644
index 59462a687..000000000
--- a/hr_expense_payment/models/hr_expense_sheet.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2019 Tecnativa - Ernesto Tejeda
-# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/)
-# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-
-from odoo import fields, models
-
-
-class HrExpenseSheet(models.Model):
- _inherit = "hr.expense.sheet"
-
- payment_ids = fields.Many2many(
- comodel_name="account.payment",
- relation="payment_expense_sheet_rel",
- column1="sheet_id",
- column2="payment_id",
- string="Payment",
- readonly=True,
- copy=False,
- )
-
- def action_register_payment(self):
- """Send context when you register payment from expense sheet"""
- action = super().action_register_payment()
- if self._name == "hr.expense.sheet":
- action["context"].update({"expense_sheet_ids": self.ids})
- return action
diff --git a/hr_expense_payment/tests/test_hr_expense_payment.py b/hr_expense_payment/tests/test_hr_expense_payment.py
index 636b7159d..e4f336830 100644
--- a/hr_expense_payment/tests/test_hr_expense_payment.py
+++ b/hr_expense_payment/tests/test_hr_expense_payment.py
@@ -1,6 +1,7 @@
# Copyright 2019 Tecnativa - Ernesto Tejeda
# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# Copyright 2024 Tecnativa - Víctor Martínez
+# Copyright 2026 Ledo
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tests import Form, tagged
@@ -15,38 +16,41 @@ class TestHrExpensePayment(TestExpenseCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
- # Create expense + sheet + approve
- cls.expense = cls.create_expense(cls)
- res = cls.expense.action_submit_expenses()
- cls.expense_sheet = cls.env[res["res_model"]].browse(res["res_id"])
- cls.expense_sheet.action_submit_sheet()
- cls.expense_sheet.action_approve_expense_sheets()
+ # Build an employee-paid expense, submit + approve + post (= receipt).
+ cls.expense = cls.create_expenses({"payment_mode": "own_account"})
+ cls.expense.action_submit()
+ cls.expense.action_approve()
+ cls.post_expenses_with_wizard(cls.expense)
def _get_payment_wizard(self):
- res = self.expense_sheet.action_register_payment()
- register_form = Form(self.env[res["res_model"]].with_context(**res["context"]))
- register_form.journal_id = self.company_data["default_journal_bank"]
- register_form.amount = self.expense_sheet.total_amount
- return register_form.save()
-
- def test_post_init_hook(self):
- self.expense_sheet.action_sheet_move_post()
- payment_wizard = self._get_payment_wizard()
- payment_wizard.action_create_payments()
- payment = self.expense_sheet.payment_ids
+ action = self.expense.action_pay()
+ wizard_form = Form(
+ self.env[action["res_model"]].with_context(**action["context"])
+ )
+ wizard_form.journal_id = self.company_data["default_journal_bank"]
+ wizard_form.amount = self.expense.total_amount
+ return wizard_form.save()
+
+ def test_action_pay_links_payment_back_to_expense(self):
+ """After action_pay → create_payment, the resulting payment's
+ expense_ids includes the source expense and the expense's
+ payment_ids includes the new payment."""
+ self.assertFalse(self.expense.payment_ids)
+ wizard = self._get_payment_wizard()
+ wizard.action_create_payments()
+ self.assertEqual(len(self.expense.payment_ids), 1)
+ payment = self.expense.payment_ids
+ self.assertIn(self.expense, payment.expense_ids)
+
+ def test_post_init_hook_backfills_legacy_payments(self):
+ """post_init_hook walks reconciliation to recover the back-link for
+ payments that pre-date the module install."""
+ wizard = self._get_payment_wizard()
+ wizard.action_create_payments()
+ payment = self.expense.payment_ids
self.assertEqual(len(payment), 1)
- self.assertEqual(len(payment.expense_sheet_ids), 1)
- payment.expense_sheet_ids = False
- # Recompute many2one
- payment = self.expense_sheet.payment_ids
- self.assertFalse(payment)
- self.assertFalse(payment.expense_sheet_ids)
+ # Wipe the back-link, then re-run the hook.
+ payment.expense_ids = False
+ self.assertFalse(self.expense.payment_ids)
post_init_hook(self.env)
- self.assertEqual(len(self.expense_sheet.payment_ids), 1)
-
- def test_get_payment_vals(self):
- self.expense_sheet.action_sheet_move_post()
- payment_wizard = self._get_payment_wizard()
- self.assertFalse(self.expense_sheet.payment_ids)
- payment_wizard.action_create_payments()
- self.assertEqual(len(self.expense_sheet.payment_ids), 1)
+ self.assertEqual(len(self.expense.payment_ids), 1)
diff --git a/hr_expense_payment/wizard/account_payment_register.py b/hr_expense_payment/wizard/account_payment_register.py
index 7381fd9ae..a4fade7da 100644
--- a/hr_expense_payment/wizard/account_payment_register.py
+++ b/hr_expense_payment/wizard/account_payment_register.py
@@ -1,5 +1,6 @@
# Copyright 2019 Tecnativa - Ernesto Tejeda
# Copyright 2020 Ecosoft Co., Ltd (https://ecosoft.co.th/)
+# Copyright 2026 Ledo
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models
@@ -10,15 +11,23 @@ class AccountPaymentRegister(models.TransientModel):
def _create_payment_vals_from_wizard(self, batch_result):
payment_vals = super()._create_payment_vals_from_wizard(batch_result)
- expense_sheet_ids = self._context.get("expense_sheet_ids", False)
- if expense_sheet_ids:
- payment_vals.update(expense_sheet_ids=expense_sheet_ids)
+ expense_ids = self.env.context.get("hr_expense_ids")
+ if expense_ids:
+ payment_vals["expense_ids"] = [(6, 0, expense_ids)]
return payment_vals
def _create_payment_vals_from_batch(self, batch_result):
payment_vals = super()._create_payment_vals_from_batch(batch_result)
- expense_sheet_ids = self._context.get("expense_sheet_ids", False)
- if expense_sheet_ids:
- moves = self.env["account.move"].browse(batch_result["lines"].move_id.ids)
- payment_vals.update(expense_sheet_ids=moves.mapped("expense_sheet_id").ids)
+ expense_ids = self.env.context.get("hr_expense_ids")
+ if expense_ids:
+ # Caller-supplied expense ids take precedence (action_pay path).
+ payment_vals["expense_ids"] = [(6, 0, expense_ids)]
+ return payment_vals
+ # Auto-derive from the moves in the batch — covers payments registered
+ # from the account.move side (e.g. directly on the in_receipt) so the
+ # back-link still populates.
+ moves = self.env["account.move"].browse(batch_result["lines"].move_id.ids)
+ expenses = moves.expense_ids
+ if expenses:
+ payment_vals["expense_ids"] = [(6, 0, expenses.ids)]
return payment_vals
diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml
deleted file mode 100644
index 58db04949..000000000
--- a/setup/_metapackage/pyproject.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-[project]
-name = "odoo-addons-oca-hr-expense"
-version = "18.0.20250109.0"
-dependencies = [
- "odoo-addon-hr_expense_payment==18.0.*",
-]
-classifiers=[
- "Programming Language :: Python",
- "Framework :: Odoo",
- "Framework :: Odoo :: 18.0",
-]
From e522b06594a1df40493254ab96fef05e94db7cdd Mon Sep 17 00:00:00 2001
From: Don Kendall
Date: Sat, 30 May 2026 10:57:09 -0400
Subject: [PATCH 71/75] [IMP] hr_expense_advance_clearing: pre-commit auto
fixes
---
hr_expense_advance_clearing/README.rst | 187 ++++++++++++++
hr_expense_advance_clearing/__manifest__.py | 8 +-
.../i18n/hr_expense_advance_clearing.pot | 242 +++++++++++++++---
hr_expense_advance_clearing/i18n/it.po | 113 ++++----
hr_expense_advance_clearing/i18n/th.po | 17 +-
hr_expense_advance_clearing/i18n/th_TH.po | 17 +-
.../readme/CONFIGURE.md | 14 +
.../readme/CONFIGURE.rst | 10 -
.../readme/CONTRIBUTORS.md | 3 +
.../readme/CONTRIBUTORS.rst | 1 -
.../readme/DESCRIPTION.md | 27 ++
.../readme/DESCRIPTION.rst | 11 -
.../readme/INSTALL.rst | 1 -
hr_expense_advance_clearing/readme/USAGE.md | 44 ++++
hr_expense_advance_clearing/readme/USAGE.rst | 33 ---
.../static/description/index.html | 182 +++++++------
16 files changed, 676 insertions(+), 234 deletions(-)
create mode 100644 hr_expense_advance_clearing/README.rst
create mode 100644 hr_expense_advance_clearing/readme/CONFIGURE.md
delete mode 100644 hr_expense_advance_clearing/readme/CONFIGURE.rst
create mode 100644 hr_expense_advance_clearing/readme/CONTRIBUTORS.md
delete mode 100644 hr_expense_advance_clearing/readme/CONTRIBUTORS.rst
create mode 100644 hr_expense_advance_clearing/readme/DESCRIPTION.md
delete mode 100644 hr_expense_advance_clearing/readme/DESCRIPTION.rst
delete mode 100644 hr_expense_advance_clearing/readme/INSTALL.rst
create mode 100644 hr_expense_advance_clearing/readme/USAGE.md
delete mode 100644 hr_expense_advance_clearing/readme/USAGE.rst
diff --git a/hr_expense_advance_clearing/README.rst b/hr_expense_advance_clearing/README.rst
new file mode 100644
index 000000000..cef685f04
--- /dev/null
+++ b/hr_expense_advance_clearing/README.rst
@@ -0,0 +1,187 @@
+.. image:: https://odoo-community.org/readme-banner-image
+ :target: https://odoo-community.org/get-involved?utm_source=readme
+ :alt: Odoo Community Association
+
+=============================
+Employee Advance and Clearing
+=============================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:3399d5032631e32709c5c3793d190f99f145eee5605de1181d2698efad056214
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--expense-lightgray.png?logo=github
+ :target: https://github.com/OCA/hr-expense/tree/19.0/hr_expense_advance_clearing
+ :alt: OCA/hr-expense
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/hr-expense-19-0/hr-expense-19-0-hr_expense_advance_clearing
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/hr-expense&target_branch=19.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+Lets the company pay an "advance" to an employee, then later reconcile
+that advance against the actual expenses incurred.
+
+In Odoo 19.0 the workflow is per-expense (the 18.0 advance-sheet vs
+clearing-sheet pair is gone with ``hr.expense.sheet``):
+
+- An **advance expense** has ``expense_type='advance'``, uses the
+ ``Employee Advance`` product, no taxes. Paid to the employee via the
+ standard ``action_pay``.
+- A **clearing expense** is a regular ``hr.expense`` with
+ ``clearing_advance_id`` set to the advance. On posting, its receivable
+ line auto-reconciles against the advance's receivable line.
+- The advance's ``clearing_residual`` tracks the unreconciled balance.
+ When it hits zero, the advance is fully cleared.
+
+Three scenarios remain:
+
+- ``clearing_total = advance`` — fully reconciled, advance is closed.
+- ``clearing_total > advance`` — Register Payment on the clearing
+ expense's difference (the wizard understands the ``expense_clearing``
+ context).
+- ``clearing_total < advance`` — use **Return Advance** on the advance
+ expense to refund the residual back to the company.
+
+19.0 note: ``hr_expense_trip`` (when available) is the natural clearing
+scope — a "Clear Trip" button on the trip would call
+``action_clear_advance`` on the trip's ``expense_ids``. Trip integration
+lives in the trip module, not here.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Configuration
+=============
+
+This module will create a new product "Employee Advance" automatically.
+You will need to setup the Expense Account of this product to your
+Employee Advance account manually.
+
+- Open Product window and search for "Employee Advance"
+- On Accounting tab, select appropriate employee advance account from
+ your chart of account
+
+Note:
+
+- You will need the "Show Full Accounting Features" to see accounting
+ data
+- Employee Advance account code, if not already exists, you can create
+ one. Use type = Current Asset and check Allow Reconciliation.
+
+Usage
+=====
+
+First-time setup: open product **Employee Advance** (auto-created on
+install) and set its expense account to your "Employee Advance"
+current-asset account with ``Allow Reconciliation``.
+
+**Create an Employee Advance**
+
+1. Expenses → My Expenses, click New.
+2. Set ``Expense Type`` to **Advance** (Employee Advance product +
+ own_account auto-fill).
+3. Optionally set ``Clearing Product`` — it's the default product for
+ clearing expense lines that reference this advance.
+4. Set the amount, Save.
+5. Submit → Approve → Post → Register Payment. The advance is paid to
+ the employee.
+
+**Clear the Advance with a regular expense**
+
+1. Expenses → My Expenses, New (or pick an existing draft).
+2. Leave ``Expense Type`` as **Expense** and set ``Clearing Advance`` to
+ the advance you want to clear against. The ``Advance Remaining``
+ smart field shows what's left of the advance.
+3. Submit → Approve → Post. On post, the clearing expense's receivable
+ line auto-reconciles against the advance's receivable line.
+
+What happens at post time:
+
+- ``clearing_total ≤ advance_residual`` → fully covered. No further
+ payment needed; the advance's residual updates.
+- ``clearing_total > advance_residual`` → the excess remains on the
+ clearing expense's move. Register Payment for the difference.
+
+**Return unused advance**
+
+1. Open the paid advance with ``clearing_residual > 0``.
+2. Click **Return Advance** (header button). The Register Payment wizard
+ opens pre-filled with the residual.
+3. Confirm. The advance is now fully closed; ``clearing_residual = 0``.
+
+**Trip integration** (when ``hr_expense_trip`` is installed)
+
+A trip's ``expense_ids`` can include both an advance and the regular
+expenses paid out of it. The trip module wires a "Clear Trip" button
+that calls ``action_clear_advance`` on the trip's regular expenses with
+the trip's advance as the target. This module does not depend on
+``hr_expense_trip``; the integration is one-way from the trip side.
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+-------
+
+* Ecosoft
+
+Contributors
+------------
+
+- Kitti Upariphutthiphong
+- Tharathip Chaweewongphan
+- Saran Lim.
+
+Maintainers
+-----------
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-kittiu| image:: https://github.com/kittiu.png?size=40px
+ :target: https://github.com/kittiu
+ :alt: kittiu
+.. |maintainer-dnplkndll| image:: https://github.com/dnplkndll.png?size=40px
+ :target: https://github.com/dnplkndll
+ :alt: dnplkndll
+
+Current `maintainers `__:
+
+|maintainer-kittiu| |maintainer-dnplkndll|
+
+This module is part of the `OCA/hr-expense `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/hr_expense_advance_clearing/__manifest__.py b/hr_expense_advance_clearing/__manifest__.py
index 99bfc5909..8ca7df595 100644
--- a/hr_expense_advance_clearing/__manifest__.py
+++ b/hr_expense_advance_clearing/__manifest__.py
@@ -1,20 +1,22 @@
# Copyright 2019 Kitti Upariphutthiphong
+# Copyright 2026 Ledo
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Employee Advance and Clearing",
- "version": "13.0.1.0.0",
+ "version": "19.0.1.0.0",
"category": "Human Resources",
- "author": "Ecosoft,Odoo Community Association (OCA)",
+ "author": "Ecosoft, Odoo Community Association (OCA)",
"license": "AGPL-3",
"website": "https://github.com/OCA/hr-expense",
"depends": ["hr_expense"],
"data": [
"data/advance_product.xml",
+ "views/account_payment_view.xml",
"views/hr_expense_views.xml",
"views/hr_employee_views.xml",
"views/hr_employee_public_views.xml",
],
"installable": True,
- "maintainers": ["kittiu"],
+ "maintainers": ["kittiu", "dnplkndll"],
}
diff --git a/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot b/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot
index 0c4a1a606..4ad58b224 100644
--- a/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot
+++ b/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot
@@ -1,12 +1,12 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
-# * hr_expense_advance_clearing
+# * hr_expense_advance_clearing
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 12.0\n"
+"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: <>\n"
+"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -14,18 +14,31 @@ msgstr ""
"Plural-Forms: \n"
#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py:46
-#, python-format
-msgid "A remaining advance return of %s %s with the reference %s related to your expense %s has been made."
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+msgid ""
+"\n"
+"Note: pending amount clearing is %(symbol)s%(amount)s"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_count
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_count
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_count
+msgid "# of Advance"
msgstr ""
#. module: hr_expense_advance_clearing
-#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_filter
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_account_payment__advance_id
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_employee_public_view_form
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_employee_form
msgid "Advance"
msgstr ""
#. module: hr_expense_advance_clearing
-#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_filter
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
msgid "Advance (not cleared)"
msgstr ""
@@ -40,33 +53,53 @@ msgid "Advance Remaining"
msgstr ""
#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:60
-#, python-format
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_employee_base.py:0
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_sheet_ids
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_sheet_ids
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_sheet_ids
+msgid "Advance Sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
msgid "Advance clearing must not contain any advance expense line"
msgstr ""
#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:63
-#, python-format
-msgid "Advance must contain only 1 advance expense line"
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Advance must contain only advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Advance: %(name)s has no amount to clear"
msgstr ""
#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:55
-#, python-format
-msgid "Advance: %s has no amount to clear"
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet
+#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_advance_sheet
+msgid "Advances"
msgstr ""
#. module: hr_expense_advance_clearing
-#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__residual
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual
msgid "Amount to clear"
msgstr ""
#. module: hr_expense_advance_clearing
-#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__residual
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual
msgid "Amount to clear of this expense sheet in company currency"
msgstr ""
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_employee_base
+msgid "Basic Employee"
+msgstr ""
+
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
@@ -74,10 +107,43 @@ msgid "Clear Advance"
msgstr ""
#. module: hr_expense_advance_clearing
-#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_filter
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search
msgid "Clearing"
msgstr ""
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_count
+msgid "Clearing Count"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__clearing_product_id
+msgid "Clearing Product"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids
+msgid "Clearing Sheet"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Clearing Sheets"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Clearings"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee__advance_count
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_base__advance_count
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_public__advance_count
+msgid "Count advance sheet in expense report"
+msgstr ""
+
#. module: hr_expense_advance_clearing
#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
msgid "Create a new expense report"
@@ -86,48 +152,60 @@ msgstr ""
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance
-#: model:product.product,name:hr_expense_advance_clearing.product_emp_advance
#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template
msgid "Employee Advance"
msgstr ""
#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:24
-#, python-format
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
msgid "Employee advance product has no payable account"
msgstr ""
#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:30
-#, python-format
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
+msgid "Employee advance, account must be the same payable account"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
msgid "Employee advance, all taxes must be removed"
msgstr ""
#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:33
-#, python-format
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
msgid "Employee advance, paid by must be employee"
msgstr ""
#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:27
-#, python-format
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
msgid "Employee advance, selected product is not valid"
msgstr ""
#. module: hr_expense_advance_clearing
#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search
msgid "Expense"
msgstr ""
#. module: hr_expense_advance_clearing
-#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet_register_payment_wizard
-msgid "Expense Register Payment Wizard"
+#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet
+msgid "Expense Report"
msgstr ""
#. module: hr_expense_advance_clearing
-#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet
-msgid "Expense Report"
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__av_line_id
+msgid "Expense created from this advance expense line"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_expense_sheet
+#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_expense_sheet
+msgid "Expenses"
msgstr ""
#. module: hr_expense_advance_clearing
@@ -135,9 +213,57 @@ msgstr ""
msgid "Final regiter payment amount even after advance clearing"
msgstr ""
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_move
+msgid "Journal Entry"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet
+msgid "No advance report found. Let's create one!"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet
+msgid "No expense report found. Let's create one!"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet
+msgid ""
+"Once you have created your advance, submit it to your manager who will "
+"validate it."
+msgstr ""
+
#. module: hr_expense_advance_clearing
#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing
-msgid "Once you have created your expense, submit it to your manager who will validate it."
+msgid ""
+"Once you have created your expense, submit it to your manager who will\n"
+" validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet
+msgid ""
+"Once you have created your expense, submit it to your manager who will "
+"validate it."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_form
+msgid "Optional clearing product is used during clear advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__clearing_product_id
+msgid ""
+"Optional: On the clear advance, the clearing product will create default "
+"product line."
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment_register
+msgid "Pay"
msgstr ""
#. module: hr_expense_advance_clearing
@@ -145,11 +271,23 @@ msgstr ""
msgid "Payable Amount"
msgstr ""
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids
+msgid "Payment Return"
+msgstr ""
+
#. module: hr_expense_advance_clearing
#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment
msgid "Payments"
msgstr ""
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__av_line_id
+msgid "Ref: Advance"
+msgstr ""
+
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
msgid "Remaining amount to clear the selected advance sheet"
@@ -157,23 +295,47 @@ msgstr ""
#. module: hr_expense_advance_clearing
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
+msgid "Return"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_account_payment_search
+#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
msgid "Return Advance"
msgstr ""
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__return_count
+msgid "Return Count"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids
+msgid "Show reference clearing on advance"
+msgstr ""
+
+#. module: hr_expense_advance_clearing
+#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids
+msgid "Show reference return advance on advance"
+msgstr ""
+
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id
msgid "Show remaining advance of this employee"
msgstr ""
#. module: hr_expense_advance_clearing
-#: model:product.product,uom_name:hr_expense_advance_clearing.product_emp_advance
-#: model:product.template,uom_name:hr_expense_advance_clearing.product_emp_advance_product_template
-msgid "Unit(s)"
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/account_move.py:0
+msgid ""
+"This operation is not allowed as some advance amount was already cleared/returned.\n"
+"Please cancel those documents first."
msgstr ""
#. module: hr_expense_advance_clearing
-#: model:product.product,weight_uom_name:hr_expense_advance_clearing.product_emp_advance
-#: model:product.template,weight_uom_name:hr_expense_advance_clearing.product_emp_advance_product_template
-msgid "kg"
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
+msgid ""
+"You cannot return advance more than actual remaining "
+"(%(symbol)s%(amount)s)%(more_info)s"
msgstr ""
-
diff --git a/hr_expense_advance_clearing/i18n/it.po b/hr_expense_advance_clearing/i18n/it.po
index 22b0ec1c0..4bd9466b8 100644
--- a/hr_expense_advance_clearing/i18n/it.po
+++ b/hr_expense_advance_clearing/i18n/it.po
@@ -17,11 +17,11 @@ msgstr ""
"X-Generator: Weblate 5.10.4\n"
#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
-#, python-format
msgid ""
"\n"
-"Note: pending amount clearing is {}{}"
+"Note: pending amount clearing is %(symbol)s%(amount)s"
msgstr ""
"\n"
"Nota: valore liquidazione in attesa è %(symbol)s%(amount)s"
@@ -33,16 +33,6 @@ msgstr ""
msgid "# of Advance"
msgstr "N° di anticipo"
-#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
-#, python-format
-msgid ""
-"A remaining advance return of {} {} with the reference {} related to your "
-"expense {} has been made."
-msgstr ""
-"È stato fatto un residuo reso anticipo di %(amount)s %(symbol)s con "
-"riferimento %(ref)s relativo alla sua spesa %(name)s."
-
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_account_payment__advance_id
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_employee_public_view_form
@@ -68,23 +58,24 @@ msgid "Advance Remaining"
msgstr "Residuo anticipo"
#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/hr_employee_base.py:0
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_sheet_ids
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_sheet_ids
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_sheet_ids
-#, python-format
msgid "Advance Sheet"
msgstr "Foglio anticipo"
#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
-#, python-format
msgid "Advance clearing must not contain any advance expense line"
-msgstr "La liquidazione anticipo non deve contenere nessuna riga anticipo spesa"
+msgstr ""
+"La liquidazione anticipo non deve contenere nessuna riga anticipo spesa"
#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
-#, python-format
msgid "Advance must contain only advance expense line"
msgstr "L'anticipo deve contenere solo una riga anticipo spesa"
@@ -142,8 +133,8 @@ msgid "Clearing Sheet"
msgstr "Foglio liquidazione"
#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
-#, python-format
msgid "Clearing Sheets"
msgstr "Fogli liquidazioni"
@@ -167,38 +158,37 @@ msgstr "Crea una nuova nota spese"
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance
-#: model:product.product,name:hr_expense_advance_clearing.product_emp_advance
#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template
msgid "Employee Advance"
msgstr "Anticipo dipendente"
#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
-#, python-format
msgid "Employee advance product has no payable account"
msgstr "Il prodotto anticipo dipendente non ha un conto pagabile"
#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
-#, python-format
msgid "Employee advance, account must be the same payable account"
msgstr "Anticipo dipendente, il conto deve essere lo stesso conto pagabile"
#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
-#, python-format
msgid "Employee advance, all taxes must be removed"
msgstr "Anticipo dipendente, devono essere rimosse tutte le tasse"
#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
-#, python-format
msgid "Employee advance, paid by must be employee"
msgstr "Anticipo dipendente, \"pagato da\" deve essere un dipendente"
#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0
-#, python-format
msgid "Employee advance, selected product is not valid"
msgstr "Anticipo dipendente, il prodotto selezionato non è valido"
@@ -315,11 +305,6 @@ msgstr "Pagamenti"
msgid "Ref: Advance"
msgstr "Rif: anticipo"
-#. module: hr_expense_advance_clearing
-#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment_register
-msgid "Register Payment"
-msgstr "Registra pagamento"
-
#. module: hr_expense_advance_clearing
#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual
msgid "Remaining amount to clear the selected advance sheet"
@@ -357,10 +342,11 @@ msgid "Show remaining advance of this employee"
msgstr "Visualizza residuo anticipo di questo dipendente"
#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/account_move.py:0
-#, python-format
msgid ""
-"This operation is not allowed as some advance amount was already cleared/returned.\n"
+"This operation is not allowed as some advance amount was already cleared/"
+"returned.\n"
"Please cancel those documents first."
msgstr ""
"Questa operazione non è consentita perché una parte del valore anticipato è "
@@ -368,45 +354,50 @@ msgstr ""
"Cancellare quella lista documenti."
#. module: hr_expense_advance_clearing
-#: model:product.product,uom_name:hr_expense_advance_clearing.product_emp_advance
-#: model:product.template,uom_name:hr_expense_advance_clearing.product_emp_advance_product_template
-msgid "Units"
-msgstr "Unità"
-
-#. module: hr_expense_advance_clearing
+#. odoo-python
#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
-#, python-format
-msgid "You can only register payment for posted journal entries."
+msgid ""
+"You cannot return advance more than actual remaining "
+"(%(symbol)s%(amount)s)%(more_info)s"
msgstr ""
-"Si possono registrare pagamenti solo per registrazioni contabili assegnate."
+"Non è possibile restituire un anticipo superiore al rimanente effettivo "
+"(%(symbol)s%(amount)s)%(more_info)s"
-#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
#, python-format
-msgid ""
-"You can't create payments for entries belonging to different companies."
-msgstr ""
-"Non si possono creare pagamenti per entrate appartenenti a diverse aziende."
+#~ msgid ""
+#~ "A remaining advance return of %(amount)s %(symbol)s with the reference "
+#~ "%(ref)s related to your expense %(name)s has been made."
+#~ msgstr ""
+#~ "È stato fatto un residuo reso anticipo di %(amount)s %(symbol)s con "
+#~ "riferimento %(ref)s relativo alla sua spesa %(name)s."
-#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
#, python-format
-msgid ""
-"You can't register a payment because there is nothing left to pay on the "
-"selected journal items."
-msgstr ""
-"Non si può registrare il pagamento perché non c'è niente da pagare nei "
-"movimenti contabili selezionati."
+#~ msgid "Advance: %s has no amount to clear"
+#~ msgstr "Anticipo: %s non ha un valore da compensare"
+
+#~ msgid "Register Payment"
+#~ msgstr "Registra pagamento"
-#. module: hr_expense_advance_clearing
-#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0
#, python-format
-msgid ""
-"You cannot return advance more than actual remaining (%(symbol)s"
-"%(amount)s)%(more_info)s"
-msgstr ""
-"Non è possibile restituire un anticipo superiore al rimanente effettivo "
-"(%(symbol)s%(amount)s)%(more_info)s"
+#~ msgid "You can only register payment for posted journal entries."
+#~ msgstr ""
+#~ "Si possono registrare pagamenti solo per registrazioni contabili "
+#~ "assegnate."
+
+#, python-format
+#~ msgid ""
+#~ "You can't create payments for entries belonging to different companies."
+#~ msgstr ""
+#~ "Non si possono creare pagamenti per entrate appartenenti a diverse "
+#~ "aziende."
+
+#, python-format
+#~ msgid ""
+#~ "You can't register a payment because there is nothing left to pay on the "
+#~ "selected journal items."
+#~ msgstr ""
+#~ "Non si può registrare il pagamento perché non c'è niente da pagare nei "
+#~ "movimenti contabili selezionati."
#, python-format
#~ msgid ""
diff --git a/hr_expense_advance_clearing/i18n/th.po b/hr_expense_advance_clearing/i18n/th.po
index 33d3c4d6f..aaba8b298 100644
--- a/hr_expense_advance_clearing/i18n/th.po
+++ b/hr_expense_advance_clearing/i18n/th.po
@@ -74,6 +74,12 @@ msgstr ""
msgid "Advance must contain only advance expense line"
msgstr ""
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Advance: %(name)s has no amount to clear"
+msgstr ""
+
#. module: hr_expense_advance_clearing
#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet
#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_advance_sheet
@@ -121,6 +127,12 @@ msgstr ""
msgid "Clearing Sheet"
msgstr ""
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Clearing Sheets"
+msgstr ""
+
#. module: hr_expense_advance_clearing
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
msgid "Clearings"
@@ -261,6 +273,8 @@ msgid "Payable Amount"
msgstr ""
#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids
msgid "Payment Return"
msgstr ""
@@ -315,7 +329,8 @@ msgstr ""
#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/account_move.py:0
msgid ""
-"This operation is not allowed as some advance amount was already cleared/returned.\n"
+"This operation is not allowed as some advance amount was already cleared/"
+"returned.\n"
"Please cancel those documents first."
msgstr ""
diff --git a/hr_expense_advance_clearing/i18n/th_TH.po b/hr_expense_advance_clearing/i18n/th_TH.po
index 6904c01c9..aae7b4f28 100644
--- a/hr_expense_advance_clearing/i18n/th_TH.po
+++ b/hr_expense_advance_clearing/i18n/th_TH.po
@@ -74,6 +74,12 @@ msgstr ""
msgid "Advance must contain only advance expense line"
msgstr ""
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Advance: %(name)s has no amount to clear"
+msgstr ""
+
#. module: hr_expense_advance_clearing
#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet
#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_advance_sheet
@@ -121,6 +127,12 @@ msgstr ""
msgid "Clearing Sheet"
msgstr ""
+#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
+msgid "Clearing Sheets"
+msgstr ""
+
#. module: hr_expense_advance_clearing
#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form
msgid "Clearings"
@@ -261,6 +273,8 @@ msgid "Payable Amount"
msgstr ""
#. module: hr_expense_advance_clearing
+#. odoo-python
+#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0
#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids
msgid "Payment Return"
msgstr ""
@@ -315,7 +329,8 @@ msgstr ""
#. odoo-python
#: code:addons/hr_expense_advance_clearing/models/account_move.py:0
msgid ""
-"This operation is not allowed as some advance amount was already cleared/returned.\n"
+"This operation is not allowed as some advance amount was already cleared/"
+"returned.\n"
"Please cancel those documents first."
msgstr ""
diff --git a/hr_expense_advance_clearing/readme/CONFIGURE.md b/hr_expense_advance_clearing/readme/CONFIGURE.md
new file mode 100644
index 000000000..b24c274eb
--- /dev/null
+++ b/hr_expense_advance_clearing/readme/CONFIGURE.md
@@ -0,0 +1,14 @@
+This module will create a new product "Employee Advance" automatically.
+You will need to setup the Expense Account of this product to your
+Employee Advance account manually.
+
+- Open Product window and search for "Employee Advance"
+- On Accounting tab, select appropriate employee advance account from
+ your chart of account
+
+Note:
+
+- You will need the "Show Full Accounting Features" to see accounting
+ data
+- Employee Advance account code, if not already exists, you can create
+ one. Use type = Current Asset and check Allow Reconciliation.
diff --git a/hr_expense_advance_clearing/readme/CONFIGURE.rst b/hr_expense_advance_clearing/readme/CONFIGURE.rst
deleted file mode 100644
index 4af425617..000000000
--- a/hr_expense_advance_clearing/readme/CONFIGURE.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-This module will create a new product "Employee Advance" automatically.
-You will need to setup the Expense Account of this product to your Employee Advance account manually.
-
-* Open Product window and search for "Employee Advance"
-* On Accounting tab, select appropriate employee advance account from your chart of account
-
-Note:
-
-* You will need the "Show Full Accounting Features" to see accounting data
-* Employee Advance account code, if not already exists, you can create one. Use type = Current Asset and check Allow Reconciliation.
diff --git a/hr_expense_advance_clearing/readme/CONTRIBUTORS.md b/hr_expense_advance_clearing/readme/CONTRIBUTORS.md
new file mode 100644
index 000000000..66d91554e
--- /dev/null
+++ b/hr_expense_advance_clearing/readme/CONTRIBUTORS.md
@@ -0,0 +1,3 @@
+- Kitti Upariphutthiphong \<\>
+- Tharathip Chaweewongphan \<\>
+- Saran Lim. \<\>
diff --git a/hr_expense_advance_clearing/readme/CONTRIBUTORS.rst b/hr_expense_advance_clearing/readme/CONTRIBUTORS.rst
deleted file mode 100644
index 033e67f43..000000000
--- a/hr_expense_advance_clearing/readme/CONTRIBUTORS.rst
+++ /dev/null
@@ -1 +0,0 @@
-* Kitti Upariphutthiphong
diff --git a/hr_expense_advance_clearing/readme/DESCRIPTION.md b/hr_expense_advance_clearing/readme/DESCRIPTION.md
new file mode 100644
index 000000000..704a762ce
--- /dev/null
+++ b/hr_expense_advance_clearing/readme/DESCRIPTION.md
@@ -0,0 +1,27 @@
+Lets the company pay an "advance" to an employee, then later reconcile
+that advance against the actual expenses incurred.
+
+In Odoo 19.0 the workflow is per-expense (the 18.0 advance-sheet vs
+clearing-sheet pair is gone with `hr.expense.sheet`):
+
+- An **advance expense** has `expense_type='advance'`, uses the
+ `Employee Advance` product, no taxes. Paid to the employee via the
+ standard `action_pay`.
+- A **clearing expense** is a regular `hr.expense` with
+ `clearing_advance_id` set to the advance. On posting, its receivable
+ line auto-reconciles against the advance's receivable line.
+- The advance's `clearing_residual` tracks the unreconciled balance.
+ When it hits zero, the advance is fully cleared.
+
+Three scenarios remain:
+
+- `clearing_total = advance` — fully reconciled, advance is closed.
+- `clearing_total > advance` — Register Payment on the clearing expense's
+ difference (the wizard understands the `expense_clearing` context).
+- `clearing_total < advance` — use **Return Advance** on the advance
+ expense to refund the residual back to the company.
+
+19.0 note: `hr_expense_trip` (when available) is the natural clearing
+scope — a "Clear Trip" button on the trip would call
+`action_clear_advance` on the trip's `expense_ids`. Trip integration
+lives in the trip module, not here.
diff --git a/hr_expense_advance_clearing/readme/DESCRIPTION.rst b/hr_expense_advance_clearing/readme/DESCRIPTION.rst
deleted file mode 100644
index 8dd91962f..000000000
--- a/hr_expense_advance_clearing/readme/DESCRIPTION.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-Standard Expenses module allow employee to do the expense reimbursement only after the expense has been made.
-In other world, employee will need to pay first and reimburse later.
-
-This module, allow company to advance an amount to the employee.
-Employee can then use that advance amount to purchase product/service first, then back to company and do the clearing.
-
-There can be 3 scenarios for advance and clearing
-
-* When clearing amount = advance amount, no other operation is required.
-* When clearing amount > advance amount, company will pay the extra to employee.
-* When clearing amount < advance amount, employee will return the remain to company.
diff --git a/hr_expense_advance_clearing/readme/INSTALL.rst b/hr_expense_advance_clearing/readme/INSTALL.rst
deleted file mode 100644
index f381baecb..000000000
--- a/hr_expense_advance_clearing/readme/INSTALL.rst
+++ /dev/null
@@ -1 +0,0 @@
-* No specific installation required
diff --git a/hr_expense_advance_clearing/readme/USAGE.md b/hr_expense_advance_clearing/readme/USAGE.md
new file mode 100644
index 000000000..eb09fc29c
--- /dev/null
+++ b/hr_expense_advance_clearing/readme/USAGE.md
@@ -0,0 +1,44 @@
+First-time setup: open product **Employee Advance** (auto-created on
+install) and set its expense account to your "Employee Advance"
+current-asset account with `Allow Reconciliation`.
+
+**Create an Employee Advance**
+
+1. Expenses → My Expenses, click New.
+2. Set `Expense Type` to **Advance** (Employee Advance product + own_account auto-fill).
+3. Optionally set `Clearing Product` — it's the default product for
+ clearing expense lines that reference this advance.
+4. Set the amount, Save.
+5. Submit → Approve → Post → Register Payment. The advance is paid to
+ the employee.
+
+**Clear the Advance with a regular expense**
+
+1. Expenses → My Expenses, New (or pick an existing draft).
+2. Leave `Expense Type` as **Expense** and set `Clearing Advance` to the
+ advance you want to clear against. The `Advance Remaining` smart
+ field shows what's left of the advance.
+3. Submit → Approve → Post. On post, the clearing expense's receivable
+ line auto-reconciles against the advance's receivable line.
+
+What happens at post time:
+
+- `clearing_total ≤ advance_residual` → fully covered. No further
+ payment needed; the advance's residual updates.
+- `clearing_total > advance_residual` → the excess remains on the
+ clearing expense's move. Register Payment for the difference.
+
+**Return unused advance**
+
+1. Open the paid advance with `clearing_residual > 0`.
+2. Click **Return Advance** (header button). The Register Payment wizard
+ opens pre-filled with the residual.
+3. Confirm. The advance is now fully closed; `clearing_residual = 0`.
+
+**Trip integration** (when `hr_expense_trip` is installed)
+
+A trip's `expense_ids` can include both an advance and the regular
+expenses paid out of it. The trip module wires a "Clear Trip" button
+that calls `action_clear_advance` on the trip's regular expenses with
+the trip's advance as the target. This module does not depend on
+`hr_expense_trip`; the integration is one-way from the trip side.
diff --git a/hr_expense_advance_clearing/readme/USAGE.rst b/hr_expense_advance_clearing/readme/USAGE.rst
deleted file mode 100644
index 1eae4b858..000000000
--- a/hr_expense_advance_clearing/readme/USAGE.rst
+++ /dev/null
@@ -1,33 +0,0 @@
-**Create an Employee Advance**
-
-#. Go to Expenses > My Expenses
-#. Create a new Expense as normal, but also check "Employee Advance" checkbox
-#. Product = Employee Advance will be set automatically, do not change.
-#. As an option, user can also set the "Clearing Product". If this is set, on the clear advance step, the clearing product will create a default product line.
-#. Set the unit price to advance amount
-#. Click Create Report as normal. Please note that, this expense report will also has flag "Employee Advance" checked.
-#. As normal, do Submit to Manager > Approve > Post Journal Entries > Register Payment.
-#. As this is Advance, you will see new field "Amount to clear".
-
-**Clear Advance**
-
-#. Go to Expenses > My Expense Report.
-#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance.
-#. Open an Advance which is now in paid status with some Amount to be cleared.
-#. Click button "Clear Advance", system will create new Expense Report with reference to the previous step Advance.
-#. Add or create Expense line(s) as normal.
-#. As normal, do Submit to Manager > Approve > Post Journal Entries
-
-Note:
-
-* If the total expense amount less than or equal to the advance amount, the status will be set to Paid right after post journal entries.
-* If the total expense amount more than the advance amount, Register Payment will pay the extra amount then set state to Paid.
-
-**Return Advance**
-
-#. Go to Expenses > My Expense Report.
-#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance.
-#. Open an Advance which is now in paid status with some Amount to be cleared.
-#. Click button "Return Advance" will open Register Payment wizard with Amount to clear.
-#. Click button "Validate" to return that amount back
-#. All returned, Amount to clear is now equal to 0.0
diff --git a/hr_expense_advance_clearing/static/description/index.html b/hr_expense_advance_clearing/static/description/index.html
index 1f3c52b12..c7efb4145 100644
--- a/hr_expense_advance_clearing/static/description/index.html
+++ b/hr_expense_advance_clearing/static/description/index.html
@@ -1,20 +1,20 @@
-
-
-Employee Advance and Clearing
+
+README.rst
-
-
Employee Advance and Clearing
+
+
+
+
+
+
+
Employee Advance and Clearing
-
-
Standard Expenses module allow employee to do the expense reimbursement only after the expense has been made.
-In other world, employee will need to pay first and reimburse later.
-
This module, allow company to advance an amount to the employee.
-Employee can then use that advance amount to purchase product/service first, then back to company and do the clearing.
-
There can be 3 scenarios for advance and clearing
+
+
Lets the company pay an “advance” to an employee, then later reconcile
+that advance against the actual expenses incurred.
+
In Odoo 19.0 the workflow is per-expense (the 18.0 advance-sheet vs
+clearing-sheet pair is gone with hr.expense.sheet ):
+
+An advance expense has expense_type='advance' , uses the
+Employee Advance product, no taxes. Paid to the employee via the
+standard action_pay .
+A clearing expense is a regular hr.expense with
+clearing_advance_id set to the advance. On posting, its receivable
+line auto-reconciles against the advance’s receivable line.
+The advance’s clearing_residual tracks the unreconciled balance.
+When it hits zero, the advance is fully cleared.
+
+
Three scenarios remain:
-When clearing amount = advance amount, no other operation is required.
-When clearing amount > advance amount, company will pay the extra to employee.
-When clearing amount < advance amount, employee will return the remain to company.
+clearing_total = advance — fully reconciled, advance is closed.
+clearing_total > advance — Register Payment on the clearing
+expense’s difference (the wizard understands the expense_clearing
+context).
+clearing_total < advance — use Return Advance on the advance
+expense to refund the residual back to the company.
+
19.0 note: hr_expense_trip (when available) is the natural clearing
+scope — a “Clear Trip” button on the trip would call
+action_clear_advance on the trip’s expense_ids . Trip integration
+lives in the trip module, not here.
Table of contents
-
-
-
-No specific installation required
-
-
-
+
This module will create a new product “Employee Advance” automatically.
-You will need to setup the Expense Account of this product to your Employee Advance account manually.
+You will need to setup the Expense Account of this product to your
+Employee Advance account manually.
Open Product window and search for “Employee Advance”
-On Accounting tab, select appropriate employee advance account from your chart of account
+On Accounting tab, select appropriate employee advance account from
+your chart of account
Note:
-You will need the “Show Full Accounting Features” to see accounting data
-Employee Advance account code, if not already exists, you can create one. Use type = Current Asset and check Allow Reconciliation.
+You will need the “Show Full Accounting Features” to see accounting
+data
+Employee Advance account code, if not already exists, you can create
+one. Use type = Current Asset and check Allow Reconciliation.
-
+
+
First-time setup: open product Employee Advance (auto-created on
+install) and set its expense account to your “Employee Advance”
+current-asset account with Allow Reconciliation .
Create an Employee Advance
-Go to Expenses > My Expenses
-Create a new Expense as normal, but also check “Employee Advance” checkbox
-Product = Employee Advance will be set automatically, do not change.
-Set the unit price to advance amount
-Click Create Report as normal. Please note that, this expense report will also has flag “Employee Advance” checked.
-As normal, do Submit to Manager > Approve > Post Journal Entries > Register Payment.
-As this is Advance, you will see new field “Amount to clear”.
+Expenses → My Expenses, click New.
+Set Expense Type to Advance (Employee Advance product +
+own_account auto-fill).
+Optionally set Clearing Product — it’s the default product for
+clearing expense lines that reference this advance.
+Set the amount, Save.
+Submit → Approve → Post → Register Payment. The advance is paid to
+the employee.
-
Clear Advance
+
Clear the Advance with a regular expense
-Go to Expenses > My Expense Report.
-Search for the Advance you want to clear, or use filter “Advance (not cleared)” to see all uncleared advance.
-Open an Advance which is now in paid status with some Amount to be cleared.
-Click button “Clear Advance”, system will create new Expense Report with reference to the previous step Advance.
-Add or create Expense line(s) as normal.
-As normal, do Submit to Manager > Approve > Post Journal Entries
+Expenses → My Expenses, New (or pick an existing draft).
+Leave Expense Type as Expense and set Clearing Advance to
+the advance you want to clear against. The Advance Remaining
+smart field shows what’s left of the advance.
+Submit → Approve → Post. On post, the clearing expense’s receivable
+line auto-reconciles against the advance’s receivable line.
-
Note:
+
What happens at post time:
-If the total expense amount less than or equal to the advance amount, the status will be set to Paid right after post journal entries.
-If the total expense amount more than the advance amount, Register Payment will pay the extra amount then set state to Paid.
+clearing_total ≤ advance_residual → fully covered. No further
+payment needed; the advance’s residual updates.
+clearing_total > advance_residual → the excess remains on the
+clearing expense’s move. Register Payment for the difference.
-
Return Advance
+
Return unused advance
-Go to Expenses > My Expense Report.
-Search for the Advance you want to clear, or use filter “Advance (not cleared)” to see all uncleared advance.
-Open an Advance which is now in paid status with some Amount to be cleared.
-Click button “Return Advance” will open Register Payment wizard with Amount to clear.
-Click button “Validate” to return that amount back
-All returned, Amount to clear is now equal to 0.0
+Open the paid advance with clearing_residual > 0 .
+Click Return Advance (header button). The Register Payment wizard
+opens pre-filled with the residual.
+Confirm. The advance is now fully closed; clearing_residual = 0 .
+
Trip integration (when hr_expense_trip is installed)
+
A trip’s expense_ids can include both an advance and the regular
+expenses paid out of it. The trip module wires a “Clear Trip” button
+that calls action_clear_advance on the trip’s regular expenses with
+the trip’s advance as the target. This module does not depend on
+hr_expense_trip ; the integration is one-way from the trip side.
-
-
Bugs are tracked on GitHub Issues .
+
+
Bugs are tracked on GitHub Issues .
In case of trouble, please check there if your issue has already been reported.
-If you spotted it first, help us smashing it by providing a detailed and welcomed
-feedback .
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+
feedback .
Do not contact contributors directly about support or help with technical issues.
-
+
-
+
This module is maintained by the OCA.
-
+
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-
This module is part of the OCA/hr project on GitHub.
+
Current maintainers :
+
+
This module is part of the OCA/hr-expense project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute .
+
From 8d34ecc53e37553080963d1fa876a8628852b8eb Mon Sep 17 00:00:00 2001
From: Don Kendall
Date: Sat, 30 May 2026 10:57:09 -0400
Subject: [PATCH 72/75] [MIG] hr_expense_advance_clearing: Migration to 19.0
hr.expense.sheet removed in 19.0; retarget to hr.expense (+ hr.employee.base->hr.employee).
---
hr_expense_advance_clearing/__init__.py | 2 +
.../data/advance_product.xml | 6 +-
.../migrations/19.0.1.0.0/post-migration.py | 89 ++++
.../migrations/19.0.1.0.0/pre-migration.py | 46 +++
.../models/__init__.py | 6 +-
.../models/account_move.py | 64 +--
.../models/account_payment.py | 36 ++
.../models/hr_employee.py | 50 +++
.../models/hr_employee_base.py | 33 --
.../models/hr_expense.py | 317 +++++++++++---
.../models/hr_expense_sheet.py | 349 ----------------
hr_expense_advance_clearing/pyproject.toml | 3 +
hr_expense_advance_clearing/tests/__init__.py | 2 +
.../tests/test_hr_expense_advance_clearing.py | 386 ++++++------------
.../views/account_payment_view.xml | 29 ++
.../views/hr_employee_views.xml | 2 +-
.../views/hr_expense_views.xml | 266 +++---------
.../wizard/__init__.py | 4 +-
.../wizard/account_payment_register.py | 64 +++
.../hr_expense_sheet_register_payment.py | 69 ----
20 files changed, 789 insertions(+), 1034 deletions(-)
create mode 100644 hr_expense_advance_clearing/migrations/19.0.1.0.0/post-migration.py
create mode 100644 hr_expense_advance_clearing/migrations/19.0.1.0.0/pre-migration.py
create mode 100644 hr_expense_advance_clearing/models/account_payment.py
create mode 100644 hr_expense_advance_clearing/models/hr_employee.py
delete mode 100644 hr_expense_advance_clearing/models/hr_employee_base.py
delete mode 100644 hr_expense_advance_clearing/models/hr_expense_sheet.py
create mode 100644 hr_expense_advance_clearing/pyproject.toml
create mode 100644 hr_expense_advance_clearing/views/account_payment_view.xml
create mode 100644 hr_expense_advance_clearing/wizard/account_payment_register.py
delete mode 100644 hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
diff --git a/hr_expense_advance_clearing/__init__.py b/hr_expense_advance_clearing/__init__.py
index 9b4296142..4d7a49b5e 100644
--- a/hr_expense_advance_clearing/__init__.py
+++ b/hr_expense_advance_clearing/__init__.py
@@ -1,2 +1,4 @@
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
from . import models
from . import wizard
diff --git a/hr_expense_advance_clearing/data/advance_product.xml b/hr_expense_advance_clearing/data/advance_product.xml
index 0a69a9fcb..2733fb032 100644
--- a/hr_expense_advance_clearing/data/advance_product.xml
+++ b/hr_expense_advance_clearing/data/advance_product.xml
@@ -1,3 +1,4 @@
+
Employee Advance
@@ -5,7 +6,10 @@
service
-
+
+
diff --git a/hr_expense_advance_clearing/migrations/19.0.1.0.0/post-migration.py b/hr_expense_advance_clearing/migrations/19.0.1.0.0/post-migration.py
new file mode 100644
index 000000000..ddebacb18
--- /dev/null
+++ b/hr_expense_advance_clearing/migrations/19.0.1.0.0/post-migration.py
@@ -0,0 +1,89 @@
+# Copyright 2024 ForgeFlow S.L.
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+import logging
+
+from openupgradelib import openupgrade
+
+_logger = logging.getLogger(__name__)
+
+
+@openupgrade.migrate()
+def migrate(env, version):
+ """Move 18.0 advance/clearing data onto the 19.0 per-expense model.
+
+ 18.0 carried the data at two levels; the *line-level* fields live on
+ ``hr_expense`` and survive the upgrade, so they are the source of truth:
+
+ ============================ ===============================
+ 18.0 (hr.expense column) 19.0 (hr.expense field)
+ ============================ ===============================
+ ``advance`` (bool) ``expense_type`` (expense/advance)
+ ``av_line_id`` (m2o expense) ``clearing_advance_id`` (m2o expense)
+ ============================ ===============================
+
+ Computed fields (``clearing_residual``, ``cleared_amount`` …) recompute on
+ their own and are intentionally not set here — matching the PR's deferral
+ of journal-reconciliation re-derivation.
+ """
+ cr = env.cr
+
+ # 1) expense_type from the 18.0 line-level `advance` boolean. The old
+ # column lingers on hr_expense until ORM cleanup, so it is readable here.
+ if openupgrade.column_exists(cr, "hr_expense", "advance"):
+ openupgrade.logged_query(
+ cr,
+ """
+ UPDATE hr_expense
+ SET expense_type = CASE WHEN advance THEN 'advance' ELSE 'expense' END
+ WHERE expense_type IS NULL
+ """,
+ )
+ # expense_type is required; default any remaining rows to 'expense'.
+ openupgrade.logged_query(
+ cr,
+ "UPDATE hr_expense SET expense_type = 'expense' WHERE expense_type IS NULL",
+ )
+
+ # 2) clearing_advance_id from the 18.0 line-level `av_line_id`: it linked a
+ # clearing expense to the advance expense it cleared — the exact
+ # semantics of the new per-expense clearing_advance_id.
+ if openupgrade.column_exists(cr, "hr_expense", "av_line_id"):
+ openupgrade.logged_query(
+ cr,
+ """
+ UPDATE hr_expense clr
+ SET clearing_advance_id = adv.id
+ FROM hr_expense adv
+ WHERE clr.av_line_id = adv.id
+ AND adv.expense_type = 'advance'
+ AND clr.clearing_advance_id IS NULL
+ """,
+ )
+
+ # 3) Sheet-level links the line-level pass could not reconstruct are NOT
+ # auto-linked (a clearing report grouped many expenses against one
+ # advance; blindly linking every line would over-attribute the draw-down
+ # and corrupt the residual). Instead, report them for manual review.
+ if openupgrade.table_exists(cr, "_ou_haac_sheet_link"):
+ cr.execute(
+ """
+ SELECT DISTINCT clr.id
+ FROM hr_expense clr
+ JOIN _ou_haac_sheet_link link ON clr.former_sheet_id = link.sheet_id
+ WHERE clr.clearing_advance_id IS NULL
+ AND clr.expense_type = 'expense'
+ """
+ )
+ unresolved = [r[0] for r in cr.fetchall()]
+ if unresolved:
+ _logger.warning(
+ "hr_expense_advance_clearing: %d expense(s) belonged to a "
+ "clearing report (sheet-level advance_sheet_id) but had no "
+ "line-level av_line_id; clearing_advance_id left empty for "
+ "manual review: %s",
+ len(unresolved),
+ unresolved,
+ )
+ openupgrade.logged_query(cr, "DROP TABLE _ou_haac_sheet_link")
+
+ _logger.info("hr_expense_advance_clearing: advance/clearing data migrated.")
diff --git a/hr_expense_advance_clearing/migrations/19.0.1.0.0/pre-migration.py b/hr_expense_advance_clearing/migrations/19.0.1.0.0/pre-migration.py
new file mode 100644
index 000000000..a3ec72f9c
--- /dev/null
+++ b/hr_expense_advance_clearing/migrations/19.0.1.0.0/pre-migration.py
@@ -0,0 +1,46 @@
+# Copyright 2024 ForgeFlow S.L.
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+import logging
+
+from openupgradelib import openupgrade
+
+_logger = logging.getLogger(__name__)
+
+
+@openupgrade.migrate()
+def migrate(env, version):
+ """Best-effort stash of the 18.0 *sheet-level* advance/clearing link.
+
+ In 18.0 a clearing expense report (``hr.expense.sheet``) pointed at the
+ advance report it cleared via ``advance_sheet_id``. 19.0 core removes the
+ ``hr.expense.sheet`` model entirely, so that column disappears with the
+ table. Because ``hr_expense_advance_clearing`` depends on ``hr_expense``,
+ core's migration (which drops the sheet table and fills
+ ``hr.expense.former_sheet_id``) runs *before* this script — so in the
+ standard upgrade path the table is already gone here and nothing is
+ stashed. The primary clearing link is rebuilt in post-migration from the
+ *line-level* ``hr.expense.av_line_id``, which lives on the surviving
+ ``hr_expense`` table.
+
+ Some OpenUpgrade orchestrations run addon pre-steps before core drops the
+ table; when that happens we preserve the sheet link so post-migration can
+ *report* (not silently guess) any clearing that the line-level pass missed.
+ """
+ cr = env.cr
+ if not openupgrade.table_exists(
+ cr, "hr_expense_sheet"
+ ) or not openupgrade.column_exists(cr, "hr_expense_sheet", "advance_sheet_id"):
+ _logger.info(
+ "hr_expense_advance_clearing: hr.expense.sheet already removed; "
+ "clearing links will be rebuilt from hr_expense.av_line_id."
+ )
+ return
+ openupgrade.logged_query(
+ cr,
+ """
+ CREATE TABLE IF NOT EXISTS _ou_haac_sheet_link AS
+ SELECT id AS sheet_id, advance_sheet_id
+ FROM hr_expense_sheet
+ WHERE advance_sheet_id IS NOT NULL
+ """,
+ )
diff --git a/hr_expense_advance_clearing/models/__init__.py b/hr_expense_advance_clearing/models/__init__.py
index 154043cdd..01fd2d4a0 100644
--- a/hr_expense_advance_clearing/models/__init__.py
+++ b/hr_expense_advance_clearing/models/__init__.py
@@ -1,4 +1,6 @@
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
from . import hr_expense
-from . import hr_expense_sheet
-from . import hr_employee_base
+from . import hr_employee
from . import account_move
+from . import account_payment
diff --git a/hr_expense_advance_clearing/models/account_move.py b/hr_expense_advance_clearing/models/account_move.py
index 0b796e081..ada6de303 100644
--- a/hr_expense_advance_clearing/models/account_move.py
+++ b/hr_expense_advance_clearing/models/account_move.py
@@ -1,4 +1,5 @@
# Copyright 2022 Ecosoft Co., Ltd. (https://ecosoft.co.th)
+# Copyright 2026 Ledo
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models
@@ -9,17 +10,28 @@ class AccountMove(models.Model):
_inherit = "account.move"
def _check_hr_advance_move_reconciled(self):
- """Check if the advance move lines already cleard/returned"""
- av_moves = self.filtered("line_ids.expense_id.sheet_id.advance")
- emp_advance = self.env.ref("hr_expense_advance_clearing.product_emp_advance")
+ """Refuse to draft/cancel/reverse a move whose advance line is
+ already reconciled with a clearing or return — user must undo the
+ downstream reconciliation first."""
+ av_moves = self.filtered(
+ lambda m: any(
+ line.expense_id.expense_type == "advance" for line in m.line_ids
+ )
+ )
+ emp_advance = self.env.ref(
+ "hr_expense_advance_clearing.product_emp_advance", False
+ )
+ if not emp_advance:
+ return
reconciled_av_move_lines = av_moves.mapped("line_ids").filtered(
- lambda l: l.product_id == emp_advance and l.matching_number
+ lambda line: line.product_id == emp_advance and line.matching_number
)
if reconciled_av_move_lines:
raise UserError(
self.env._(
- "This operation is not allowed as some advance amount was already "
- "cleared/returned.\nPlease cancel those documents first."
+ "This operation is not allowed as some advance amount was "
+ "already cleared/returned.\nPlease cancel those documents "
+ "first."
)
)
@@ -37,24 +49,9 @@ def _reverse_moves(self, default_values_list=None, cancel=False):
default_values_list=default_values_list, cancel=cancel
)
- @api.depends(
- "line_ids.matched_debit_ids.debit_move_id.move_id.payment_id.is_matched",
- "line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual",
- "line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual_currency",
- "line_ids.matched_credit_ids.credit_move_id.move_id.payment_id.is_matched",
- "line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual",
- "line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual_currency",
- "line_ids.balance",
- "line_ids.currency_id",
- "line_ids.amount_currency",
- "line_ids.amount_residual",
- "line_ids.amount_residual_currency",
- "line_ids.payment_id.state",
- "line_ids.full_reconcile_id",
- "state",
- )
def _compute_amount(self):
- """Compute amount residual for advance clearing case."""
+ """Mirror 18.0: for a clearing expense's move, the receivable/
+ payable residual reflects only the unreconciled portion."""
res = super()._compute_amount()
for move in self:
total_residual = 0.0
@@ -62,18 +59,25 @@ def _compute_amount(self):
for line in move.line_ids:
if line.account_type not in ("asset_receivable", "liability_payable"):
continue
- # Line residual amount.
- clearing = line.expense_id.sheet_id.filtered(
- lambda sheet: sheet.advance_sheet_id
- )
+ # Line residual amount on a clearing expense.
+ clearing = line.expense_id.filtered("clearing_advance_id")
if clearing:
- # Residual amount.
total_residual += line.amount_residual
total_residual_currency += line.amount_residual_currency
-
- # Update amount residual for case clearing
if total_residual and total_residual_currency:
sign = move.direction_sign
move.amount_residual = -sign * total_residual
move.amount_residual_signed = total_residual_currency
return res
+
+ def action_force_register_payment(self):
+ """Core blocks Register Payment on move_type='entry'. When the move
+ belongs to a clearing expense whose total exceeds the advance, we
+ legitimately need to register the residual."""
+ if all(
+ m.move_type == "entry"
+ and any(line.expense_id.clearing_advance_id for line in m.line_ids)
+ for m in self
+ ):
+ return self.line_ids.action_register_payment(ctx={"expense_clearing": 1})
+ return super().action_force_register_payment()
diff --git a/hr_expense_advance_clearing/models/account_payment.py b/hr_expense_advance_clearing/models/account_payment.py
new file mode 100644
index 000000000..896962f9d
--- /dev/null
+++ b/hr_expense_advance_clearing/models/account_payment.py
@@ -0,0 +1,36 @@
+# Copyright 2022 Ecosoft Co., Ltd. (https://ecosoft.co.th)
+# Copyright 2026 Ledo
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from odoo import api, fields, models
+
+
+class AccountPayment(models.Model):
+ _inherit = "account.payment"
+
+ # 18.0 pointed at hr.expense.sheet; 19.0 points at the advance expense.
+ advance_id = fields.Many2one(
+ comodel_name="hr.expense",
+ string="Advance",
+ domain="[('expense_type', '=', 'advance')]",
+ readonly=True,
+ help="The employee advance this payment relates to — the advance it "
+ "pays out, or the advance whose unused balance it returns.",
+ )
+
+ def _synchronize_from_moves(self, changed_fields):
+ """Skip the standard move-payment sync for return-advance payments
+ — the move is a generic entry, not the payment's own posting flow."""
+ self = (
+ self.with_context(skip_account_move_synchronization=True)
+ if self.filtered("advance_id")
+ else self
+ )
+ return super()._synchronize_from_moves(changed_fields)
+
+ @api.model
+ def _get_valid_payment_account_types(self):
+ account_types = super()._get_valid_payment_account_types()
+ if self.env.context.get("hr_return_advance"):
+ account_types.append("asset_current")
+ return account_types
diff --git a/hr_expense_advance_clearing/models/hr_employee.py b/hr_expense_advance_clearing/models/hr_employee.py
new file mode 100644
index 000000000..0df69cda1
--- /dev/null
+++ b/hr_expense_advance_clearing/models/hr_employee.py
@@ -0,0 +1,50 @@
+# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th)
+# Copyright 2026 Ledo
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+# 19.0 dropped the `hr.employee.base` abstract model that 18.0 carried,
+# so this module adds the advance helpers to `hr.employee` directly. The
+# public mirror (`hr.employee.public`) gets the count + open-action so the
+# smart button works on the public form too.
+
+from odoo import fields, models
+
+
+class HrEmployee(models.Model):
+ _inherit = "hr.employee"
+
+ advance_expense_ids = fields.One2many(
+ comodel_name="hr.expense",
+ inverse_name="employee_id",
+ domain=[("expense_type", "=", "advance")],
+ readonly=True,
+ )
+ advance_count = fields.Integer(
+ string="# of Advances",
+ compute="_compute_advance_count",
+ help="Count of advance expenses paid to this employee.",
+ )
+
+ def _compute_advance_count(self):
+ for employee in self:
+ employee.advance_count = len(employee.advance_expense_ids)
+
+ def action_open_advance_clearing(self):
+ self.ensure_one()
+ return {
+ "name": self.env._("Employee Advances"),
+ "type": "ir.actions.act_window",
+ "res_model": "hr.expense",
+ "view_mode": "list,form",
+ "domain": [("id", "in", self.advance_expense_ids.ids)],
+ }
+
+
+class HrEmployeePublic(models.Model):
+ _inherit = "hr.employee.public"
+
+ advance_count = fields.Integer(related="employee_id.advance_count")
+
+ def action_open_advance_clearing(self):
+ self.ensure_one()
+ return self.employee_id.action_open_advance_clearing()
diff --git a/hr_expense_advance_clearing/models/hr_employee_base.py b/hr_expense_advance_clearing/models/hr_employee_base.py
deleted file mode 100644
index a625ea0d2..000000000
--- a/hr_expense_advance_clearing/models/hr_employee_base.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th)
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-from odoo import fields, models
-
-
-class HrEmployeeBase(models.AbstractModel):
- _inherit = "hr.employee.base"
-
- advance_sheet_ids = fields.One2many(
- comodel_name="hr.expense.sheet",
- inverse_name="employee_id",
- domain=[("advance", "=", True)],
- readonly=True,
- )
- advance_count = fields.Integer(
- string="# of Advance",
- compute="_compute_advance_count",
- help="Count advance sheet in expense report",
- )
-
- def _compute_advance_count(self):
- self.advance_count = len(self.advance_sheet_ids)
-
- def action_open_advance_clearing(self):
- self.ensure_one()
- return {
- "name": self.env._("Advance Sheet"),
- "type": "ir.actions.act_window",
- "res_model": "hr.expense.sheet",
- "view_mode": "list,form",
- "domain": [("id", "in", self.advance_sheet_ids.ids)],
- }
diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py
index b8e55c7dd..f20428c1a 100644
--- a/hr_expense_advance_clearing/models/hr_expense.py
+++ b/hr_expense_advance_clearing/models/hr_expense.py
@@ -1,37 +1,122 @@
# Copyright 2019 Kitti Upariphutthiphong
+# Copyright 2026 Ledo
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import Command, api, fields, models
-from odoo.exceptions import ValidationError
+from odoo import api, fields, models
+from odoo.exceptions import UserError, ValidationError
class HrExpense(models.Model):
+ """19.0 redesign: the 18.0 advance/clearing-sheet pair becomes a flat
+ per-expense model.
+
+ - An "advance" expense (expense_type='advance') is money paid to the
+ employee in advance of any spending.
+ - A "regular" expense (expense_type='expense') is the normal
+ reimbursement flow. When a regular expense clears against an
+ advance, it sets clearing_advance_id on itself.
+ - Reconciliation: the advance's receivable line reconciles with the
+ clearing expense's receivable line. Once the advance's
+ clearing_residual hits zero, state advances to 'cleared'.
+ """
+
_inherit = "hr.expense"
- advance = fields.Boolean(string="Employee Advance", default=False)
+ expense_type = fields.Selection(
+ selection=[("expense", "Expense"), ("advance", "Advance")],
+ default="expense",
+ required=True,
+ tracking=True,
+ help="An 'advance' is money paid to the employee up front. Regular "
+ "'expense' records are either reimbursements or clearings against "
+ "a prior advance (when clearing_advance_id is set).",
+ )
+ clearing_advance_id = fields.Many2one(
+ comodel_name="hr.expense",
+ string="Clearing Advance",
+ domain="[('expense_type', '=', 'advance'),"
+ " ('employee_id', '=', employee_id),"
+ " ('clearing_residual', '>', 0.0)]",
+ tracking=True,
+ ondelete="restrict",
+ help="When set on a regular expense, this expense clears against "
+ "the referenced advance. Only advances of the same employee "
+ "with residual > 0 are selectable.",
+ )
+ clearing_expense_ids = fields.One2many(
+ comodel_name="hr.expense",
+ inverse_name="clearing_advance_id",
+ string="Clearing Expenses",
+ readonly=True,
+ help="Expenses cleared against this advance.",
+ )
+ clearing_count = fields.Integer(compute="_compute_clearing_count")
+ payment_return_ids = fields.One2many(
+ comodel_name="account.payment",
+ inverse_name="advance_id",
+ string="Returned Payments",
+ readonly=True,
+ help="Refund payments returning unused advance money to the company.",
+ )
+ return_count = fields.Integer(compute="_compute_return_count")
+
+ cleared_amount = fields.Monetary(
+ compute="_compute_clearing_residual",
+ store=True,
+ help="Sum of clearing expenses' totals (company currency).",
+ )
+ returned_amount = fields.Monetary(
+ compute="_compute_clearing_residual",
+ store=True,
+ help="Sum of return-advance payments (company currency).",
+ )
+ clearing_residual = fields.Monetary(
+ string="Amount to Clear",
+ compute="_compute_clearing_residual",
+ store=True,
+ help="Advance amount remaining to be cleared or returned.",
+ )
+ advance_residual = fields.Monetary(
+ string="Advance Remaining",
+ related="clearing_advance_id.clearing_residual",
+ store=True,
+ help="Remaining amount on the linked advance (for clearing expenses).",
+ )
+ amount_payable = fields.Monetary(
+ string="Payable Amount",
+ compute="_compute_amount_payable",
+ help="Register-payment amount after subtracting the cleared advance.",
+ )
clearing_product_id = fields.Many2one(
comodel_name="product.product",
string="Clearing Product",
tracking=True,
domain="[('can_be_expensed', '=', True),"
- "'|', ('company_id', '=', False), ('company_id', '=', company_id)]",
+ " '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
ondelete="restrict",
- help="Optional: On the clear advance, the clearing "
- "product will create default product line.",
- )
- av_line_id = fields.Many2one(
- comodel_name="hr.expense",
- string="Ref: Advance",
- ondelete="set null",
- help="Expense created from this advance expense line",
+ help="Optional: when set on an advance, this product is used as the "
+ "default for clearing expense lines.",
)
- @api.constrains("advance")
+ # -------------------------------------------------------------------------
+ # Helpers
+ # -------------------------------------------------------------------------
+
+ def _get_product_advance(self):
+ return self.env.ref("hr_expense_advance_clearing.product_emp_advance", False)
+
+ # -------------------------------------------------------------------------
+ # Constraints
+ # -------------------------------------------------------------------------
+
+ @api.constrains("expense_type", "product_id", "tax_ids", "payment_mode")
def _check_advance(self):
- for expense in self.filtered("advance"):
- emp_advance = self.env.ref(
- "hr_expense_advance_clearing.product_emp_advance"
- )
+ """An advance expense must use the Employee Advance product, no
+ taxes, and be employee-paid."""
+ emp_advance = self._get_product_advance()
+ if not emp_advance:
+ return True
+ for expense in self.filtered(lambda e: e.expense_type == "advance"):
if not emp_advance.property_account_expense_id:
raise ValidationError(
self.env._("Employee advance product has no payable account")
@@ -40,12 +125,6 @@ def _check_advance(self):
raise ValidationError(
self.env._("Employee advance, selected product is not valid")
)
- if expense.account_id != emp_advance.property_account_expense_id:
- raise ValidationError(
- self.env._(
- "Employee advance, account must be the same payable account"
- )
- )
if expense.tax_ids:
raise ValidationError(
self.env._("Employee advance, all taxes must be removed")
@@ -56,46 +135,156 @@ def _check_advance(self):
)
return True
- @api.onchange("advance")
- def onchange_advance(self):
- self.tax_ids = False
- if self.advance:
- self.product_id = self.env.ref(
- "hr_expense_advance_clearing.product_emp_advance"
- )
+ @api.constrains("clearing_advance_id", "expense_type", "employee_id")
+ def _check_clearing(self):
+ """A clearing expense must be 'expense' type, must reference an
+ advance of the same employee, and the advance must be paid."""
+ for expense in self.filtered("clearing_advance_id"):
+ if expense.expense_type == "advance":
+ raise ValidationError(
+ self.env._("An advance cannot itself clear another advance.")
+ )
+ advance = expense.clearing_advance_id
+ if advance.employee_id != expense.employee_id:
+ raise ValidationError(
+ self.env._("The linked advance must belong to the same employee.")
+ )
+
+ # -------------------------------------------------------------------------
+ # Onchanges + defaults
+ # -------------------------------------------------------------------------
+
+ @api.onchange("expense_type")
+ def _onchange_expense_type(self):
+ if self.expense_type == "advance":
+ emp_advance = self._get_product_advance()
+ if emp_advance:
+ self.product_id = emp_advance
+ self.tax_ids = False
+ self.payment_mode = "own_account"
+ self.clearing_advance_id = False
- def _get_account_move_line_values(self):
- move_line_values_by_expense = super()._get_account_move_line_values()
- # Only when do the clearing, change cr payable to cr advance
- emp_advance = self.env.ref("hr_expense_advance_clearing.product_emp_advance")
- sheets = self.mapped("sheet_id").filtered("advance_sheet_id")
- sheets_x = sheets.filtered(lambda x: x.advance_sheet_residual <= 0.0)
- if sheets_x: # Advance Sheets with no residual left
- raise ValidationError(
- _("Advance: %s has no amount to clear")
- % ", ".join(sheets_x.mapped("name"))
+ @api.onchange("clearing_advance_id")
+ def _onchange_clearing_advance_id(self):
+ """Pre-fill clearing product from the advance if it has one set."""
+ advance = self.clearing_advance_id
+ if advance and advance.clearing_product_id and not self.product_id:
+ self.product_id = advance.clearing_product_id
+
+ # -------------------------------------------------------------------------
+ # Computes
+ # -------------------------------------------------------------------------
+
+ @api.depends(
+ "expense_type",
+ "total_amount_currency",
+ "clearing_expense_ids.total_amount_currency",
+ "clearing_expense_ids.state",
+ "payment_return_ids.amount",
+ "payment_return_ids.state",
+ )
+ def _compute_clearing_residual(self):
+ for expense in self:
+ if expense.expense_type != "advance":
+ expense.cleared_amount = 0.0
+ expense.returned_amount = 0.0
+ expense.clearing_residual = 0.0
+ continue
+ cleared = sum(
+ expense.clearing_expense_ids.filtered(
+ lambda e: e.state not in ("draft", "submitted", "refused")
+ ).mapped("total_amount_currency")
)
- for sheet in sheets:
- advance_to_clear = sheet.advance_sheet_residual
- for move_lines in move_line_values_by_expense.values():
- payable_move_line = False
- for move_line in move_lines:
- credit = move_line["credit"]
- if not credit:
- continue
- # cr payable -> cr advance
- remain_payable = 0.0
- if credit > advance_to_clear:
- remain_payable = credit - advance_to_clear
- move_line["credit"] = advance_to_clear
- advance_to_clear = 0.0
- # extra payable line
- payable_move_line = move_line.copy()
- payable_move_line["credit"] = remain_payable
- else:
- advance_to_clear -= credit
- # advance line
- move_line["account_id"] = emp_advance.property_account_expense_id.id
- if payable_move_line:
- move_lines.append(payable_move_line)
- return move_line_values_by_expense
+ returned = sum(
+ expense.payment_return_ids.filtered(
+ lambda p: p.state in ("in_process", "paid")
+ ).mapped("amount")
+ )
+ expense.cleared_amount = cleared
+ expense.returned_amount = returned
+ expense.clearing_residual = (
+ expense.total_amount_currency - cleared - returned
+ )
+
+ @api.depends("clearing_expense_ids")
+ def _compute_clearing_count(self):
+ for expense in self:
+ expense.clearing_count = len(expense.clearing_expense_ids)
+
+ @api.depends("payment_return_ids")
+ def _compute_return_count(self):
+ for expense in self:
+ expense.return_count = len(expense.payment_return_ids)
+
+ @api.depends(
+ "total_amount_currency",
+ "clearing_advance_id.clearing_residual",
+ )
+ def _compute_amount_payable(self):
+ """For a clearing expense: payable = total − advance available."""
+ for expense in self:
+ if not expense.clearing_advance_id:
+ expense.amount_payable = 0.0
+ continue
+ residual = expense.clearing_advance_id.clearing_residual
+ expense.amount_payable = max(expense.total_amount_currency - residual, 0.0)
+
+ # -------------------------------------------------------------------------
+ # Server actions
+ # -------------------------------------------------------------------------
+
+ def action_return_advance(self):
+ """Open Register Payment for the remaining advance residual so the
+ employee can refund what they didn't spend."""
+ self.ensure_one()
+ if self.expense_type != "advance":
+ raise UserError(self.env._("Only advance expenses can be returned."))
+ if self.clearing_residual <= 0:
+ raise UserError(self.env._("This advance has no residual to return."))
+ return {
+ "type": "ir.actions.act_window",
+ "name": self.env._("Return Advance"),
+ "res_model": "account.payment.register",
+ "view_mode": "form",
+ "target": "new",
+ "context": {
+ "default_partner_type": "customer",
+ "default_partner_id": self.employee_id.sudo().work_contact_id.id,
+ "default_amount": self.clearing_residual,
+ "default_currency_id": self.currency_id.id,
+ "default_advance_id": self.id,
+ "hr_return_advance": 1,
+ },
+ }
+
+ def action_view_clearings(self):
+ self.ensure_one()
+ return {
+ "type": "ir.actions.act_window",
+ "name": self.env._("Clearing Expenses"),
+ "res_model": "hr.expense",
+ "view_mode": "list,form",
+ "domain": [("id", "in", self.clearing_expense_ids.ids)],
+ }
+
+ def action_view_returns(self):
+ self.ensure_one()
+ return {
+ "type": "ir.actions.act_window",
+ "name": self.env._("Returned Payments"),
+ "res_model": "account.payment",
+ "view_mode": "list,form",
+ "domain": [("id", "in", self.payment_return_ids.ids)],
+ }
+
+ # Auto-reconcile note: the 18.0 module reconciled the clearing sheet's
+ # journal items against the advance sheet's employee-advance-account
+ # lines at sheet-post time (sheet's _do_create_moves routed the credit
+ # line to the advance account specifically). In 19.0 the equivalent
+ # per-expense routing requires overriding _prepare_receipts_vals to
+ # swap the credit account for clearing expenses, which is non-trivial.
+ # Scoped out for this iteration — clearing_residual is driven by
+ # count-based aggregation of clearing_expense_ids' totals; users can
+ # reconcile journal items via the standard Odoo reconciliation UI.
+ # Follow-up: ADR section "Auto-reconciliation" tracks the design
+ # required to bring this back in a future iteration.
diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py
deleted file mode 100644
index fb9cdd1bd..000000000
--- a/hr_expense_advance_clearing/models/hr_expense_sheet.py
+++ /dev/null
@@ -1,349 +0,0 @@
-# Copyright 2019 Kitti Upariphutthiphong
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-import ast
-
-from odoo import Command, api, fields, models
-from odoo.exceptions import ValidationError
-from odoo.tools.safe_eval import safe_eval
-
-
-class HrExpenseSheet(models.Model):
- _inherit = "hr.expense.sheet"
-
- advance = fields.Boolean(
- string="Employee Advance",
- )
- advance_sheet_id = fields.Many2one(
- comodel_name="hr.expense.sheet",
- string="Clear Advance",
- domain="[('advance', '=', True), ('employee_id', '=', employee_id),"
- " ('clearing_residual', '>', 0.0)]",
- readonly=True,
- states={
- "draft": [("readonly", False)],
- "submit": [("readonly", False)],
- "approve": [("readonly", False)],
- },
- help="Show remaining advance of this employee",
- )
- clearing_sheet_ids = fields.One2many(
- comodel_name="hr.expense.sheet",
- inverse_name="advance_sheet_id",
- string="Clearing Sheet",
- readonly=True,
- help="Show reference clearing on advance",
- )
- clearing_residual = fields.Monetary(
- string="Amount to clear",
- compute="_compute_clearing_residual",
- store=True,
- help="Amount to clear of this expense sheet in company currency",
- )
- advance_sheet_residual = fields.Monetary(
- string="Advance Remaining",
- related="advance_sheet_id.clearing_residual",
- store=True,
- help="Remaining amount to clear the selected advance sheet",
- )
- amount_payable = fields.Monetary(
- string="Payable Amount",
- compute="_compute_amount_payable",
- help="Final regiter payment amount even after advance clearing",
- )
-
- @api.constrains("advance_sheet_id", "expense_line_ids")
- def _check_advance_expense(self):
- advance_lines = self.expense_line_ids.filtered("advance")
- if self.advance_sheet_id and advance_lines:
- raise ValidationError(
- self.env._("Advance clearing must not contain any advance expense line")
- )
- if advance_lines and len(advance_lines) != len(self.expense_line_ids):
- raise ValidationError(
- self.env._("Advance must contain only advance expense line")
- )
-
- @api.depends("account_move_id.payment_state")
- def _compute_payment_state(self):
- """After clear advance.
- if amount residual is zero, payment state will change to 'paid'
- """
- res = super()._compute_payment_state()
- for sheet in self:
- if (
- sheet.advance_sheet_id
- and set(sheet.account_move_ids.mapped("state")) == {"posted"}
- and not sheet.amount_residual
- ):
- sheet.payment_state = "paid"
- return res
-
- def _get_product_advance(self):
- return self.env.ref("hr_expense_advance_clearing.product_emp_advance", False)
-
- @api.depends("account_move_id.line_ids.amount_residual")
- def _compute_clearing_residual(self):
- emp_advance = self.env.ref(
- "hr_expense_advance_clearing.product_emp_advance", False
- )
- for sheet in self:
- residual_company = 0.0
- if emp_advance:
- property_account_expense_id = emp_advance.with_company(
- sheet.company_id
- ).property_account_expense_id
- for line in sheet.sudo().account_move_id.line_ids:
- if line.account_id == property_account_expense_id:
- residual_company += line.amount_residual
- sheet.clearing_residual = residual_company
-
- def _compute_amount_payable(self):
- for sheet in self:
- rec_lines = sheet.account_move_id.line_ids.filtered(
- lambda x: x.credit and x.account_id.reconcile and not x.reconciled
- )
- sheet.amount_payable = -sum(rec_lines.mapped("amount_residual"))
-
- def action_sheet_move_create(self):
- res = super(HrExpenseSheet, self).action_sheet_move_create()
- # Reconcile advance of this sheet with the advance_sheet
- emp_advance = self.env.ref("hr_expense_advance_clearing.product_emp_advance")
- ctx = self._context.copy()
- ctx.update({"skip_account_move_synchronization": True})
- for sheet in self:
- move_lines = (
- sheet.account_move_id.line_ids
- | sheet.advance_sheet_id.account_move_id.line_ids
- )
- account_id = emp_advance.property_account_expense_id.id
- adv_move_lines = (
- self.env["account.move.line"]
- .sudo()
- .search(
- [
- ("id", "in", move_lines.ids),
- ("account_id", "=", account_id),
- ("reconciled", "=", False),
- ]
- )
- )
- adv_move_lines.with_context(ctx).reconcile()
- # Update state on clearing advance when advance residual > total amount
- if advance_residual != -1:
- sheet.write(
- {
- "state": "done",
- }
- )
- # Update amount residual and state when advance residual < total amount
- else:
- sheet.write(
- {
- "state": "post",
- "payment_state": "not_paid",
- "amount_residual": sheet.total_amount
- - amount_residual_bf_reconcile,
- }
- )
- return res
-
- def _get_move_line_vals(self):
- self.ensure_one()
- move_line_vals = []
- advance_to_clear = self.advance_sheet_residual
- emp_advance = self._get_product_advance()
- account_advance = emp_advance.property_account_expense_id
- for expense in self.expense_line_ids:
- move_line_name = (
- f"{expense.employee_id.name}: {expense.name.splitlines()[0][:64]}"
- )
- partner_id = expense.employee_id.sudo().work_contact_id.id
-
- total_amount = -expense.total_amount
- total_amount_currency = -expense.total_amount_currency
-
- # Source move line
- move_line_src = expense._get_move_line_src(move_line_name, partner_id)
- move_line_values = [move_line_src]
-
- # Destination move line
- move_line_dst = expense._get_move_line_dst(
- move_line_name,
- partner_id,
- total_amount,
- total_amount_currency,
- account_advance,
- )
-
- # Check clearing > advance, it will split line
- credit = move_line_dst["credit"]
- # cr payable -> cr advance
- remain_payable = 0.0
- payable_move_line = []
- rounding = expense.currency_id.rounding
- if (
- float_compare(
- credit,
- advance_to_clear,
- precision_rounding=rounding,
- )
- == 1
- ):
- remain_payable = credit - advance_to_clear
- move_line_dst.update(
- {"credit": advance_to_clear, "amount_currency": -advance_to_clear}
- )
- advance_to_clear = 0.0
- # extra payable line
- account_dest = expense.sheet_id._get_expense_account_destination()
- payable_move_line = move_line_dst.copy()
- payable_move_line.update(
- {
- "credit": remain_payable,
- "amount_currency": -remain_payable,
- "account_id": account_dest,
- }
- )
- else:
- advance_to_clear -= credit # Reduce remaining advance
-
- # Add destination first (if credit is not zero)
- if not float_is_zero(move_line_dst["credit"], precision_rounding=rounding):
- move_line_values.append(move_line_dst)
- if payable_move_line:
- move_line_values.append(payable_move_line)
- move_line_vals.extend(move_line_values)
- return move_line_vals
-
- def _prepare_bills_vals(self):
- """create journal entry instead of bills when clearing document"""
- self.ensure_one()
- res = super()._prepare_bills_vals()
- if self.advance_sheet_id and self.payment_mode == "own_account":
- # Advance Sheets with no residual left
- if self.advance_sheet_residual <= 0.0:
- raise ValidationError(
- self.env._(
- "Advance: %(name)s has no amount to clear", name=self.name
- )
- )
- res.update(
- {
- "move_type": "entry",
- "line_ids": [
- Command.create(vals) for vals in self._get_move_line_vals()
- ],
- }
- )
- return res
-
- def _check_can_approve(self):
- """Check advance residual before approval"""
- for sheet in self.filtered("advance_sheet_id"):
- if sheet.advance_sheet_residual <= 0.0:
- raise ValidationError(
- self.env._(
- "Advance: %(name)s has no amount to clear",
- name=sheet.advance_sheet_id.name,
- )
- )
- return super()._check_can_approve()
-
- def open_clear_advance(self):
- self.ensure_one()
- action = self.env.ref(
- "hr_expense_advance_clearing." "action_hr_expense_sheet_advance_clearing"
- )
- vals = action.sudo().read()[0]
- context1 = vals.get("context", {})
- if context1:
- context1 = safe_eval(context1)
- context1["default_advance_sheet_id"] = self.id
- context1["default_employee_id"] = self.employee_id.id
- vals["context"] = context1
- return vals
-
- def get_domain_advance_sheet_expense_line(self):
- return self.advance_sheet_id.expense_line_ids.filtered("clearing_product_id")
-
- def create_clearing_expense_line(self, line):
- clear_advance = self._prepare_clear_advance(line)
- clearing_line = self.env["hr.expense"].new(clear_advance)
- return clearing_line
-
- @api.onchange("advance_sheet_id")
- def _onchange_advance_sheet_id(self):
- self.expense_line_ids -= self.expense_line_ids.filtered("av_line_id")
- self.advance_sheet_id.expense_line_ids.sudo().read() # prefetch
- lines = self.get_domain_advance_sheet_expense_line()
- for line in lines:
- self.expense_line_ids += self.create_clearing_expense_line(line)
-
- def _prepare_clear_advance(self, line):
- # Prepare the clearing expense
- clear_line_dict = {
- "advance": False,
- "name": False,
- "product_id": line.clearing_product_id.id,
- "clearing_product_id": False,
- "date": fields.Date.context_today(self),
- "account_id": False,
- "state": "draft",
- "product_uom_id": False,
- "av_line_id": line.id,
- }
- clear_line = self.env["hr.expense"].new(clear_line_dict)
- clear_line._compute_from_product_id_company_id() # Set some vals
- # Prepare the original advance line
- adv_dict = line._convert_to_write(line._cache)
- # remove no update columns
- _fields = line._fields
- del_cols = [k for k in _fields.keys() if _fields[k].type == "one2many"]
- del_cols += list(self.env["mail.thread"]._fields.keys())
- del_cols += list(self.env["mail.activity.mixin"]._fields.keys())
- del_cols += list(clear_line_dict.keys())
- del_cols = list(set(del_cols))
- adv_dict = {k: v for k, v in adv_dict.items() if k not in del_cols}
- # Assign the known value from original advance line
- clear_line.update(adv_dict)
- clearing_dict = clear_line._convert_to_write(clear_line._cache)
- # Convert list of int to [(6, 0, list)]
- clearing_dict = {
- k: isinstance(v, list)
- and all(isinstance(x, int) for x in v)
- and [(6, 0, v)]
- or v
- for k, v in clearing_dict.items()
- }
- return clearing_dict
-
- def action_open_clearings(self):
- self.ensure_one()
- return {
- "name": self.env._("Clearing Sheets"),
- "type": "ir.actions.act_window",
- "res_model": "hr.expense.sheet",
- "view_mode": "list,form",
- "domain": [("id", "in", self.clearing_sheet_ids.ids)],
- }
-
- def action_open_payment_return(self):
- self.ensure_one()
- return {
- "name": self.env._("Payment Return"),
- "type": "ir.actions.act_window",
- "res_model": "account.payment",
- "view_mode": "list,form",
- "domain": [("id", "in", self.payment_return_ids.ids)],
- }
-
- def action_register_payment(self):
- action = super().action_register_payment()
- if self.env.context.get("hr_return_advance"):
- action["context"].update(
- {
- "clearing_sheet_ids": self.clearing_sheet_ids.ids,
- }
- )
- return action
diff --git a/hr_expense_advance_clearing/pyproject.toml b/hr_expense_advance_clearing/pyproject.toml
new file mode 100644
index 000000000..4231d0ccc
--- /dev/null
+++ b/hr_expense_advance_clearing/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["whool"]
+build-backend = "whool.buildapi"
diff --git a/hr_expense_advance_clearing/tests/__init__.py b/hr_expense_advance_clearing/tests/__init__.py
index 6867ba88a..865cc440a 100644
--- a/hr_expense_advance_clearing/tests/__init__.py
+++ b/hr_expense_advance_clearing/tests/__init__.py
@@ -1 +1,3 @@
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
from . import test_hr_expense_advance_clearing
diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
index 02ba48948..04adf9979 100644
--- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
+++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py
@@ -1,11 +1,10 @@
# Copyright 2019 Kitti Upariphutthiphong
# Copyright 2024 Tecnativa - Víctor Martínez
+# Copyright 2026 Ledo
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import fields
from odoo.exceptions import UserError, ValidationError
-from odoo.tests.common import Form, tagged
-from odoo.tools import mute_logger
+from odoo.tests import tagged
from odoo.addons.hr_expense.tests.common import TestExpenseCommon
@@ -13,288 +12,137 @@
@tagged("-at_install", "post_install")
class TestHrExpenseAdvanceClearing(TestExpenseCommon):
@classmethod
- def setUpClass(cls, chart_template_ref=None):
- super().setUpClass(chart_template_ref=chart_template_ref)
- advance_account = cls.company_data["default_account_assets"]
+ def setUpClass(cls):
+ super().setUpClass()
+ advance_account = cls.company_data["default_account_deferred_expense"]
advance_account.reconcile = True
cls.emp_advance = cls.env.ref("hr_expense_advance_clearing.product_emp_advance")
cls.emp_advance.property_account_expense_id = advance_account
cls.product_a.standard_price = 0
- # Create advance expense 1,000
- cls.advance = cls._create_expense_sheet(
- cls,
- "Advance 1,000",
- cls.expense_employee,
- cls.emp_advance,
- 1000.0,
- advance=True,
- )
- # Create clearing expense 1,000
- cls.clearing_equal = cls._create_expense_sheet(
- cls, "Buy service 1,000", cls.expense_employee, cls.product_a, 1000.0
- )
- # Create clearing expense 1,200
- cls.clearing_more = cls._create_expense_sheet(
- cls, "Buy service 1,200", cls.expense_employee, cls.product_a, 1200.0
- )
- # Create clearing expense 800
- cls.clearing_less = cls._create_expense_sheet(
- cls, "Buy service 800", cls.expense_employee, cls.product_a, 800.0
- )
-
- def _create_expense(
- self,
- description,
- employee,
- product,
- amount,
- advance=False,
- payment_mode="own_account",
- account=False,
- ):
- with Form(
- self.env["hr.expense"].with_context(default_advance=advance)
- ) as expense:
- expense.name = description
- expense.employee_id = employee
- if not advance:
- expense.product_id = product
- expense.unit_amount = amount
- expense.payment_mode = payment_mode
- if account:
- expense.account_id = account
- expense = expense.save()
- expense.tax_ids = False # Test no vat
- return expense
- def _create_expense_sheet(
- self, description, employee, product, amount, advance=False
- ):
- expense = self._create_expense(
- self, description, employee, product, amount, advance
- )
- # Add expense to expense sheet
- expense_sheet = self.env["hr.expense.sheet"].create(
+ def _new_advance(self, amount=1000.0):
+ advance = self.env["hr.expense"].create(
{
- "name": description,
- "employee_id": expense.employee_id.id,
- "expense_line_ids": [(6, 0, [expense.id])],
- "advance": advance,
+ "name": f"Advance {amount}",
+ "employee_id": self.expense_employee.id,
+ "product_id": self.emp_advance.id,
+ "total_amount_currency": amount,
+ "expense_type": "advance",
+ "payment_mode": "own_account",
}
)
- return expense_sheet
+ advance.action_submit()
+ if advance.state == "submitted":
+ advance.action_approve()
+ return advance
- def _register_payment(self, move_id, amount, ctx=False, hr_return_advance=False):
- if not ctx:
- ctx = {
- "active_ids": [move_id.id],
- "active_id": move_id.id,
- "active_model": "account.move",
+ def _new_clearing(self, advance, amount):
+ return self.env["hr.expense"].create(
+ {
+ "name": f"Clearing {amount}",
+ "employee_id": self.expense_employee.id,
+ "product_id": self.product_a.id,
+ "total_amount_currency": amount,
+ "payment_mode": "own_account",
+ "clearing_advance_id": advance.id,
}
- ctx["hr_return_advance"] = hr_return_advance
- PaymentWizard = self.env["account.payment.register"]
- with Form(PaymentWizard.with_context(**ctx)) as f:
- f.journal_id = self.company_data["default_journal_bank"]
- f.payment_date = fields.Date.today()
- f.amount = amount
- payment_wizard = f.save()
- payment_wizard.expense_post_payment()
+ )
- def test_0_test_constraints(self):
- """ Test some constraints """
- # Advance Sheet can't be clearing at the same time.
- with self.assertRaises(ValidationError):
- self.advance.advance_sheet_id = self.advance
- # Advance Sheet can't change account is not the equal
- # Account on Advance Expense's product.
- with self.assertRaises(ValidationError):
- expense = self._create_expense(
- "Advance 1,000",
- self.expense_employee,
- self.emp_advance,
- 1.0,
- advance=True,
- account=self.account_sales,
- )
- expense.account_id = self.company_data["default_account_payable"]
- expense._check_advance()
- # Advance Sheet should not have > 1 expense lines
- with self.assertRaises(ValidationError):
- expense = self._create_expense(
- "Buy service 1,000", self.expense_employee, self.product_a, 1.0
- )
- self.advance.write({"expense_line_ids": [(4, expense.id)]})
- # Advance Expense's product, must not has tax involved
- with self.assertRaises(ValidationError):
- self.emp_advance.supplier_taxes_id |= self.company_data[
- "default_tax_purchase"
- ]
- expense = self._create_expense(
- "Advance 1,000",
- self.expense_employee,
- self.emp_advance,
- 1.0,
- advance=True,
- )
- self.emp_advance.supplier_taxes_id = False # Remove tax bf proceed
- # Advance Expense, must not paid by company
- with self.assertRaises(ValidationError):
- expense = self._create_expense(
- "Advance 1,000",
- self.expense_employee,
- self.emp_advance,
- 1.0,
- advance=True,
- payment_mode="company_account",
- )
- # Advance Expense, must be product advance only
+ def test_advance_constraint_taxes_forbidden(self):
+ """An advance expense cannot carry taxes."""
+ tax = self.env["account.tax"].create(
+ {"name": "T", "amount": 10.0, "type_tax_use": "purchase"}
+ )
+ advance = self.env["hr.expense"].create(
+ {
+ "name": "Advance",
+ "employee_id": self.expense_employee.id,
+ "product_id": self.emp_advance.id,
+ "total_amount_currency": 100.0,
+ "expense_type": "advance",
+ "payment_mode": "own_account",
+ }
+ )
with self.assertRaises(ValidationError):
- expense = self._create_expense(
- "Advance 1,000",
- self.expense_employee,
- self.emp_advance,
- 1.0,
- advance=True,
- )
- expense.product_id = self.product_a
- expense._check_advance()
- # Advance Expense's product must have account configured
+ advance.tax_ids = [(6, 0, tax.ids)]
+
+ def test_clearing_constraint_same_employee(self):
+ """A clearing expense's advance must belong to the same employee."""
+ advance = self._new_advance()
+ # hr.employee creation requires resource.resource access in 19.0;
+ # use sudo() since this test isn't exercising employee permissions.
+ other_emp = self.env["hr.employee"].sudo().create({"name": "Other"})
with self.assertRaises(ValidationError):
- self.emp_advance.property_account_expense_id = False
- expense = self._create_expense(
- "Advance 1,000",
- self.expense_employee,
- self.emp_advance,
- 1.0,
- advance=True,
+ self.env["hr.expense"].create(
+ {
+ "name": "Mismatch",
+ "employee_id": other_emp.id,
+ "product_id": self.product_a.id,
+ "total_amount_currency": 50.0,
+ "clearing_advance_id": advance.id,
+ }
)
- @mute_logger("odoo.models.unlink")
- def test_1_clear_equal_advance(self):
- """ When clear equal advance, all set """
- # ------------------ Advance --------------------------
- self.advance.action_submit_sheet()
- self.advance.approve_expense_sheets()
- self.advance.action_sheet_move_create()
- self.assertEqual(self.advance.clearing_residual, 1000.0)
- self._register_payment(self.advance)
- self.assertEqual(self.advance.state, "done")
- # ------------------ Clearing --------------------------
- # Clear this with previous advance
- vals = self.advance.open_clear_advance() # Test Clear Advance button
- ctx = vals.get("context")
- self.assertEqual(ctx["default_advance_sheet_id"], self.advance.id)
- self.clearing_equal.advance_sheet_id = self.advance
- self.assertEqual(self.clearing_equal.advance_sheet_residual, 1000.0)
- self.clearing_equal.action_submit_sheet()
- self.clearing_equal.approve_expense_sheets()
- self.clearing_equal.action_sheet_move_create()
- # Equal amount, state change to Paid and advance is cleared
- self.assertEqual(self.clearing_equal.state, "done")
- self.assertEqual(self.clearing_equal.advance_sheet_residual, 0.0)
- # Clear this with previous advance is done
- self.clearing_more.advance_sheet_id = self.advance
- self.clearing_more.action_submit_sheet()
- self.clearing_more.approve_expense_sheets()
- with self.assertRaises(ValidationError):
- self.clearing_more.action_sheet_move_create()
- # There are 2 clearing in advance
- self.assertEqual(self.advance.clearing_count, 2)
- # Check link clearing in advance must be equal clearing count
- clearing_dict = self.advance.action_open_clearings()
- self.assertEqual(
- len(clearing_dict["domain"][0][2]), self.advance.clearing_count
- )
- # Check advance from employee
- self.assertEqual(self.expense_employee.advance_count, 1)
- clearing_document = self.expense_employee.action_open_advance_clearing()
- self.assertEqual(
- len(clearing_document["domain"][0][2]), self.expense_employee.advance_count
- )
- # Check back state move in advance after create clearing
- with self.assertRaises(UserError):
- self.advance.account_move_id.button_draft()
- with self.assertRaises(UserError):
- self.advance.account_move_id.button_cancel()
- with self.assertRaises(UserError):
- self.advance.account_move_id._reverse_moves()
+ def test_clearing_residual_tracks_drafts_vs_approved(self):
+ """clearing_residual excludes draft/submitted/refused clearings;
+ only approved+ clearings count."""
+ advance = self._new_advance(1000.0)
+ c_draft = self._new_clearing(advance, 300.0)
+ self.assertEqual(c_draft.state, "draft")
+ self.assertEqual(advance.cleared_amount, 0.0)
+ self.assertEqual(advance.clearing_residual, 1000.0)
+ c_draft.action_submit()
+ if c_draft.state == "submitted":
+ c_draft.action_approve()
+ advance.invalidate_recordset(["cleared_amount", "clearing_residual"])
+ self.assertEqual(advance.cleared_amount, 300.0)
+ self.assertEqual(advance.clearing_residual, 700.0)
- @mute_logger("odoo.models.unlink")
- def test_2_clear_more_than_advance(self):
- """ When clear more than advance, do pay more """
- # ------------------ Advance --------------------------
- self.advance.action_submit_sheet()
- self.advance.approve_expense_sheets()
- self.advance.action_sheet_move_create()
- self.assertEqual(self.advance.clearing_residual, 1000.0)
- self._register_payment(self.advance)
- self.assertEqual(self.advance.state, "done")
- # ------------------ Clearing --------------------------
- # Clear this with previous advance
- self.clearing_more.advance_sheet_id = self.advance
- self.assertEqual(self.clearing_more.advance_sheet_residual, 1000.0)
- self.clearing_more.action_submit_sheet()
- self.clearing_more.approve_expense_sheets()
- self.clearing_more.action_sheet_move_create()
- # More amount, state not changed to paid, and has to pay 200 more
- self.assertEqual(self.clearing_more.state, "post")
- self.assertEqual(self.clearing_more.amount_payable, 200.0)
- self._register_payment(self.clearing_more)
- self.assertEqual(self.clearing_more.state, "done")
+ def test_amount_payable_on_clearing(self):
+ """A clearing expense's amount_payable = max(total − advance_residual, 0)."""
+ advance = self._new_advance(1000.0)
+ c_eq = self._new_clearing(advance, 1000.0)
+ self.assertEqual(c_eq.amount_payable, 0.0)
+ c_gt = self._new_clearing(advance, 1500.0)
+ self.assertEqual(c_gt.amount_payable, 500.0)
+ c_lt = self._new_clearing(advance, 500.0)
+ self.assertEqual(c_lt.amount_payable, 0.0)
- @mute_logger("odoo.models.unlink")
- def test_3_clear_less_than_advance(self):
- """ When clear less than advance, do return advance """
- # ------------------ Advance --------------------------
- self.advance.action_submit_sheet()
- self.advance.approve_expense_sheets()
- self.advance.action_sheet_move_create()
- self.assertEqual(self.advance.clearing_residual, 1000.0)
- self._register_payment(self.advance)
- self.assertEqual(self.advance.state, "done")
- # ------------------ Clearing --------------------------
- # Clear this with previous advance
- self.clearing_less.advance_sheet_id = self.advance
- self.assertEqual(self.clearing_less.advance_sheet_residual, 1000.0)
- self.clearing_less.action_submit_sheet()
- self.clearing_less.approve_expense_sheets()
- self.clearing_less.action_sheet_move_create()
- # Less amount, state set to done. Still remain 200 to be returned
- self.assertEqual(self.clearing_less.state, "done")
- self.assertEqual(self.clearing_less.advance_sheet_residual, 200.0)
- # Back to advance and do return advance, clearing residual become 0.0
- self._register_payment(self.advance, hr_return_advance=True)
- self.assertEqual(self.advance.clearing_residual, 0.0)
- # Check payment of return advance
- payment = self.env["account.payment"].search(
- [("advance_id", "=", self.advance.id)]
- )
- self.assertEqual(len(payment), 1)
+ def test_employee_advance_count(self):
+ """advance_count + advance_expense_ids on hr.employee."""
+ self.assertEqual(self.expense_employee.advance_count, 0)
+ self._new_advance(500.0)
+ self.expense_employee.invalidate_recordset(["advance_count"])
+ self.assertEqual(self.expense_employee.advance_count, 1)
- @mute_logger("odoo.models.unlink")
- def test_4_clearing_product_advance(self):
- """When select clearing product on advance"""
- # ------------------ Advance --------------------------
- self.advance.expense_line_ids.clearing_product_id = self.product_a
- self.advance.action_submit_sheet()
- self.advance.approve_expense_sheets()
- self.advance.action_sheet_move_create()
- self.assertEqual(self.advance.clearing_residual, 1000.0)
- self._register_payment(self.advance.account_move_id, 1000.0)
- self.assertEqual(self.advance.state, "done")
- # ------------------ Clearing --------------------------
- with Form(self.env["hr.expense.sheet"]) as sheet:
- sheet.name = "Test Clearing"
- sheet.employee_id = self.expense_employee
- ex_sheet = sheet.save()
- ex_sheet.advance_sheet_id = self.advance
- self.assertEqual(len(ex_sheet.expense_line_ids), 0)
- ex_sheet._onchange_advance_sheet_id()
- self.assertEqual(len(ex_sheet.expense_line_ids), 1)
- reverse_move = self.advance.account_move_id._reverse_moves(
- default_values_list=[
- {"invoice_date": self.advance.account_move_id.date, "ref": False}
- ],
- cancel=True,
+ def test_action_return_advance_guards(self):
+ """action_return_advance gates on expense_type + residual, and
+ returns the payment-register wizard action when valid."""
+ advance = self._new_advance(500.0)
+ # A regular expense cannot be returned.
+ regular = self.env["hr.expense"].create(
+ {
+ "name": "regular",
+ "employee_id": self.expense_employee.id,
+ "product_id": self.product_a.id,
+ "total_amount_currency": 50.0,
+ "payment_mode": "own_account",
+ }
)
- self.assertNotEqual(reverse_move, self.advance.account_move_id)
+ with self.assertRaises(UserError):
+ regular.action_return_advance()
+ # The advance has residual → action returns the wizard.
+ self.assertGreater(advance.clearing_residual, 0)
+ action = advance.action_return_advance()
+ self.assertEqual(action["res_model"], "account.payment.register")
+ self.assertEqual(action["context"]["default_advance_id"], advance.id)
+ self.assertTrue(action["context"].get("hr_return_advance"))
+ # Once fully cleared, residual is zero → action raises.
+ clearing = self._new_clearing(advance, 500.0)
+ clearing.action_submit()
+ if clearing.state == "submitted":
+ clearing.action_approve()
+ advance.invalidate_recordset(["cleared_amount", "clearing_residual"])
+ self.assertEqual(advance.clearing_residual, 0.0)
+ with self.assertRaises(UserError):
+ advance.action_return_advance()
diff --git a/hr_expense_advance_clearing/views/account_payment_view.xml b/hr_expense_advance_clearing/views/account_payment_view.xml
new file mode 100644
index 000000000..849fad05e
--- /dev/null
+++ b/hr_expense_advance_clearing/views/account_payment_view.xml
@@ -0,0 +1,29 @@
+
+
+
+ account.payment.search
+ account.payment
+
+
+
+
+
+
+
+
+
+
+ account.payment.form
+ account.payment
+
+
+
+
+
+
+
+
diff --git a/hr_expense_advance_clearing/views/hr_employee_views.xml b/hr_expense_advance_clearing/views/hr_employee_views.xml
index b558ac1f2..ed872da9b 100644
--- a/hr_expense_advance_clearing/views/hr_employee_views.xml
+++ b/hr_expense_advance_clearing/views/hr_employee_views.xml
@@ -10,7 +10,7 @@
name="action_open_advance_clearing"
type="object"
icon="fa-bars"
- attrs="{'invisible': [('advance_count', '=', 0)]}"
+ invisible="advance_count == 0"
>
diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml
index 1fe1dc6ff..80be553a7 100644
--- a/hr_expense_advance_clearing/views/hr_expense_views.xml
+++ b/hr_expense_advance_clearing/views/hr_expense_views.xml
@@ -1,60 +1,7 @@
+
-
-
-
-
- Advances
- hr.expense.sheet
- tree,kanban,form,pivot,graph,activity
-
- [('state', '!=', 'cancel'), ('advance', '=', True)]
- {'search_default_my_reports': 1, 'default_advance': True}
-
-
- No advance report found. Let's create one!
-
- Once you have created your advance, submit it to your manager who will validate it.
-
-
-
-
- Expenses
- hr.expense.sheet
- tree,kanban,form,pivot,graph,activity
-
- [('state', '!=', 'cancel'), ('advance', '=', False)]
- {'search_default_my_reports': 1}
-
-
- No expense report found. Let's create one!
-
- Once you have created your expense, submit it to your manager who will validate it.
-
-
-
-
-
-
-
-
- hr.expense.view.search
+ hr.expense.view.search - advance_clearing
hr.expense
@@ -62,200 +9,89 @@
-
-
-
-
-
- hr.expense.sheet.view.search
- hr.expense.sheet
-
-
-
-
-
- Advance Clearing
- hr.expense.sheet
- form
-
-
- [('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')]
-
- {'search_default_my_reports': 1}
-
-
- Create a new expense report
-
-
- Once you have created your expense, submit it to your manager who will validate it.
-
-
-
-
- hr.expense.form
+
+
+ hr.expense.view.form - advance_clearing
hr.expense
-
-
+
{'readonly': [('advance', '=', True)]}
+ name="readonly"
+ >state != 'draft' or expense_type == 'advance'
1
-
-
- {'invisible': [('advance', '!=', False)]}
-
-
- {'invisible': [('advance', '!=', False)]}
-
-
-
-
- hr.expense.sheet.tree
- hr.expense.sheet
-
-
-
-
-
-
-
-
-
-
-
- view.hr.expense.sheet.form
- hr.expense.sheet
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {'default_advance': advance, 'search_default_is_advance': advance, 'search_default_is_expense': not advance, 'form_view_ref' : 'hr_expense.hr_expense_view_form_without_header', 'default_company_id': company_id, 'default_employee_id': employee_id}
- {'readonly': [('id', '=', False), ('advance_sheet_id', '!=', False)]}
- True
-
-
- True
-
+
+
+ expense_type == 'advance'
+
-
-
+
+
+
+
+
+
+
+
diff --git a/hr_expense_advance_clearing/wizard/__init__.py b/hr_expense_advance_clearing/wizard/__init__.py
index f41f99dba..09e22cb07 100644
--- a/hr_expense_advance_clearing/wizard/__init__.py
+++ b/hr_expense_advance_clearing/wizard/__init__.py
@@ -1 +1,3 @@
-from . import hr_expense_sheet_register_payment
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from . import account_payment_register
diff --git a/hr_expense_advance_clearing/wizard/account_payment_register.py b/hr_expense_advance_clearing/wizard/account_payment_register.py
new file mode 100644
index 000000000..bb6c430f7
--- /dev/null
+++ b/hr_expense_advance_clearing/wizard/account_payment_register.py
@@ -0,0 +1,64 @@
+# Copyright 2020 Ecosoft Co., Ltd (https://ecosoft.co.th/)
+# Copyright 2026 Ledo
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
+
+
+from odoo import models
+from odoo.exceptions import UserError
+from odoo.tools import float_compare
+
+
+class AccountPaymentRegister(models.TransientModel):
+ _inherit = "account.payment.register"
+
+ def _validate_over_return(self):
+ """For a return-advance wizard: cannot return more than the actual
+ remaining residual (advance.clearing_residual minus any pending
+ clearings that haven't been posted yet)."""
+ clearings = (
+ self.env["hr.expense"]
+ .browse(self.env.context.get("clearing_expense_ids", []))
+ .filtered(lambda e: e.state == "approved")
+ )
+ amount_not_clear = sum(clearings.mapped("total_amount"))
+ actual_remaining = self.source_amount_currency - amount_not_clear
+ symbol = self.source_currency_id.symbol
+ more_info = ""
+ if amount_not_clear:
+ note = self.env._("\nNote: pending amount clearing is %(symbol)s%(amount)s")
+ more_info = note % {
+ "symbol": symbol,
+ "amount": f"{amount_not_clear:,.2f}",
+ }
+ if float_compare(self.amount, actual_remaining, 2) == 1:
+ msg = self.env._(
+ "You cannot return advance more than actual remaining "
+ "(%(symbol)s%(amount)s)%(more_info)s"
+ )
+ raise UserError(
+ msg
+ % {
+ "symbol": symbol,
+ "amount": f"{actual_remaining:,.2f}",
+ "more_info": more_info,
+ }
+ )
+
+ def _init_payments(self, to_process, edit_mode=False):
+ if self.env.context.get("hr_return_advance"):
+ self._validate_over_return()
+ advance_id = self.env.context.get("default_advance_id")
+ for x in to_process:
+ x["create_vals"]["partner_type"] = "customer"
+ if advance_id:
+ x["create_vals"]["advance_id"] = advance_id
+ return super()._init_payments(to_process, edit_mode)
+
+ def _create_payments(self):
+ """For a clearing-payment where the clearing exceeds the advance,
+ mark the payment as paid so the residual is recomputed."""
+ payments = super()._create_payments()
+ if self.env.context.get("expense_clearing"):
+ for payment in payments:
+ payment.write({"move_id": payment.move_id.id, "state": "paid"})
+ return payments
diff --git a/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py b/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
deleted file mode 100644
index da975bc71..000000000
--- a/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright 2019 Kitti Upariphutthiphong
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-from werkzeug.urls import url_encode
-
-from odoo import _, api, models
-
-
-class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):
- _inherit = "hr.expense.sheet.register.payment.wizard"
-
- @api.model
- def default_get(self, field_list):
- res = super().default_get(field_list)
- ctx = self._context.copy()
- sheet = self.env["hr.expense.sheet"].browse(ctx.get("active_id"))
- if ctx.get("hr_return_advance", False):
- res.update({"amount": sheet.clearing_residual})
- else:
- res.update({"amount": sheet.amount_payable})
- return res
-
- def expense_post_payment(self):
- if self._context.get("hr_return_advance", False):
- return self.expense_post_return_advance()
- return super().expense_post_payment()
-
- def expense_post_return_advance(self):
- """ This is opposite operation of expense_post_payment(),
- it return remaining advance from employee back to company
- """
- self.ensure_one()
- context = dict(self._context or {})
- active_ids = context.get("active_ids", [])
- expense_sheet = self.env["hr.expense.sheet"].browse(active_ids)
- emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance")
- advance_account = emp_advance.property_account_expense_id
-
- # Create return advance and post it
- payment = self.env["account.payment"].create(self._get_payment_vals())
- # Set new payment_type and payment entry to be Dr Bank, Cr Advance
- payment.payment_type = "inbound"
- payment.partner_type = "customer"
- payment.destination_account_id = advance_account
- payment.post()
-
- # Log the return advance in the chatter
- body = _(
- "A remaining advance return of %s %s with the reference "
- "%s related to your expense %s "
- "has been made."
- ) % (
- payment.amount,
- payment.currency_id.symbol,
- url_encode({"model": "account.payment", "res_id": payment.id}),
- payment.name,
- expense_sheet.name,
- )
- expense_sheet.message_post(body=body)
-
- # Reconcile the return advance and the advance,
- # i.e. lookup on the advance account on move lines
- account_move_lines_to_reconcile = self.env["account.move.line"]
- for line in payment.move_line_ids + expense_sheet.account_move_id.line_ids:
- if line.account_id == advance_account:
- account_move_lines_to_reconcile |= line
- account_move_lines_to_reconcile.reconcile()
-
- return {"type": "ir.actions.act_window_close"}
From dc95b50e1bc27c893b6d347c5c03d895391497e7 Mon Sep 17 00:00:00 2001
From: Don Kendall
Date: Fri, 19 Jun 2026 09:25:53 -0400
Subject: [PATCH 73/75] [ADD] hr_expense_payment_trip: pay a whole trip in one
payment (prototype)
Bridge module (auto_install) depending on hr_expense_payment + hr_expense_trip.
Adds a 'Pay Trip' action that registers a single payment over the trip's posted,
unpaid expenses via the standard expense register-payment flow.
---
hr_expense_payment_trip/README.rst | 105 ++++
hr_expense_payment_trip/__init__.py | 1 +
hr_expense_payment_trip/__manifest__.py | 18 +
hr_expense_payment_trip/models/__init__.py | 1 +
hr_expense_payment_trip/models/hr_trip.py | 42 ++
hr_expense_payment_trip/pyproject.toml | 3 +
.../readme/CONTRIBUTORS.md | 3 +
hr_expense_payment_trip/readme/DESCRIPTION.md | 3 +
hr_expense_payment_trip/readme/USAGE.md | 3 +
.../static/description/index.html | 450 ++++++++++++++++++
hr_expense_payment_trip/tests/__init__.py | 1 +
.../tests/test_hr_expense_payment_trip.py | 54 +++
.../views/hr_trip_views.xml | 23 +
13 files changed, 707 insertions(+)
create mode 100644 hr_expense_payment_trip/README.rst
create mode 100644 hr_expense_payment_trip/__init__.py
create mode 100644 hr_expense_payment_trip/__manifest__.py
create mode 100644 hr_expense_payment_trip/models/__init__.py
create mode 100644 hr_expense_payment_trip/models/hr_trip.py
create mode 100644 hr_expense_payment_trip/pyproject.toml
create mode 100644 hr_expense_payment_trip/readme/CONTRIBUTORS.md
create mode 100644 hr_expense_payment_trip/readme/DESCRIPTION.md
create mode 100644 hr_expense_payment_trip/readme/USAGE.md
create mode 100644 hr_expense_payment_trip/static/description/index.html
create mode 100644 hr_expense_payment_trip/tests/__init__.py
create mode 100644 hr_expense_payment_trip/tests/test_hr_expense_payment_trip.py
create mode 100644 hr_expense_payment_trip/views/hr_trip_views.xml
diff --git a/hr_expense_payment_trip/README.rst b/hr_expense_payment_trip/README.rst
new file mode 100644
index 000000000..0b04843d3
--- /dev/null
+++ b/hr_expense_payment_trip/README.rst
@@ -0,0 +1,105 @@
+.. image:: https://odoo-community.org/readme-banner-image
+ :target: https://odoo-community.org/get-involved?utm_source=readme
+ :alt: Odoo Community Association
+
+=======================
+HR Expense Payment Trip
+=======================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:5555e97ad3afa3ba869d7220b29db539698c672faf7c497e37ba96488620f48b
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Alpha
+.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--expense-lightgray.png?logo=github
+ :target: https://github.com/OCA/hr-expense/tree/19.0/hr_expense_payment_trip
+ :alt: OCA/hr-expense
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/hr-expense-19-0/hr-expense-19-0-hr_expense_payment_trip
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/hr-expense&target_branch=19.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+Adds a **Pay Trip** action on a trip that registers a single payment
+covering all of the trip's posted, unpaid expenses, instead of paying
+each expense one by one.
+
+.. IMPORTANT::
+ This is an alpha version, the data model and design can change at any time without warning.
+ Only for development or testing purpose, do not use in production.
+ `More details on development status `_
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Usage
+=====
+
+Open a trip whose expenses have been posted (via *Create Bill*) and
+click **Pay Trip**. The standard expense register-payment wizard opens
+for the whole batch; confirming it creates one payment linked back to
+the expenses.
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+-------
+
+* Ledo
+
+Contributors
+------------
+
+- Ledo
+
+ - Don Kendall
+
+Maintainers
+-----------
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-dnplkndll| image:: https://github.com/dnplkndll.png?size=40px
+ :target: https://github.com/dnplkndll
+ :alt: dnplkndll
+
+Current `maintainer `__:
+
+|maintainer-dnplkndll|
+
+This module is part of the `OCA/hr-expense `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/hr_expense_payment_trip/__init__.py b/hr_expense_payment_trip/__init__.py
new file mode 100644
index 000000000..0650744f6
--- /dev/null
+++ b/hr_expense_payment_trip/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/hr_expense_payment_trip/__manifest__.py b/hr_expense_payment_trip/__manifest__.py
new file mode 100644
index 000000000..658c47dca
--- /dev/null
+++ b/hr_expense_payment_trip/__manifest__.py
@@ -0,0 +1,18 @@
+# Copyright 2026 Ledo
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+{
+ "name": "HR Expense Payment Trip",
+ "version": "19.0.1.0.0",
+ "category": "Human Resources",
+ "summary": "Pay all of a trip's expenses in a single payment",
+ "author": "Ledo, Odoo Community Association (OCA)",
+ "maintainers": ["dnplkndll"],
+ "license": "AGPL-3",
+ "development_status": "Alpha",
+ "website": "https://github.com/OCA/hr-expense",
+ "depends": ["hr_expense_payment", "hr_expense_trip"],
+ "data": ["views/hr_trip_views.xml"],
+ "installable": True,
+ "auto_install": True,
+}
diff --git a/hr_expense_payment_trip/models/__init__.py b/hr_expense_payment_trip/models/__init__.py
new file mode 100644
index 000000000..4692f2534
--- /dev/null
+++ b/hr_expense_payment_trip/models/__init__.py
@@ -0,0 +1 @@
+from . import hr_trip
diff --git a/hr_expense_payment_trip/models/hr_trip.py b/hr_expense_payment_trip/models/hr_trip.py
new file mode 100644
index 000000000..3c19dd33e
--- /dev/null
+++ b/hr_expense_payment_trip/models/hr_trip.py
@@ -0,0 +1,42 @@
+# Copyright 2026 Ledo
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from odoo import api, fields, models
+from odoo.exceptions import UserError
+
+
+class HrTrip(models.Model):
+ _inherit = "hr.trip"
+
+ payable_expense_count = fields.Integer(
+ compute="_compute_payable_expense_count",
+ help="Posted, not fully paid expenses linked to the trip.",
+ )
+
+ @api.depends(
+ "expense_ids.account_move_id.payment_state",
+ "expense_ids.account_move_id.state",
+ )
+ def _compute_payable_expense_count(self):
+ for trip in self:
+ trip.payable_expense_count = len(trip._payable_expenses())
+
+ def _payable_expenses(self):
+ """Trip expenses with a posted move that is not yet fully paid."""
+ self.ensure_one()
+ return self.expense_ids.filtered(
+ lambda expense: expense.account_move_id.state == "posted"
+ and expense.account_move_id.payment_state
+ in ("not_paid", "partial", "in_payment")
+ )
+
+ def action_pay_trip(self):
+ """Register a single payment covering the trip's payable expenses by
+ threading them through the standard expense register-payment flow."""
+ self.ensure_one()
+ payable = self._payable_expenses()
+ if not payable:
+ raise UserError(
+ self.env._("No posted, unpaid expenses to pay on this trip.")
+ )
+ return payable.action_pay()
diff --git a/hr_expense_payment_trip/pyproject.toml b/hr_expense_payment_trip/pyproject.toml
new file mode 100644
index 000000000..4231d0ccc
--- /dev/null
+++ b/hr_expense_payment_trip/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["whool"]
+build-backend = "whool.buildapi"
diff --git a/hr_expense_payment_trip/readme/CONTRIBUTORS.md b/hr_expense_payment_trip/readme/CONTRIBUTORS.md
new file mode 100644
index 000000000..df56e4bb3
--- /dev/null
+++ b/hr_expense_payment_trip/readme/CONTRIBUTORS.md
@@ -0,0 +1,3 @@
+* Ledo
+
+ * Don Kendall
diff --git a/hr_expense_payment_trip/readme/DESCRIPTION.md b/hr_expense_payment_trip/readme/DESCRIPTION.md
new file mode 100644
index 000000000..f7ce700bd
--- /dev/null
+++ b/hr_expense_payment_trip/readme/DESCRIPTION.md
@@ -0,0 +1,3 @@
+Adds a **Pay Trip** action on a trip that registers a single payment
+covering all of the trip's posted, unpaid expenses, instead of paying each
+expense one by one.
diff --git a/hr_expense_payment_trip/readme/USAGE.md b/hr_expense_payment_trip/readme/USAGE.md
new file mode 100644
index 000000000..1a86dee4e
--- /dev/null
+++ b/hr_expense_payment_trip/readme/USAGE.md
@@ -0,0 +1,3 @@
+Open a trip whose expenses have been posted (via *Create Bill*) and click
+**Pay Trip**. The standard expense register-payment wizard opens for the whole
+batch; confirming it creates one payment linked back to the expenses.
diff --git a/hr_expense_payment_trip/static/description/index.html b/hr_expense_payment_trip/static/description/index.html
new file mode 100644
index 000000000..15734e987
--- /dev/null
+++ b/hr_expense_payment_trip/static/description/index.html
@@ -0,0 +1,450 @@
+
+
+
+
+
+HR Expense Payment Trip
+
+
+
+
+
+
+
+
+
+
+
HR Expense Payment Trip
+
+
+
Adds a Pay Trip action on a trip that registers a single payment
+covering all of the trip’s posted, unpaid expenses, instead of paying
+each expense one by one.
+
+
Important
+
This is an alpha version, the data model and design can change at any time without warning.
+Only for development or testing purpose, do not use in production.
+More details on development status
+
+
Table of contents
+
+
+
+
Open a trip whose expenses have been posted (via Create Bill ) and
+click Pay Trip . The standard expense register-payment wizard opens
+for the whole batch; confirming it creates one payment linked back to
+the expenses.
+
+
+
+
Bugs are tracked on GitHub Issues .
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+feedback .
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
+
+
+
+
+
This module is maintained by the OCA.
+
+
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
Current maintainer :
+
+
This module is part of the OCA/hr-expense project on GitHub.
+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute .
+
+
+
+
+
+
diff --git a/hr_expense_payment_trip/tests/__init__.py b/hr_expense_payment_trip/tests/__init__.py
new file mode 100644
index 000000000..6a73aa4db
--- /dev/null
+++ b/hr_expense_payment_trip/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_hr_expense_payment_trip
diff --git a/hr_expense_payment_trip/tests/test_hr_expense_payment_trip.py b/hr_expense_payment_trip/tests/test_hr_expense_payment_trip.py
new file mode 100644
index 000000000..74d42dae1
--- /dev/null
+++ b/hr_expense_payment_trip/tests/test_hr_expense_payment_trip.py
@@ -0,0 +1,54 @@
+# Copyright 2026 Ledo
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from datetime import date
+from unittest.mock import patch
+
+from odoo.exceptions import UserError
+from odoo.tests.common import TransactionCase
+
+
+class TestHrExpensePaymentTrip(TransactionCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.employee = cls.env["hr.employee"].create({"name": "Trip Payer"})
+ cls.trip = cls.env["hr.trip"].create(
+ {
+ "name": "Berlin",
+ "start_date": date(2026, 1, 1),
+ "end_date": date(2026, 1, 5),
+ "employee_id": cls.employee.id,
+ }
+ )
+ cls.expense = cls.env["hr.expense"].create(
+ {
+ "name": "Hotel",
+ "employee_id": cls.employee.id,
+ "date": date(2026, 1, 2),
+ "total_amount": 100.0,
+ "trip_id": cls.trip.id,
+ }
+ )
+
+ def test_no_payable_expenses_raises(self):
+ """Nothing posted/payable yet -> a clear error, not a silent no-op."""
+ self.assertEqual(self.trip.payable_expense_count, 0)
+ with self.assertRaises(UserError):
+ self.trip.action_pay_trip()
+
+ def test_pay_trip_delegates_to_expense_action_pay(self):
+ """A single payment flow is delegated to the trip's payable expenses."""
+ with (
+ patch.object(
+ type(self.trip), "_payable_expenses", return_value=self.expense
+ ),
+ patch.object(
+ type(self.expense),
+ "action_pay",
+ return_value={"type": "ir.actions.act_window"},
+ ) as mocked_pay,
+ ):
+ action = self.trip.action_pay_trip()
+ mocked_pay.assert_called_once()
+ self.assertEqual(action["type"], "ir.actions.act_window")
diff --git a/hr_expense_payment_trip/views/hr_trip_views.xml b/hr_expense_payment_trip/views/hr_trip_views.xml
new file mode 100644
index 000000000..56e57729a
--- /dev/null
+++ b/hr_expense_payment_trip/views/hr_trip_views.xml
@@ -0,0 +1,23 @@
+
+
+
+ hr.trip.view.form - payment
+ hr.trip
+
+
+
+
+
+
+
+
+
+
+
From 97bb6af121fd51f189f9650d3eaf684e45362ff1 Mon Sep 17 00:00:00 2001
From: Don Kendall
Date: Fri, 19 Jun 2026 09:25:55 -0400
Subject: [PATCH 74/75] [ADD] hr_expense_advance_clearing_trip: tie an advance
to a trip (prototype)
Bridge module (auto_install) depending on hr_expense_advance_clearing +
hr_expense_trip. Adds a Trip Advance on the trip and an 'Apply Advance' action
that clears the trip's regular expenses against it in one step.
---
hr_expense_advance_clearing_trip/README.rst | 105 ++++
hr_expense_advance_clearing_trip/__init__.py | 1 +
.../__manifest__.py | 18 +
.../models/__init__.py | 1 +
.../models/hr_trip.py | 53 +++
.../pyproject.toml | 3 +
.../readme/CONTRIBUTORS.md | 3 +
.../readme/DESCRIPTION.md | 3 +
.../readme/USAGE.md | 4 +
.../static/description/index.html | 450 ++++++++++++++++++
.../tests/__init__.py | 1 +
.../test_hr_expense_advance_clearing_trip.py | 75 +++
.../views/hr_trip_views.xml | 28 ++
13 files changed, 745 insertions(+)
create mode 100644 hr_expense_advance_clearing_trip/README.rst
create mode 100644 hr_expense_advance_clearing_trip/__init__.py
create mode 100644 hr_expense_advance_clearing_trip/__manifest__.py
create mode 100644 hr_expense_advance_clearing_trip/models/__init__.py
create mode 100644 hr_expense_advance_clearing_trip/models/hr_trip.py
create mode 100644 hr_expense_advance_clearing_trip/pyproject.toml
create mode 100644 hr_expense_advance_clearing_trip/readme/CONTRIBUTORS.md
create mode 100644 hr_expense_advance_clearing_trip/readme/DESCRIPTION.md
create mode 100644 hr_expense_advance_clearing_trip/readme/USAGE.md
create mode 100644 hr_expense_advance_clearing_trip/static/description/index.html
create mode 100644 hr_expense_advance_clearing_trip/tests/__init__.py
create mode 100644 hr_expense_advance_clearing_trip/tests/test_hr_expense_advance_clearing_trip.py
create mode 100644 hr_expense_advance_clearing_trip/views/hr_trip_views.xml
diff --git a/hr_expense_advance_clearing_trip/README.rst b/hr_expense_advance_clearing_trip/README.rst
new file mode 100644
index 000000000..0761323d6
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/README.rst
@@ -0,0 +1,105 @@
+.. image:: https://odoo-community.org/readme-banner-image
+ :target: https://odoo-community.org/get-involved?utm_source=readme
+ :alt: Odoo Community Association
+
+================================
+HR Expense Advance Clearing Trip
+================================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:a8ca889f8be002a0d3fb73711695845e611500f8267cf9d930d04f453d7c5821
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Alpha
+.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--expense-lightgray.png?logo=github
+ :target: https://github.com/OCA/hr-expense/tree/19.0/hr_expense_advance_clearing_trip
+ :alt: OCA/hr-expense
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/hr-expense-19-0/hr-expense-19-0-hr_expense_advance_clearing_trip
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/hr-expense&target_branch=19.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+Links an employee **advance** to a **trip** so the trip's expenses can
+be cleared against that advance in one step, rather than setting the
+advance on each expense line individually.
+
+.. IMPORTANT::
+ This is an alpha version, the data model and design can change at any time without warning.
+ Only for development or testing purpose, do not use in production.
+ `More details on development status `_
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Usage
+=====
+
+On a trip, set the **Trip Advance** (an advance-type expense for the
+same employee) and click **Apply Advance**. Every regular expense on the
+trip that is not yet linked to an advance is cleared against the trip
+advance. The **Advance Remaining** field shows the residual to clear.
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+-------
+
+* Ledo
+
+Contributors
+------------
+
+- Ledo
+
+ - Don Kendall
+
+Maintainers
+-----------
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-dnplkndll| image:: https://github.com/dnplkndll.png?size=40px
+ :target: https://github.com/dnplkndll
+ :alt: dnplkndll
+
+Current `maintainer `__:
+
+|maintainer-dnplkndll|
+
+This module is part of the `OCA/hr-expense `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/hr_expense_advance_clearing_trip/__init__.py b/hr_expense_advance_clearing_trip/__init__.py
new file mode 100644
index 000000000..0650744f6
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/hr_expense_advance_clearing_trip/__manifest__.py b/hr_expense_advance_clearing_trip/__manifest__.py
new file mode 100644
index 000000000..2868b52df
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/__manifest__.py
@@ -0,0 +1,18 @@
+# Copyright 2026 Ledo
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+{
+ "name": "HR Expense Advance Clearing Trip",
+ "version": "19.0.1.0.0",
+ "category": "Human Resources",
+ "summary": "Tie an employee advance to a trip and clear the trip's expenses",
+ "author": "Ledo, Odoo Community Association (OCA)",
+ "maintainers": ["dnplkndll"],
+ "license": "AGPL-3",
+ "development_status": "Alpha",
+ "website": "https://github.com/OCA/hr-expense",
+ "depends": ["hr_expense_advance_clearing", "hr_expense_trip"],
+ "data": ["views/hr_trip_views.xml"],
+ "installable": True,
+ "auto_install": True,
+}
diff --git a/hr_expense_advance_clearing_trip/models/__init__.py b/hr_expense_advance_clearing_trip/models/__init__.py
new file mode 100644
index 000000000..4692f2534
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/models/__init__.py
@@ -0,0 +1 @@
+from . import hr_trip
diff --git a/hr_expense_advance_clearing_trip/models/hr_trip.py b/hr_expense_advance_clearing_trip/models/hr_trip.py
new file mode 100644
index 000000000..55b90e699
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/models/hr_trip.py
@@ -0,0 +1,53 @@
+# Copyright 2026 Ledo
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from odoo import api, fields, models
+from odoo.exceptions import UserError
+
+
+class HrTrip(models.Model):
+ _inherit = "hr.trip"
+
+ advance_id = fields.Many2one(
+ comodel_name="hr.expense",
+ string="Trip Advance",
+ domain="[('expense_type', '=', 'advance'), ('employee_id', '=', employee_id)]",
+ help="Advance paid to the employee for this trip. Use 'Apply Advance' "
+ "to clear the trip's expenses against it.",
+ )
+ advance_residual = fields.Monetary(
+ related="advance_id.clearing_residual",
+ string="Advance Remaining",
+ )
+ currency_id = fields.Many2one(related="advance_id.currency_id")
+
+ @api.onchange("employee_id")
+ def _onchange_employee_clear_advance(self):
+ # The advance must belong to the trip's employee.
+ if self.advance_id and self.advance_id.employee_id != self.employee_id:
+ self.advance_id = False
+
+ def action_apply_advance(self):
+ """Clear the trip's regular expenses against the trip advance, by
+ setting clearing_advance_id on each one that is not already cleared."""
+ self.ensure_one()
+ if not self.advance_id:
+ raise UserError(self.env._("Set a Trip Advance first."))
+ clearings = self.expense_ids.filtered(
+ lambda expense: expense.expense_type == "expense"
+ and not expense.clearing_advance_id
+ )
+ if not clearings:
+ raise UserError(
+ self.env._("No unlinked regular expenses to clear on this trip.")
+ )
+ clearings.write({"clearing_advance_id": self.advance_id.id})
+
+ def action_view_advance(self):
+ self.ensure_one()
+ return {
+ "type": "ir.actions.act_window",
+ "res_model": "hr.expense",
+ "res_id": self.advance_id.id,
+ "view_mode": "form",
+ }
diff --git a/hr_expense_advance_clearing_trip/pyproject.toml b/hr_expense_advance_clearing_trip/pyproject.toml
new file mode 100644
index 000000000..4231d0ccc
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["whool"]
+build-backend = "whool.buildapi"
diff --git a/hr_expense_advance_clearing_trip/readme/CONTRIBUTORS.md b/hr_expense_advance_clearing_trip/readme/CONTRIBUTORS.md
new file mode 100644
index 000000000..df56e4bb3
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/readme/CONTRIBUTORS.md
@@ -0,0 +1,3 @@
+* Ledo
+
+ * Don Kendall
diff --git a/hr_expense_advance_clearing_trip/readme/DESCRIPTION.md b/hr_expense_advance_clearing_trip/readme/DESCRIPTION.md
new file mode 100644
index 000000000..f922400a2
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/readme/DESCRIPTION.md
@@ -0,0 +1,3 @@
+Links an employee **advance** to a **trip** so the trip's expenses can be
+cleared against that advance in one step, rather than setting the advance on
+each expense line individually.
diff --git a/hr_expense_advance_clearing_trip/readme/USAGE.md b/hr_expense_advance_clearing_trip/readme/USAGE.md
new file mode 100644
index 000000000..3875a51d2
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/readme/USAGE.md
@@ -0,0 +1,4 @@
+On a trip, set the **Trip Advance** (an advance-type expense for the same
+employee) and click **Apply Advance**. Every regular expense on the trip that
+is not yet linked to an advance is cleared against the trip advance. The
+**Advance Remaining** field shows the residual to clear.
diff --git a/hr_expense_advance_clearing_trip/static/description/index.html b/hr_expense_advance_clearing_trip/static/description/index.html
new file mode 100644
index 000000000..af19b16de
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/static/description/index.html
@@ -0,0 +1,450 @@
+
+
+
+
+
+HR Expense Advance Clearing Trip
+
+
+
+
+
+
+
+
+
+
+
HR Expense Advance Clearing Trip
+
+
+
Links an employee advance to a trip so the trip’s expenses can
+be cleared against that advance in one step, rather than setting the
+advance on each expense line individually.
+
+
Important
+
This is an alpha version, the data model and design can change at any time without warning.
+Only for development or testing purpose, do not use in production.
+More details on development status
+
+
Table of contents
+
+
+
+
On a trip, set the Trip Advance (an advance-type expense for the
+same employee) and click Apply Advance . Every regular expense on the
+trip that is not yet linked to an advance is cleared against the trip
+advance. The Advance Remaining field shows the residual to clear.
+
+
+
+
Bugs are tracked on GitHub Issues .
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+feedback .
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
+
+
+
+
+
This module is maintained by the OCA.
+
+
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
Current maintainer :
+
+
This module is part of the OCA/hr-expense project on GitHub.
+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute .
+
+
+
+
+
+
diff --git a/hr_expense_advance_clearing_trip/tests/__init__.py b/hr_expense_advance_clearing_trip/tests/__init__.py
new file mode 100644
index 000000000..5c5933709
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_hr_expense_advance_clearing_trip
diff --git a/hr_expense_advance_clearing_trip/tests/test_hr_expense_advance_clearing_trip.py b/hr_expense_advance_clearing_trip/tests/test_hr_expense_advance_clearing_trip.py
new file mode 100644
index 000000000..966044f6d
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/tests/test_hr_expense_advance_clearing_trip.py
@@ -0,0 +1,75 @@
+# Copyright 2026 Ledo
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from datetime import date
+
+from odoo.exceptions import UserError
+from odoo.tests import tagged
+
+from odoo.addons.hr_expense.tests.common import TestExpenseCommon
+
+
+@tagged("-at_install", "post_install")
+class TestHrExpenseAdvanceClearingTrip(TestExpenseCommon):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ advance_account = cls.company_data["default_account_deferred_expense"]
+ advance_account.reconcile = True
+ cls.emp_advance = cls.env.ref("hr_expense_advance_clearing.product_emp_advance")
+ cls.emp_advance.property_account_expense_id = advance_account
+ cls.product_a.standard_price = 0
+ cls.employee = cls.expense_employee
+ cls.trip = cls.env["hr.trip"].create(
+ {
+ "name": "Munich",
+ "start_date": date(2026, 2, 1),
+ "end_date": date(2026, 2, 7),
+ "employee_id": cls.employee.id,
+ }
+ )
+ cls.advance = cls.env["hr.expense"].create(
+ {
+ "name": "Munich advance",
+ "employee_id": cls.employee.id,
+ "product_id": cls.emp_advance.id,
+ "total_amount_currency": 1000.0,
+ "expense_type": "advance",
+ "payment_mode": "own_account",
+ }
+ )
+ cls.exp_a = cls.env["hr.expense"].create(
+ {
+ "name": "Hotel",
+ "employee_id": cls.employee.id,
+ "product_id": cls.product_a.id,
+ "total_amount_currency": 300.0,
+ "trip_id": cls.trip.id,
+ }
+ )
+ cls.exp_b = cls.env["hr.expense"].create(
+ {
+ "name": "Meals",
+ "employee_id": cls.employee.id,
+ "product_id": cls.product_a.id,
+ "total_amount_currency": 120.0,
+ "trip_id": cls.trip.id,
+ }
+ )
+
+ def test_apply_advance_clears_trip_expenses(self):
+ self.trip.advance_id = self.advance
+ self.trip.action_apply_advance()
+ self.assertEqual(self.exp_a.clearing_advance_id, self.advance)
+ self.assertEqual(self.exp_b.clearing_advance_id, self.advance)
+
+ def test_apply_advance_without_advance_raises(self):
+ self.trip.advance_id = False
+ with self.assertRaises(UserError):
+ self.trip.action_apply_advance()
+
+ def test_apply_advance_skips_already_cleared(self):
+ self.trip.advance_id = self.advance
+ self.exp_a.clearing_advance_id = self.advance
+ self.trip.action_apply_advance()
+ self.assertEqual(self.exp_b.clearing_advance_id, self.advance)
diff --git a/hr_expense_advance_clearing_trip/views/hr_trip_views.xml b/hr_expense_advance_clearing_trip/views/hr_trip_views.xml
new file mode 100644
index 000000000..0f8d84f5e
--- /dev/null
+++ b/hr_expense_advance_clearing_trip/views/hr_trip_views.xml
@@ -0,0 +1,28 @@
+
+
+
+ hr.trip.view.form - advance clearing
+ hr.trip
+
+
+
+
+
+
+
+
+
+
+
+
+
From d09452c1b51a570278a926f5d1f4580d5920e568 Mon Sep 17 00:00:00 2001
From: Don Kendall
Date: Fri, 19 Jun 2026 12:35:53 -0400
Subject: [PATCH 75/75] [DEMO] trip suite: combined branch + SMB sales-rep walk
narrative
---
DEMO_trip_suite.md | 43 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
create mode 100644 DEMO_trip_suite.md
diff --git a/DEMO_trip_suite.md b/DEMO_trip_suite.md
new file mode 100644
index 000000000..66b1c07d7
--- /dev/null
+++ b/DEMO_trip_suite.md
@@ -0,0 +1,43 @@
+# HR Expense — Trip suite demo (SMB sales-rep walk)
+
+This branch stacks the optional **Trip** layer over the base hr-expense flow so it
+can be installed + evaluated as one unit:
+
+- `hr_expense_trip` — the optional Trip container (groups expense lines; approval
+ workflow draft → request → approve → collect receipts → done; trip PDF).
+- `hr_expense_advance_clearing` + `hr_expense_advance_clearing_trip` — a **trip
+ advance = the budget** (cash issued for the trip, cleared against actual spend).
+- `hr_expense_payment` + `hr_expense_payment_trip` — **reimburse the whole trip
+ in one payment**.
+
+The Trip is **opt-in**: nothing changes about the plain per-expense flow unless a
+trip is used.
+
+## Scenario — a sales rep on the road
+
+**Without a trip (base flow):** the rep files each expense individually (flight,
+hotel, dinner). Each is approved and reimbursed on its own; there is no budget and
+no grouping — fine for the occasional expense, noisy for a multi-day trip.
+
+**With a trip:** the rep's manager opens a **Trip** ("Q1 East Coast Sales Tour"),
+issues a **$2,000 advance** as the trip budget, and approves the trip. The rep
+collects receipts against the trip; **Apply Advance** clears them against the
+budget (Advance Remaining tracks what's left), and **Pay Trip** reimburses any
+balance in one payment. One approval, one budget, one settlement.
+
+| Need | Base | Trip |
+|---|---|---|
+| Group a multi-day trip's expenses | — | trip.expense_ids |
+| Budget | — | advance ( $ issued up front ) |
+| Approval | per expense | one trip approval workflow |
+| Reimburse | per expense | one payment (Pay Trip) |
+| Document | per expense | trip PDF report |
+
+## Screenshots
+- Trip with advance budget + Apply Advance ([advance demo](https://storage.googleapis.com/ledo-pr-assets/hr-expense/pr-3/advance-clearing-trip.png)).
+- Trip form + batched expenses + state machine ([trip foundation](https://storage.googleapis.com/ledo-pr-assets/hr-expense/pr-1/01-buffer-form-flow-index.png) — see hr-expense#1 for the trip walk).
+
+## Eval
+Open this branch's runboat build (admin/admin) and follow the scenario above.
+Note: in 19.0, expense posting routes through `hr.expense.post.wizard`, so for the
+**Pay Trip** step do *Create Bill* → confirm the post wizard → **Pay Trip**.