From a9d8137d1f66044d96e5ff8cd657574d4935715e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul=20=28ACSONE=29?= Date: Fri, 23 Jun 2017 16:55:01 +0200 Subject: [PATCH] [10.0] extract hr_expense_private_car (#36) * remove private car stuff from hr_expense_usability * hr_private_car_expenses --- hr_expense_private_car/README.rst | 13 ++ hr_expense_private_car/__init__.py | 4 + hr_expense_private_car/__manifest__.py | 29 +++ .../hr_employee_view.xml | 0 hr_expense_private_car/hr_expense.py | 190 ++++++++++++++++++ .../hr_expense_data.xml | 0 hr_expense_private_car/hr_expense_view.xml | 24 +++ .../post_install.py | 0 .../private_car_demo.xml | 0 .../private_car_km_price_view.xml | 0 .../security/expense_security.xml | 10 + .../security/ir.model.access.csv | 0 .../static/description/icon.png | Bin 0 -> 6662 bytes hr_expense_usability/README.rst | 1 - hr_expense_usability/__init__.py | 1 - hr_expense_usability/__manifest__.py | 6 - hr_expense_usability/hr_expense.py | 182 +---------------- hr_expense_usability/hr_expense_view.xml | 5 - hr_expense_usability/product_view.xml | 2 +- .../security/expense_security.xml | 6 - setup/hr_expense_private_car/odoo/__init__.py | 1 + .../odoo/addons/__init__.py | 1 + .../odoo/addons/hr_expense_private_car | 1 + setup/hr_expense_private_car/setup.py | 6 + 24 files changed, 281 insertions(+), 201 deletions(-) create mode 100644 hr_expense_private_car/README.rst create mode 100644 hr_expense_private_car/__init__.py create mode 100644 hr_expense_private_car/__manifest__.py rename {hr_expense_usability => hr_expense_private_car}/hr_employee_view.xml (100%) create mode 100644 hr_expense_private_car/hr_expense.py rename {hr_expense_usability => hr_expense_private_car}/hr_expense_data.xml (100%) create mode 100644 hr_expense_private_car/hr_expense_view.xml rename {hr_expense_usability => hr_expense_private_car}/post_install.py (100%) rename {hr_expense_usability => hr_expense_private_car}/private_car_demo.xml (100%) rename {hr_expense_usability => hr_expense_private_car}/private_car_km_price_view.xml (100%) create mode 100644 hr_expense_private_car/security/expense_security.xml rename {hr_expense_usability => hr_expense_private_car}/security/ir.model.access.csv (100%) create mode 100644 hr_expense_private_car/static/description/icon.png create mode 100644 setup/hr_expense_private_car/odoo/__init__.py create mode 100644 setup/hr_expense_private_car/odoo/addons/__init__.py create mode 120000 setup/hr_expense_private_car/odoo/addons/hr_expense_private_car create mode 100644 setup/hr_expense_private_car/setup.py diff --git a/hr_expense_private_car/README.rst b/hr_expense_private_car/README.rst new file mode 100644 index 0000000..cefb624 --- /dev/null +++ b/hr_expense_private_car/README.rst @@ -0,0 +1,13 @@ +==================== +HR Expense Usability +==================== + +* support for Private car expenses (frais kilométriques selon barème fiscal), + +Credits +======= + +Contributors +------------ + +* Alexis de Lattre diff --git a/hr_expense_private_car/__init__.py b/hr_expense_private_car/__init__.py new file mode 100644 index 0000000..2be8a21 --- /dev/null +++ b/hr_expense_private_car/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import hr_expense +from .post_install import create_private_car_km_prices diff --git a/hr_expense_private_car/__manifest__.py b/hr_expense_private_car/__manifest__.py new file mode 100644 index 0000000..dca3b49 --- /dev/null +++ b/hr_expense_private_car/__manifest__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# © 2015-2017 Akretion (http://www.akretion.com) +# @author Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'HR Expense Private Car', + 'version': '10.0.1.0.0', + 'category': 'Human Resources', + 'license': 'AGPL-3', + 'summary': 'Better usability for the management of expenses', + 'description': '', + 'author': 'Akretion', + 'website': 'http://www.akretion.com', + 'depends': [ + 'hr_expense_usability', + ], + 'data': [ + 'hr_expense_data.xml', + 'hr_employee_view.xml', + 'hr_expense_view.xml', + 'private_car_km_price_view.xml', + 'security/expense_security.xml', + 'security/ir.model.access.csv', + ], + 'post_init_hook': 'create_private_car_km_prices', + 'demo': ['private_car_demo.xml'], + 'installable': True, +} diff --git a/hr_expense_usability/hr_employee_view.xml b/hr_expense_private_car/hr_employee_view.xml similarity index 100% rename from hr_expense_usability/hr_employee_view.xml rename to hr_expense_private_car/hr_employee_view.xml diff --git a/hr_expense_private_car/hr_expense.py b/hr_expense_private_car/hr_expense.py new file mode 100644 index 0000000..3214994 --- /dev/null +++ b/hr_expense_private_car/hr_expense.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# © 2014-2017 Akretion (http://www.akretion.com) +# @author Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models, fields, api, _ +from odoo.exceptions import UserError, ValidationError +from odoo.tools import float_compare, float_is_zero +import odoo.addons.decimal_precision as dp + + +# I had to choose between several ideas when I developped this module : +# 1) constraint on product_id in expense line +# Idea : we put a constraint on the field product_id of the expense line +# and, if it's a private_car_expense_ok=True product but it's not the private +# car expense product of the employee, we block +# Drawback : not convenient for the employee because he has to select the +# right private car expense product by himself + +# 2) single product, dedicated object for prices +# Idea : we create only one "private car expense" product, and we +# create a new object to store the price depending on the CV, etc... +# Drawback : need to create a new object +# => that's what is implemented in this module + +# 3) single generic "My private car" product selectable by the user ; +# several specific private car products NOT selectable by the user +# Idea : When the user selects the generic "My private car" product, +# it is automatically replaced by the specific one via the on_change +# Drawback : decimal precision 'Product Price' on standard_price of product +# (but we need 3) + +class PrivateCarKmPrice(models.Model): + _name = 'private.car.km.price' + _description = 'Private Car Kilometer Price' + _order = 'name' + + name = fields.Char(required=True) + unit_amount = fields.Float( + string='Price per KM', digits=dp.get_precision('Expense Unit Price'), + help='Price per kilometer in company currency.') + company_id = fields.Many2one( + 'res.company', string='Company', + default=lambda self: self.env['res.company']._company_default_get( + 'private.car.km.price')) + active = fields.Boolean(default=True) + + +class HrEmployee(models.Model): + _inherit = 'hr.employee' + + def compute_private_car_total_km_this_year(self): + res = {} + private_car_product_id = self.env.ref( + 'hr_expense_usability.generic_private_car_expense').id + today = fields.Date.context_today(self) + today_dt = fields.Date.from_string(today) + self._cr.execute( + """ + SELECT el.employee_id, sum(el.quantity) + FROM hr_expense el + WHERE el.state NOT IN ('draft', 'cancel') + AND el.employee_id IN %s + AND el.product_id=%s + AND EXTRACT(year FROM el.date) = %s + GROUP BY el.employee_id + """, + (tuple(self.ids), private_car_product_id, today_dt.year)) + for line in self._cr.dictfetchall(): + res[line['employee_id']] = line['sum'] + for empl in self: + empl.private_car_total_km_this_year = res.get(empl.id) or 0.0 + + private_car_plate = fields.Char( + 'Private Car Plate', size=32, copy=False, track_visibility='onchange', + help="This field will be copied on the expenses of this employee.") + private_car_km_price_id = fields.Many2one( + 'private.car.km.price', string='Private Car Price', copy=False, + ondelete='restrict', track_visibility='onchange', + help="This field will be copied on the expenses of this employee.") + private_car_total_km_this_year = fields.Float( + compute='compute_private_car_total_km_this_year', + string="Total KM with Private Car This Year", readonly=True, + help="Number of kilometers (KM) with private car for this " + "employee in expenses in Approved, Waiting Payment or Paid " + "state in the current civil year. This is usefull to check or " + "estimate if the Private Car Product selected for this " + "employee is compatible with the number of kilometers " + "reimbursed to this employee during the civil year.") + + +class HrExpense(models.Model): + _inherit = 'hr.expense' + + private_car_plate = fields.Char( + string='Private Car Plate', size=32, track_visibility='onchange', + readonly=True, states={'draft': [('readonly', False)]}) + private_car_km_price_id = fields.Many2one( + 'private.car.km.price', string='Private Car Price', copy=False, + ondelete='restrict', track_visibility='onchange', + help="This field will be copied on the expenses of this employee.") + # only for field visibility + private_car_expense = fields.Boolean() + + @api.onchange('product_id') + def _onchange_product_id(self): + private_car_product = self.env.ref( + 'hr_expense_usability.generic_private_car_expense') + if ( + self.product_id and + self.product_id == private_car_product and + self.employee_id): + if not self.employee_id.private_car_km_price_id: + raise UserError(_( + "Missing Private Car Km Price on the configuration of " + "the employee '%s'.") % self.employee_id.display_name) + if not self.employee_id.private_car_plate: + raise UserError(_( + "Missing Private Car Plate on the configuration of " + "the employee '%s'.") % self.employee_id.display_name) + self.private_car_expense = True + self.currency_id = self.company_id.currency_id + self.private_car_plate = self.employee_id.private_car_plate + self.private_car_km_price_id =\ + self.employee_id.private_car_km_price_id + else: + self.private_car_expense = False + self.private_car_plate = False + self.private_car_km_price_id = False + return super(HrExpense, self)._onchange_product_id() + + @api.onchange('private_car_km_price_id') + def _onchange_private_car_km_price_id(self): + if self.private_car_km_price_id and self.employee_id: + self.unit_amount =\ + self.employee_id.private_car_km_price_id.unit_amount + + @api.onchange('unit_amount') + def _onchange_unit_amount(self): + res = {} + if self.private_car_expense: + original_unit_amount = self.private_car_km_price_id.unit_amount + prec = self.env['decimal.precision'].precision_get( + 'Expense Unit Price') + if float_compare( + original_unit_amount, self.unit_amount, + precision_digits=prec): + if self.env.user.has_group('account.group_account_manager'): + res['warning'] = { + 'title': _('Warning - Private Car Expense'), + 'message': _( + "You should not change the unit price " + "for private car expenses. You should change " + "the Private Car Product or update the Cost " + "Price of the selected Private Car Product " + "and re-create the Expense.\n\nBut, as " + "you are in the group 'Account Manager', we " + "suppose that you know what you are doing, " + "so the original unit amount (%s) is not " + "restored.") % original_unit_amount, + } + else: + res['warning'] = { + 'title': _('Warning - Private Car Expense'), + 'message': _( + "You should not change the unit price " + "for private car expenses. The original unit " + "amount has been restored.\n\nOnly users in " + "the 'Account Manager' group are allowed to " + "change the unit amount for private car " + "expenses manually.")} + res['value'] = {'unit_amount': original_unit_amount} + return res + + @api.constrains( + 'product_id', 'private_car_plate', 'private_car_km_price_id') + def _check_expense(self): + generic_private_car_product = self.env.ref( + 'hr_expense_usability.generic_private_car_expense') + for exp in self: + if exp.product_id == generic_private_car_product: + if not exp.private_car_plate: + raise ValidationError(_( + "Missing 'Private Car Plate' on the " + "expense '%s' of employee '%s'.") + % (exp.name, exp.employee_id.display_name)) + if not exp.private_car_km_price_id: + raise ValidationError(_( + "Missing 'Private Car Km Price' on the " + "expense '%s'.") % exp.name) diff --git a/hr_expense_usability/hr_expense_data.xml b/hr_expense_private_car/hr_expense_data.xml similarity index 100% rename from hr_expense_usability/hr_expense_data.xml rename to hr_expense_private_car/hr_expense_data.xml diff --git a/hr_expense_private_car/hr_expense_view.xml b/hr_expense_private_car/hr_expense_view.xml new file mode 100644 index 0000000..2054282 --- /dev/null +++ b/hr_expense_private_car/hr_expense_view.xml @@ -0,0 +1,24 @@ + + + + + + + + usability.hr.expense.form + hr.expense + + + + + + + + + + + diff --git a/hr_expense_usability/post_install.py b/hr_expense_private_car/post_install.py similarity index 100% rename from hr_expense_usability/post_install.py rename to hr_expense_private_car/post_install.py diff --git a/hr_expense_usability/private_car_demo.xml b/hr_expense_private_car/private_car_demo.xml similarity index 100% rename from hr_expense_usability/private_car_demo.xml rename to hr_expense_private_car/private_car_demo.xml diff --git a/hr_expense_usability/private_car_km_price_view.xml b/hr_expense_private_car/private_car_km_price_view.xml similarity index 100% rename from hr_expense_usability/private_car_km_price_view.xml rename to hr_expense_private_car/private_car_km_price_view.xml diff --git a/hr_expense_private_car/security/expense_security.xml b/hr_expense_private_car/security/expense_security.xml new file mode 100644 index 0000000..4bee528 --- /dev/null +++ b/hr_expense_private_car/security/expense_security.xml @@ -0,0 +1,10 @@ + + + + + Private Car Kilometer Prices Multi-company + + ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] + + + diff --git a/hr_expense_usability/security/ir.model.access.csv b/hr_expense_private_car/security/ir.model.access.csv similarity index 100% rename from hr_expense_usability/security/ir.model.access.csv rename to hr_expense_private_car/security/ir.model.access.csv diff --git a/hr_expense_private_car/static/description/icon.png b/hr_expense_private_car/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1a60d0ef970b6a9b19e742f3bb9f86cb17f52daa GIT binary patch literal 6662 zcmWkz1z3|^6rLy{-Ca^r(k(6BB_Jss(jZ-;2uKMyx>H(8Bt~}+ejHr`k?yYl`s`ag zd$w=)JNKNo7Nw=3h=WOq34uUxl$GRkz_SLpn`5AXM+N#LM(~7Ysj4UkdH8qBg_R_M zcb>W_z43%Vu!#QMA3@SH$-$fGUdrn7=qp(G1pH5(F}p(`5Q-#aIT=0QxxGL?H{H#q zivtP8MCLcOPJ_ul6i9iYROU}vq!>@S)Wu9|O}NtLxj#|4I2>pdUriRP7LP0lS)`Bc z6*^2ga5*FiePBuXTKnn6)eI3Pd20_xt<7xm3)N)i*;?y&65|(&fqHLvqf>s)2#=3Y z&}{wXJU9@)xe@0+6b;hInOaUW;X?cxw|Fq_cpB0DRi#K+LQ+8BGzPJ=NQ;fe97a!1 z8OuP8%SevRK#nbo{{$l}REdd?Ka`4+GEAqG`plRfKXmzE6CE8LzPvo{Bt#W(*oodP znd4dl&cMgR(}9DFJLcK4IMmiAd3t`HUsp%u-9q#D@ndpY+Rw`e=mat>NiyZrvegJi z2?^7QEw939$CBr65)u*vV`Fha&l#RVmYjt6diBb@W`1w( z{M@^~p`mlttz*lpYr~^+^F*5-Us{CPHiq}R9(!R?(K9J2I)wtbsh%G3$nbD?#hbcR z{c`5M8JSW&f8|oWV<)k{bXgk0e`=)ZwJd84*Wvc3(YO(eEk#A)DN0Ni;j(!fP$<;i z*;!sy6>Hb~Wvmk7yJo4L%GTBvr<7EVii%1;2?~V?aNWu!Q?SX!#Vl=ebG+!@u(2@>FCU+R#>SL@#LgKHWo)Xj zD{YK|m5+tRDm-An>3KR7`aYD`TQV-GlRU$$Lr_S_ax7DH4Wi#zU%%jav?jlFa1kgaq_FBIG{a>g z99(pL{elz=6AMe~rS4V*4q_E|Dy#2mesJTel( zlf7Bm+L~Eu-cD;{V}l@jVdUkpHCbdpZl#DkFxS$8#374QURpL7CFJDL-AIavT#lD$ zWcmNd%UiA4K~!_>?Cii77u7r8^4zB>K4L-PLO~0)9nT)64Y_sy28EiYN(7uAq;S6R zP-lk<^+e|(cH~u5hE699Vq;^El6i~D%Xbj2?RVbOzblPtR8zQ^vosWAcv0wMAn%`N zcva&D1hac%C zGDSU~M9Ik_@q4v12i!AxT$jYJH@HJ6ei~KotpzD??$wia&$Nx&s@58Ewg)3)h3)Te zui#HZN$}F?p(+lG&F{U|dY)FZCfe5dUGH#3_cC~7ww9EXtgZD%$CAj0#-r_wfD|afq}upre^Rlk&mC>)%Q+KV)2ii(ozpsmoQwhbagIGDd~?Aw=@UO-)y~V_7d9 zI44-ToE;n-P7CPnB8`lVHHi|?AYlCM(CkM?M;Sl!KPt)FH|1pJJTG;EFL-z#lyri84V z`5Uy}9uLcW?Bsc!`&3j^v}>ojot7y-oWBn;vv;m-b{$3Zd?A|jMY@1}z8TUlJKKMo zt=6d08{#HH9T_iW_vH2K*KgX6q19jI7}8=0$Qww=$gmFDA0)%OS8rR~M;7EXog5qp zlD@X!(Tb6Tt z4$!{Q+uPecq9bgpA`M3BMMXsbk(R1AJeu5E9#X!2yM4uy$wVY^Gz8^XfN~g2EH6tH z;T3AJrW#d#9^brCrN_rOKR+M33vOp8#yq0#Pms;Sc-Ju3SzljI3{`3P!26qo8)q#V z#_<6Vg1MOIpONgoJ_R|31hydLsvEgt9Chb(xdGYP>1l^;9l-J$E!HP}y5KnukDv?w>+0eXoE)jZ)>l#_Yx$ht@z_-0`tlMXxdTV|#jJM;@NCaQ9g&YDP^f;6#;s;UE`%GB%6NScy?0pemU zC!pdx(mP=w5}rY+eRdVtt5bR!!IH!l{5#O`7Qc@^WjG-{9kWs&D^}rggHddKy+C1c zv22mfGd8xTF}zPu9%19+cI|n0Z+YccSI5sf3GFVnq>N=t47)D3-Ps|k&2tR&^?x#o ziq1*}oI8nRX;@oZOJlF{@$*|W7+J}GWGo_D5y3@8MHTToUSI!!6)yXA(AurZl-Vu~kARDh7G4fa+FNWfa1+Z~wulL;X>Wg^DlIL2O&X1= zQbd@RmUeg*2#!kt&?_fb9q!cma8?fw4+Rn~4I{m%$aZ(6!cj*Dq-*u?`v*1DBXK$+ z`J?6|61A9`nVL)WTlG@CE_(q6AQ76HnojF-?qY7Ha_*UYVN1OpO}7nsd3osHREl&t zljGWx=`m`8j%M_l8L1)8ti(Wvgy=Fd;i_<2lL|ASjGqAE^i_#z#ZOYm{zx}Z>uvXE zoRL*GEn{OUpW93K$t|x7YdgF47Z_bg>VDB4>#$2caw}eK7>zyAqqs+3)tOV6ZRLHl z6-B5&cdv#THtu*dt$4Rsp^k#Gh-Kk2Gcz0ha(l+d$7e~h5yOiS25B;hYj}sK-55?m z9%+WcWk(Bfn-0z4%yG#Z&KLzo-)uTm+2CMF{VJ2Flg;t&J@2}^ zn+vP`g{B7<6f&teV`ycZ8dk?!o}Mpw%t+!KH&av7EThUc=h98N?*#>q+b?^$E$>hp zbH14Ab7y9_g zieK3vSDd-@t0Tpco1$^dl~f1u$69{mRP9 zP8hv4vzhsVtD|Gsw4=O@4RcuM+g;T4KY!xzM0l~;xVUJu`R#DQ77T!XC|#>}p8vUO zynIbsZ?vaQqr)J}M0geRF%v_pHALuhSIO_+`b)?o>mE<5Rytu3k;vj=mW5yLkGs3O z|4oVrE?7+JOP~GAP$H|wu&}Vc-QC@CF#oFDrp*FdS7?3Y$uEpQE7AoCpcw^DjFNv` zK8OIT($vKJM`kJ)R^tu!@O*8Xi=*Fy31IHuxw<}YSsarKqTt>RZl{}`p8jD}Nlr=m z#SdRzUY?bMG(6%|a-?xJOg56E9L z(%?sM=_F%(mV?NIGH}46Bjp(0#ylg&wAdJe`rcn{0z9YW$B&XU3d2b1M_L301r3ah z+^W?B&jYDOMB58sW^Z=7Q)8n&254&`K$)jg%91T+UT+T%2Qu?IF){H=dU`rJLY?Wa zrn-9X?rhz&lwrjz4uwZ4+~$3%+^|pA*Vnw`cu!CY77C0%2pvTVSfSwb77}40b@6(R<4Rhm9F4pXH zb%}tC>K8X)hgz<7g)6D5M&pRUo8vZ=0$^q$)VL$r5&?S0^!H0^2Y`cJib2LF$JK2-tR5|Cl}(qA!v`P*&3zZ2PO#FcC-IcHt$zyIL%HI zL&5-SLVO9J81VasD9q(kguV06Q_4X~K$>9^yI532% z6v-+mpeZvYYWEXKMMp;yV1{ey>OL-+qG;Lww*L9gXhsB{h^%Ky!I8;J06z^1+ z5_wNtK!X*+?Nh%a2&ayY2x6zUhTmIm)a`R5dhPC^f14U)!r}aiJfK(HSdj)8EeDf6mj4IQX z&;QJei02<{{Ys;7;2W@UaAMauvp0LEX%*b5`rQXcM{WFJOa0`p09lOS%NJ;W*DE8R z0oq&MnF&yWEef6x~#if zP-lndJXaree0`jv$L0hGp%A_ zVa0?oFffJPQC4D2Y}W7?Yrz^DC4O9qo@BtE|(9{HC8yRq9Vta)>)RJ8!77 z5Jf8#BxNJ;%`wMMoX)nVyUJddR}E|&Kg4l&Q8W%9xftNi^b8<-`e@DcUz%5<0M}$* z1g*ak@Fg>Zbe@_4D|+lu`!sV`C#R9rfiPd;RUr^GoOV3M`w@;sCdA+8|L~icIz*W8 z1r;T=Ee0@J)Tk@xCBUhbFzb>?gb@dyI47R}bf zF05~9X`;+~e<7cM%$&SB*&IY3ai(eKMwR=`I{g&9Ip1R}fQzh#o8TkpIiV`y;JZ`6 zBoDYWKF}Qn^A|JIK>Ypv|Ky1KCF)lg5^%tBDo{xgDDK-bDJTS9(rxH>ETq*JJmHNq%s z6Bk9<;Y5fu;OWA>`NpZ)I=gXv`1z&DTzxT>uuIRmI?Gfu(4rGySHh2)X`-kHPg;L~ zglDeKE?&#rygfu=tL`9h^Cct{Sc6`JDwC^Kj_OufpvIC+pUDmk4CKBr{yCB1+BR$3 zI3I!m8JDqxm28k`>Se9(FEC z)Bg~XeD~6sEtr{-VY0EYvHg7RU9~8!|KDdi<E%WYavU#~S!^v-HT6yGsMHD+vL!`i)X(VEyiS+;n^B59HRYoSbrIW=kD_q7D80{0e{p*<4>2?>VUX! zNaOY1w!P0bG!it8$`X731z0P that's what is implemented in this module - -# 3) single generic "My private car" product selectable by the user ; -# several specific private car products NOT selectable by the user -# Idea : When the user selects the generic "My private car" product, -# it is automatically replaced by the specific one via the on_change -# Drawback : decimal precision 'Product Price' on standard_price of product -# (but we need 3) - class ProductTemplate(models.Model): _inherit = 'product.template' @@ -84,64 +63,9 @@ class ProductProduct(models.Model): self.taxes_id = False -class PrivateCarKmPrice(models.Model): - _name = 'private.car.km.price' - _description = 'Private Car Kilometer Price' - _order = 'name' - - name = fields.Char(required=True) - unit_amount = fields.Float( - string='Price per KM', digits=dp.get_precision('Expense Unit Price'), - help='Price per kilometer in company currency.') - company_id = fields.Many2one( - 'res.company', string='Company', - default=lambda self: self.env['res.company']._company_default_get( - 'private.car.km.price')) - active = fields.Boolean(default=True) - - class HrEmployee(models.Model): _inherit = 'hr.employee' - def compute_private_car_total_km_this_year(self): - res = {} - private_car_product_id = self.env.ref( - 'hr_expense_usability.generic_private_car_expense').id - today = fields.Date.context_today(self) - today_dt = fields.Date.from_string(today) - self._cr.execute( - """ - SELECT el.employee_id, sum(el.quantity) - FROM hr_expense el - WHERE el.state NOT IN ('draft', 'cancel') - AND el.employee_id IN %s - AND el.product_id=%s - AND EXTRACT(year FROM el.date) = %s - GROUP BY el.employee_id - """, - (tuple(self.ids), private_car_product_id, today_dt.year)) - for line in self._cr.dictfetchall(): - res[line['employee_id']] = line['sum'] - for empl in self: - empl.private_car_total_km_this_year = res.get(empl.id) or 0.0 - - private_car_plate = fields.Char( - 'Private Car Plate', size=32, copy=False, track_visibility='onchange', - help="This field will be copied on the expenses of this employee.") - private_car_km_price_id = fields.Many2one( - 'private.car.km.price', string='Private Car Price', copy=False, - ondelete='restrict', track_visibility='onchange', - help="This field will be copied on the expenses of this employee.") - private_car_total_km_this_year = fields.Float( - compute='compute_private_car_total_km_this_year', - string="Total KM with Private Car This Year", readonly=True, - help="Number of kilometers (KM) with private car for this " - "employee in expenses in Approved, Waiting Payment or Paid " - "state in the current civil year. This is usefull to check or " - "estimate if the Private Car Product selected for this " - "employee is compatible with the number of kilometers " - "reimbursed to this employee during the civil year.") - def _get_accounting_partner_from_employee(self): # By default, odoo uses self.employee_id.address_home_id # which users usually don't configure @@ -172,15 +96,6 @@ class HrExpense(models.Model): # I want a specific precision for unit_amount of expense # main reason is KM cost which is 3 by default unit_amount = fields.Float(digits=dp.get_precision('Expense Unit Price')) - private_car_plate = fields.Char( - string='Private Car Plate', size=32, track_visibility='onchange', - readonly=True, states={'draft': [('readonly', False)]}) - private_car_km_price_id = fields.Many2one( - 'private.car.km.price', string='Private Car Price', copy=False, - ondelete='restrict', track_visibility='onchange', - help="This field will be copied on the expenses of this employee.") - # only for field visibility - private_car_expense = fields.Boolean() tax_amount = fields.Monetary( string='Tax Amount', currency_field='currency_id', readonly=True, states={'draft': [('readonly', False)]}) @@ -241,106 +156,11 @@ class HrExpense(models.Model): self.untaxed_amount_usability = total self.tax_amount = False - @api.onchange('product_id') - def _onchange_product_id(self): - private_car_product = self.env.ref( - 'hr_expense_usability.generic_private_car_expense') - if ( - self.product_id and - self.product_id == private_car_product and - self.employee_id): - if not self.employee_id.private_car_km_price_id: - raise UserError(_( - "Missing Private Car Km Price on the configuration of " - "the employee '%s'.") % self.employee_id.display_name) - if not self.employee_id.private_car_plate: - raise UserError(_( - "Missing Private Car Plate on the configuration of " - "the employee '%s'.") % self.employee_id.display_name) - self.private_car_expense = True - self.currency_id = self.company_id.currency_id - self.private_car_plate = self.employee_id.private_car_plate - self.private_car_km_price_id =\ - self.employee_id.private_car_km_price_id - else: - self.private_car_expense = False - self.private_car_plate = False - self.private_car_km_price_id = False - return super(HrExpense, self)._onchange_product_id() - - @api.onchange('private_car_km_price_id') - def _onchange_private_car_km_price_id(self): - if self.private_car_km_price_id and self.employee_id: - self.unit_amount =\ - self.employee_id.private_car_km_price_id.unit_amount - - @api.onchange('unit_amount') - def _onchange_unit_amount(self): - res = {} - if self.private_car_expense: - original_unit_amount = self.private_car_km_price_id.unit_amount - prec = self.env['decimal.precision'].precision_get( - 'Expense Unit Price') - if float_compare( - original_unit_amount, self.unit_amount, - precision_digits=prec): - if self.env.user.has_group('account.group_account_manager'): - res['warning'] = { - 'title': _('Warning - Private Car Expense'), - 'message': _( - "You should not change the unit price " - "for private car expenses. You should change " - "the Private Car Product or update the Cost " - "Price of the selected Private Car Product " - "and re-create the Expense.\n\nBut, as " - "you are in the group 'Account Manager', we " - "suppose that you know what you are doing, " - "so the original unit amount (%s) is not " - "restored.") % original_unit_amount, - } - else: - res['warning'] = { - 'title': _('Warning - Private Car Expense'), - 'message': _( - "You should not change the unit price " - "for private car expenses. The original unit " - "amount has been restored.\n\nOnly users in " - "the 'Account Manager' group are allowed to " - "change the unit amount for private car " - "expenses manually.")} - res['value'] = {'unit_amount': original_unit_amount} - return res - @api.constrains( - 'product_id', 'private_car_plate', 'payment_mode', 'tax_ids', + 'product_id', 'payment_mode', 'tax_ids', 'untaxed_amount_usability', 'tax_amount', 'quantity', 'unit_amount') def _check_expense(self): - generic_private_car_product = self.env.ref( - 'hr_expense_usability.generic_private_car_expense') for exp in self: - if exp.product_id == generic_private_car_product: - if not exp.private_car_plate: - raise ValidationError(_( - "Missing 'Private Car Plate' on the " - "expense '%s' of employee '%s'.") - % (exp.name, exp.employee_id.display_name)) - if not exp.private_car_km_price_id: - raise ValidationError(_( - "Missing 'Private Car Km Price' on the " - "expense '%s'.") % exp.name) - if exp.currency_id != exp.company_id.currency_id: - raise ValidationError(_( - "The expense '%s' is a private car expense, " - "so the currency of this expense (%s) should " - "be the currency of the company (%s).") % ( - exp.name, - exp.currency_id.name, - exp.company_id.currency_id.name)) - if exp.tax_ids: - raise ValidationError(_( - "The expense '%s' is a private car expense " - "so it shouldn't have taxes.") - % exp.name) if exp.tax_ids: if len(exp.tax_ids) > 1: raise ValidationError(_( diff --git a/hr_expense_usability/hr_expense_view.xml b/hr_expense_usability/hr_expense_view.xml index 146b160..0bb1d25 100644 --- a/hr_expense_usability/hr_expense_view.xml +++ b/hr_expense_usability/hr_expense_view.xml @@ -13,11 +13,6 @@ hr.expense - - - - - diff --git a/hr_expense_usability/product_view.xml b/hr_expense_usability/product_view.xml index 6af11b1..28f29fc 100644 --- a/hr_expense_usability/product_view.xml +++ b/hr_expense_usability/product_view.xml @@ -7,7 +7,7 @@ - private.car.expense.product.template.search + hr.expense.usablility.product.template.search product.template diff --git a/hr_expense_usability/security/expense_security.xml b/hr_expense_usability/security/expense_security.xml index eb1b162..4279fd2 100644 --- a/hr_expense_usability/security/expense_security.xml +++ b/hr_expense_usability/security/expense_security.xml @@ -15,10 +15,4 @@ - - Private Car Kilometer Prices Multi-company - - ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] - - diff --git a/setup/hr_expense_private_car/odoo/__init__.py b/setup/hr_expense_private_car/odoo/__init__.py new file mode 100644 index 0000000..de40ea7 --- /dev/null +++ b/setup/hr_expense_private_car/odoo/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/setup/hr_expense_private_car/odoo/addons/__init__.py b/setup/hr_expense_private_car/odoo/addons/__init__.py new file mode 100644 index 0000000..de40ea7 --- /dev/null +++ b/setup/hr_expense_private_car/odoo/addons/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/setup/hr_expense_private_car/odoo/addons/hr_expense_private_car b/setup/hr_expense_private_car/odoo/addons/hr_expense_private_car new file mode 120000 index 0000000..47c0a3f --- /dev/null +++ b/setup/hr_expense_private_car/odoo/addons/hr_expense_private_car @@ -0,0 +1 @@ +../../../../hr_expense_private_car \ No newline at end of file diff --git a/setup/hr_expense_private_car/setup.py b/setup/hr_expense_private_car/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/hr_expense_private_car/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)