commit 17f4e2caab3be285929951333bd8a4d4171088fd Author: Camilo Sarmiento Date: Wed Apr 15 17:37:53 2020 -0500 migrato to git diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..48f5dff --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,2 @@ +Version 3.2.0 - 2014-05-05 +* Initial release (Builder Level) diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..58f3b08 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,15 @@ +Copyright (C) 2011-2016 Oscar Alvarez. + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..2256b41 --- /dev/null +++ b/INSTALL @@ -0,0 +1,34 @@ +Installing trytond_staff_payroll +================================ + +Prerequisites +------------- + + * Python 2.7 or later (http://www.python.org/) + * trytond (http://www.tryton.org/) + * trytond_company (http://www.tryton.org/) + * trytond_party (http://www.tryton.org/) + * trytond_currency (http://www.tryton.org/) + * trytond_staff (http://www.tryton.org/) + + +Installation +------------ + +Once you've downloaded and unpacked the trytond_product source release, enter the +directory where the archive was unpacked, and run: + + python setup.py install + +Note that you may need administrator/root privileges for this step, as +this command will by default attempt to install module to the Python +site-packages directory on your system. + +For advanced options, please refer to the easy_install and/or the distutils +documentation: + + http://peak.telecommunity.com/DevCenter/EasyInstall + http://docs.python.org/inst/inst.html + +To use without installation, extract the archive into ``trytond/modules`` with +the directory name product. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..b445ae7 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,10 @@ +include INSTALL +include README +include TODO +include COPYRIGHT +include CHANGELOG +include LICENSE +include tryton.cfg +include *.xml +include *.odt +include doc/* diff --git a/README b/README new file mode 100644 index 0000000..c4729d2 --- /dev/null +++ b/README @@ -0,0 +1,36 @@ +trytond_staff_payroll +===================== + +The staff module of the Tryton application platform. +See __tryton__.py + +Installing +---------- + +See INSTALL + +Support +------- + +If you encounter any problems with Tryton, please don't hesitate to ask +questions on the Tryton bug tracker, mailing list, wiki or IRC channel: + + http://bugs.tryton.org/ + http://groups.tryton.org/ + http://wiki.tryton.org/ + irc://irc.freenode.net/tryton + +License +------- + +See LICENSE + +Copyright +--------- + +See COPYRIGHT + + +For more information please visit the Tryton web site: + + http://www.tryton.org/ diff --git a/TODO b/TODO new file mode 100644 index 0000000..139597f --- /dev/null +++ b/TODO @@ -0,0 +1,2 @@ + + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..511bcb3 --- /dev/null +++ b/__init__.py @@ -0,0 +1,41 @@ +#This file is part of Tryton. The COPYRIGHT file at the top level of +#this repository contains the full copyright notices and license terms. +from trytond.pool import Pool +from .period import Period, OpenPeriod +from .wage_type import WageType, WageTypeSalary +from .payroll import Payroll, PayrollLine, PayrollGroupStart, \ + PayrollGroup, PayrollReport, Move, PayrollRecompute, PayrollPreliquidation +from .category import CategoryWagesDefault, EmployeeCategory +from .employee import Employee, MandatoryWage, CreateMandatoryWages +from .configuration import StaffConfigurationSequence, StaffConfiguration +from .position import Position, WorkdayDefinition + + +def register(): + Pool.register( + Position, + WorkdayDefinition, + Period, + WageType, + MandatoryWage, + EmployeeCategory, + Employee, + Payroll, + PayrollLine, + CategoryWagesDefault, + PayrollGroupStart, + StaffConfigurationSequence, + StaffConfiguration, + WageTypeSalary, + Move, + module='staff_payroll', type_='model') + Pool.register( + PayrollReport, + module='staff_payroll', type_='report') + Pool.register( + OpenPeriod, + PayrollGroup, + CreateMandatoryWages, + PayrollPreliquidation, + PayrollRecompute, + module='staff_payroll', type_='wizard') diff --git a/category.py b/category.py new file mode 100644 index 0000000..938db7b --- /dev/null +++ b/category.py @@ -0,0 +1,25 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +from trytond.model import ModelView, ModelSQL, fields + +__all__ = ['EmployeeCategory', 'CategoryWagesDefault'] + + +class EmployeeCategory(ModelSQL, ModelView): + 'Employee Category' + __name__ = 'staff.employee_category' + name = fields.Char('Name', required=True) + code = fields.Char('Code') + wages_default = fields.Many2Many('employee_category-staff.wages', + 'employee_category', 'wage_type', 'Category - Wages Default') + + +class CategoryWagesDefault(ModelSQL): + 'Employee Category - Wages Default' + __name__ = 'employee_category-staff.wages' + _table = 'employee_category_staff_wages' + employee_category = fields.Many2One('staff.employee_category', + 'Employee Category', select=True, required=True, + ondelete='CASCADE') + wage_type = fields.Many2One('staff.wage_type', 'Wage Type', select=True, + required=True, ondelete='CASCADE') diff --git a/category.xml b/category.xml new file mode 100644 index 0000000..708890d --- /dev/null +++ b/category.xml @@ -0,0 +1,65 @@ + + + + + + + staff.employee_category + tree + category_tree + + + staff.employee_category + form + category_form + + + + Employee Category + staff.employee_category + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/configuration.py b/configuration.py new file mode 100644 index 0000000..7b7773c --- /dev/null +++ b/configuration.py @@ -0,0 +1,88 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +from trytond import backend +from trytond.model import fields +from trytond.pool import PoolMeta, Pool +from trytond.pyson import Eval +from trytond.tools.multivalue import migrate_property + +__all__ = ['StaffConfiguration', 'StaffConfigurationSequence'] + + + +def default_func(field_name): + @classmethod + def default(cls, **pattern): + return getattr( + cls.multivalue_model(field_name), + 'default_%s' % field_name, lambda: None)() + return default + + +class StaffConfiguration: + __metaclass__ = PoolMeta + __name__ = 'staff.configuration' + staff_payroll_sequence = fields.MultiValue(fields.Many2One('ir.sequence', + 'Payroll Sequence', required=True, domain=[ + ('code', '=', 'staff.payroll')]) + ) + default_hour_workday = fields.Integer('Default Hour Workday', + required=True, help='In hours') + default_restday = fields.Integer('Default Restday', required=True, + help='In hours') + default_liquidation_period = fields.Integer('Default Liquidation Period', + required=True, help='In days') + week_hours_work = fields.Integer('Week Hours Work', required=True, + help='In hours') + default_journal = fields.Many2One('account.journal', 'Default Journal', + required=True) + + default_staff_payroll_sequence = default_func('staff_payroll_sequence') + minimum_salary = fields.Integer('Minimum Salary', required=True) + + @classmethod + def multivalue_model(cls, field): + pool = Pool() + if field in ['staff_payroll_sequence', 'staff_contract_sequence']: + return pool.get('staff.configuration.sequence') + return super(StaffConfiguration, cls).multivalue_model(field) + + +class StaffConfigurationSequence: + __metaclass__ = PoolMeta + __name__ = 'staff.configuration.sequence' + staff_payroll_sequence = fields.Many2One( + 'ir.sequence', "Staff Payroll Sequence", required=True, + domain=[ + ('company', 'in', [Eval('company', -1), None]), + ('code', '=', 'staff.payroll'), + ], + depends=['company']) + + @classmethod + def __register__(cls, module_name): + TableHandler = backend.get('TableHandler') + exist = TableHandler.table_exist(cls._table) + + super(StaffConfigurationSequence, cls).__register__(module_name) + + if not exist: + cls._migrate_property([], [], []) + + @classmethod + def _migrate_property(cls, field_names, value_names, fields): + field_names.append('staff_payroll_sequence') + value_names.append('staff_payroll_sequence') + fields.append('company') + migrate_property( + 'staff.configuration', field_names, cls, value_names, + fields=fields) + + @classmethod + def default_staff_payroll_sequence(cls): + pool = Pool() + ModelData = pool.get('ir.model.data') + try: + return ModelData.get_id('staff', 'sequence_staff_payroll') + except KeyError: + return None diff --git a/configuration.xml b/configuration.xml new file mode 100644 index 0000000..989f352 --- /dev/null +++ b/configuration.xml @@ -0,0 +1,19 @@ + + + + + + + staff.configuration + + configuration_form + + + + Payroll + staff.payroll + + + + diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..2abf7cb --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,37 @@ +Staff Payroll +############# + +The staff payroll defines the Payments to employees of company. + + +Staff Payroll +************* + +The staff payroll is a model that allow to register wages payed and +deduction of salary about payroll for each employee. + + +Wage Type +----------------------------------- +Formula Unit Price +Factor e.g: if social security pay is a deduction 2.5% of +salary and salary is USD $5000 +factor expresion would be something like as 'salary * 0.025' +then will be: eval( 5000 * 0.025 ) + +Preliquidation +If the field is True, It is created automatic (with a trigger) +in payroll wage line + +Uom +For example for Extratime/Overtime the uom is "Hour", but +the other wages are common used the "Unit" + + + +----------------------------------- + +Definitions: + # payments (e.g: Salary) + # discounts (e.g: Bank Credits) + # deductions (e.g: Social Security) diff --git a/employee.py b/employee.py new file mode 100644 index 0000000..5ed1dfb --- /dev/null +++ b/employee.py @@ -0,0 +1,104 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +from trytond.model import fields, ModelSQL, ModelView +from trytond.pool import PoolMeta, Pool +from trytond.transaction import Transaction +from trytond.pyson import Not, Bool, Eval, If +from trytond.wizard import Wizard, StateTransition +from datetime import datetime, date + +__all__ = ['Employee', 'MandatoryWage', 'CreateMandatoryWages'] + + +class Employee: + __metaclass__ = PoolMeta + __name__ = 'company.employee' + category = fields.Many2One('staff.employee_category', 'Category') + mandatory_wages = fields.One2Many('staff.payroll.mandatory_wage', + 'employee', 'Mandatory Wage') + + @classmethod + def __setup__(cls): + super(Employee, cls).__setup__() + cls._error_messages.update({ + 'no_bank_accounts': ('The employee does not have banks!'), + }) + + def get_defect_amount_wage_type(self, wage_id): + defect_amount = sum([w.defect_amount or 0 for w in self.mandatory_wages if w.wage_type.id == wage_id]) + return defect_amount + + def get_last_date_futhermore(self): + if self.contract and self.contract.futhermores: + values = [f.end_date for f in self.contract.futhermores if f.end_date and f.state != 'draft'] + last_date_futhermore = max(values) if values else None + if last_date_futhermore: + return last_date_futhermore + else: + return self.contract.end_date or None + + +class MandatoryWage(ModelSQL, ModelView): + "Mandatory Wage" + __name__ = 'staff.payroll.mandatory_wage' + employee = fields.Many2One('company.employee', 'Employee') + wage_type = fields.Many2One('staff.wage_type', 'Wage Type', + required=True, domain=[('active', '=', True)]) + party = fields.Many2One('party.party', 'Party', domain=[('active', '=', True)],) + defect_amount = fields.Numeric('Defect Amount') + + @classmethod + def __setup__(cls): + super(MandatoryWage, cls).__setup__() + cls._order.insert(0, ('id', 'ASC')) + cls._error_messages.update({ + 'party_required': ('Error the wage type %s does not have party!'), + }) + + + @classmethod + def validate(cls, mandatoryWages): + for wage in mandatoryWages: + wage.check_partys() + + def check_partys(self): + pool = Pool() + Wage = pool.get('staff.wage_type') + if self.wage_type: + wage_type = self.wage_type + wages = Wage.search([ + ('id', '=', self.wage_type.id), + ]) + if wages: + for wage in wages: + if wage.party_required and not self.party: + self.raise_user_error('party_required', wage.name) + +class CreateMandatoryWages(Wizard): + 'Create Mandatory Wages' + __name__ = 'staff.payroll.create_mandatory_wages' + start_state = 'create_wages' + create_wages = StateTransition() + + def transition_create_wages(self): + pool = Pool() + Employee = pool.get('company.employee') + MandatoryWage = pool.get('staff.payroll.mandatory_wage') + + employees_ids = Transaction().context['active_ids'] + employees = Employee.browse(employees_ids) + values = [] + for employee in employees: + if employee.category and employee.category.wages_default: + current_wages_ids = [m.wage_type.id + for m in employee.mandatory_wages] + for wage in employee.category.wages_default: + if wage.id in current_wages_ids: + continue + val = { + 'employee': employee.id, + 'wage_type': wage.id, + } + values.append(val) + MandatoryWage.create(values) + return 'end' diff --git a/employee.xml b/employee.xml new file mode 100644 index 0000000..d54dda2 --- /dev/null +++ b/employee.xml @@ -0,0 +1,36 @@ + + + + + + + staff.payroll.mandatory_wage + tree + mandatory_wage_tree + + + staff.payroll.mandatory_wage + form + mandatory_wage_form + + + + company.employee + + employee_form + + + + Create Mandatory Wages + staff.payroll.create_mandatory_wages + + + form_action + company.employee,-1 + + + + + diff --git a/locale/es.po b/locale/es.po new file mode 100644 index 0000000..8366e9a --- /dev/null +++ b/locale/es.po @@ -0,0 +1,1211 @@ +# +msgid "" +msgstr "Content-Type: text/plain; charset=utf-8\n" + +msgctxt "error:company.employee:" +msgid "The employee does not have banks!" +msgstr "El empleado no tiene un banco!" + +msgctxt "error:staff.payroll.mandatory_wage:" +msgid "Error the wage type %s does not have party!" +msgstr "Error el tipo de pago %s no tiene tercero" + +msgctxt "error:staff.payroll.period:" +msgid "The date end can not smaller than date start" +msgstr "La fecha final no puede ser menor a la fecha inicial" + +msgctxt "error:staff.payroll.period:" +msgid "The period overlap another period!" +msgstr "El periodo se cruza con otro periodo." + +msgctxt "error:staff.payroll:" +msgid "Already exist one payroll in this period with this contract!" +msgstr "" +"Ya existe una nómina para este empleado con el mismo contrato y periodo " +"seleccionado" + +msgctxt "error:staff.payroll:" +msgid "Bad configuration of the wage type \"%s\"." +msgstr "Errada configuración del tipo de pago \"%s\"." + +msgctxt "error:staff.payroll:" +msgid "Payroll \"%s\" has a move, must be deleted before deletion." +msgstr "La nómina \"%s\" tiene un asiento, debe ser borrado antes eliminarse." + +msgctxt "error:staff.payroll:" +msgid "Payroll \"%s\" must be cancelled before deletion." +msgstr "La nómina \"%s\" debe ser cancelada antes de ser borrada." + +msgctxt "error:staff.payroll:" +msgid "Payroll period is closed!" +msgstr "El periodo de nómina esta cerrado!" + +msgctxt "error:staff.payroll:" +msgid "Sequence Payroll is missing!" +msgstr "Falta la Secuencia de Nomina!" + +msgctxt "error:staff.payroll:" +msgid "The date end can not smaller than date start, for employee %s" +msgstr "" + +msgctxt "error:staff.payroll:" +msgid "" +"The date start/end is repetead or crossed with other" +" date payroll" +msgstr "" +"La fecha de inicio/fin esta repetida o cruzada con otra fecha de nómina" + +msgctxt "error:staff.payroll:" +msgid "The employee does not have salary!" +msgstr "El empleado no tiene concepto de salario creado!" + +msgctxt "error:staff.position:" +msgid "Missing default values for workday or restday on configuration!" +msgstr "" +"Faltan los valores por defecto para el día laboral o el descanso en la " +"configuración!" + +msgctxt "error:staff.wage_type:" +msgid "Invalid formula [ %s ] for unit price!" +msgstr "La fórmula para precio unitario [ %s ] es inválida!" + +msgctxt "field:company.employee,category:" +msgid "Category" +msgstr "Categoría" + +msgctxt "field:company.employee,mandatory_wages:" +msgid "Mandatory Wage" +msgstr "Concepto Obligatorio" + +msgctxt "field:company.employee,party_bank:" +msgid "Party Bank" +msgstr "Banco" + +msgctxt "field:company.employee,party_box_family:" +msgid "Party Box Family" +msgstr "Caja de Compensación Familiar" + +msgctxt "field:company.employee,party_health:" +msgid "Party Health" +msgstr "EPS" + +msgctxt "field:company.employee,party_retirement:" +msgid "Party Retirement" +msgstr "Fondo de Pension" + +msgctxt "field:company.employee,party_risk:" +msgid "Party Risk" +msgstr "ARL" + +msgctxt "field:employee_category-staff.wages,create_date:" +msgid "Create Date" +msgstr "Fecha de Creación" + +msgctxt "field:employee_category-staff.wages,create_uid:" +msgid "Create User" +msgstr "Creado por Usuario" + +msgctxt "field:employee_category-staff.wages,employee_category:" +msgid "Employee Category" +msgstr "Categoría de Empleado" + +msgctxt "field:employee_category-staff.wages,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:employee_category-staff.wages,rec_name:" +msgid "Record Name" +msgstr "Nombre" + +msgctxt "field:employee_category-staff.wages,wage_type:" +msgid "Wage Type" +msgstr "Tipo de Pago" + +msgctxt "field:employee_category-staff.wages,write_date:" +msgid "Write Date" +msgstr "Fecha de Modificación" + +msgctxt "field:employee_category-staff.wages,write_uid:" +msgid "Write User" +msgstr "Modificado por Usuario" + +msgctxt "field:staff.configuration,create_date:" +msgid "Create Date" +msgstr "Fecha de Creación" + +msgctxt "field:staff.configuration,create_uid:" +msgid "Create User" +msgstr "Creado por Usuario" + +msgctxt "field:staff.configuration,default_hour_workday:" +msgid "Default Hour Workday" +msgstr "Día Laboral por Defecto" + +msgctxt "field:staff.configuration,default_journal:" +msgid "Default Journal" +msgstr "Libro Contable por Defecto" + +msgctxt "field:staff.configuration,default_liquidation_period:" +msgid "Default Liquidation Period" +msgstr "Periodo de Liquidación por Defecto" + +msgctxt "field:staff.configuration,default_restday:" +msgid "Default Restday" +msgstr "Descanso por Defecto" + +msgctxt "field:staff.configuration,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:staff.configuration,minimum_salary:" +msgid "Minimum Salary" +msgstr "" + +msgctxt "field:staff.configuration,rec_name:" +msgid "Name" +msgstr "Nombre" + +msgctxt "field:staff.configuration,staff_payroll_sequence:" +msgid "Payroll Sequence" +msgstr "Secuencia de Nómina" + +msgctxt "field:staff.configuration,week_hours_work:" +msgid "Week Hours Work" +msgstr "Horas de Semanales" + +msgctxt "field:staff.configuration,write_date:" +msgid "Write Date" +msgstr "Modificado por Usuario" + +msgctxt "field:staff.configuration,write_uid:" +msgid "Write User" +msgstr "Módificado por Usuario" + +msgctxt "field:staff.configuration.sequence,staff_payroll_sequence:" +msgid "Staff Payroll Sequence" +msgstr "" + +msgctxt "field:staff.employee_category,code:" +msgid "Code" +msgstr "Código" + +msgctxt "field:staff.employee_category,create_date:" +msgid "Create Date" +msgstr "Fecha de Creación" + +msgctxt "field:staff.employee_category,create_uid:" +msgid "Create User" +msgstr "Creado por Usuario" + +msgctxt "field:staff.employee_category,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:staff.employee_category,name:" +msgid "Name" +msgstr "Nombre" + +msgctxt "field:staff.employee_category,rec_name:" +msgid "Record Name" +msgstr "Nombre" + +msgctxt "field:staff.employee_category,wages_default:" +msgid "Category - Wages Default" +msgstr "Categoría - Pagos por Defecto" + +msgctxt "field:staff.employee_category,write_date:" +msgid "Write Date" +msgstr "Fecha Modificación" + +msgctxt "field:staff.employee_category,write_uid:" +msgid "Write User" +msgstr "Módificado por Usuario" + +msgctxt "field:staff.payroll,company:" +msgid "Company" +msgstr "Compañia" + +msgctxt "field:staff.payroll,contract:" +msgid "Contract" +msgstr "Contrato" + +msgctxt "field:staff.payroll,create_date:" +msgid "Create Date" +msgstr "Fecha de Creación" + +msgctxt "field:staff.payroll,create_uid:" +msgid "Create User" +msgstr "Creado por Usuario" + +msgctxt "field:staff.payroll,currency:" +msgid "Currency" +msgstr "Moneda" + +msgctxt "field:staff.payroll,date_effective:" +msgid "Date Effective" +msgstr "Fecha Efectiva" + +msgctxt "field:staff.payroll,description:" +msgid "Description" +msgstr "Descripción" + +msgctxt "field:staff.payroll,employee:" +msgid "Employee" +msgstr "Empleado" + +msgctxt "field:staff.payroll,end:" +msgid "End" +msgstr "Fecha Final" + +msgctxt "field:staff.payroll,gross_payments:" +msgid "Gross Payments" +msgstr "Pago Bruto" + +msgctxt "field:staff.payroll,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:staff.payroll,journal:" +msgid "Journal" +msgstr "Libro Diario" + +msgctxt "field:staff.payroll,kind:" +msgid "Kind" +msgstr "Clase" + +msgctxt "field:staff.payroll,lines:" +msgid "Wage Line" +msgstr "Linea" + +msgctxt "field:staff.payroll,move:" +msgid "Move" +msgstr "Asiento" + +msgctxt "field:staff.payroll,net_payment:" +msgid "Net Payment" +msgstr "Pago Neto" + +msgctxt "field:staff.payroll,notes:" +msgid "Notes" +msgstr "Observaciones" + +msgctxt "field:staff.payroll,number:" +msgid "Number" +msgstr "Número" + +msgctxt "field:staff.payroll,origin:" +msgid "Origin" +msgstr "Origen" + +msgctxt "field:staff.payroll,period:" +msgid "Period" +msgstr "Período" + +msgctxt "field:staff.payroll,rec_name:" +msgid "Record Name" +msgstr "Nombre" + +msgctxt "field:staff.payroll,start:" +msgid "Start" +msgstr "Fecha Inicial" + +msgctxt "field:staff.payroll,state:" +msgid "State" +msgstr "Estado" + +msgctxt "field:staff.payroll,total_cost:" +msgid "Total Cost" +msgstr "Costo Total" + +msgctxt "field:staff.payroll,total_deductions:" +msgid "Total Deductions" +msgstr "Total Deducciones" + +msgctxt "field:staff.payroll,worked_days:" +msgid "Worked Days" +msgstr "Días Trabajados" + +msgctxt "field:staff.payroll,write_date:" +msgid "Write Date" +msgstr "Fecha de Modificación" + +msgctxt "field:staff.payroll,write_uid:" +msgid "Write User" +msgstr "Modificado por Usuario" + +msgctxt "field:staff.payroll.line,amount:" +msgid "Amount" +msgstr "Valor" + +msgctxt "field:staff.payroll.line,create_date:" +msgid "Create Date" +msgstr "Fecha de Creación" + +msgctxt "field:staff.payroll.line,create_uid:" +msgid "Create User" +msgstr "Creado por Usuario" + +msgctxt "field:staff.payroll.line,description:" +msgid "Description" +msgstr "Descripción" + +msgctxt "field:staff.payroll.line,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:staff.payroll.line,party:" +msgid "Party" +msgstr "Tercero" + +msgctxt "field:staff.payroll.line,payroll:" +msgid "Payroll" +msgstr "Nomina" + +msgctxt "field:staff.payroll.line,quantity:" +msgid "Quantity" +msgstr "Cantidad" + +msgctxt "field:staff.payroll.line,rec_name:" +msgid "Record Name" +msgstr "Nombre" + +msgctxt "field:staff.payroll.line,receipt:" +msgid "Print Receipt" +msgstr "Imprimir Recibo" + +msgctxt "field:staff.payroll.line,reconciled:" +msgid "Reconciled" +msgstr "Conciliado" + +msgctxt "field:staff.payroll.line,sequence:" +msgid "Sequence" +msgstr "Secuencia" + +msgctxt "field:staff.payroll.line,unit_value:" +msgid "Unit Value" +msgstr "Valor Unitario" + +msgctxt "field:staff.payroll.line,uom:" +msgid "Unit" +msgstr "Unidad" + +msgctxt "field:staff.payroll.line,wage_type:" +msgid "Wage Type" +msgstr "Tipo de Pago" + +msgctxt "field:staff.payroll.line,write_date:" +msgid "Write Date" +msgstr "Modificado por Usuario" + +msgctxt "field:staff.payroll.line,write_uid:" +msgid "Write User" +msgstr "Módificado por Usuario" + +msgctxt "field:staff.payroll.mandatory_wage,create_date:" +msgid "Create Date" +msgstr "Fecha de Creación" + +msgctxt "field:staff.payroll.mandatory_wage,create_uid:" +msgid "Create User" +msgstr "Creado por Usuario" + +msgctxt "field:staff.payroll.mandatory_wage,employee:" +msgid "Employee" +msgstr "Empleado" + +msgctxt "field:staff.payroll.mandatory_wage,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:staff.payroll.mandatory_wage,party:" +msgid "Party" +msgstr "Tercero" + +msgctxt "field:staff.payroll.mandatory_wage,rec_name:" +msgid "Record Name" +msgstr "Nombre" + +msgctxt "field:staff.payroll.mandatory_wage,wage_type:" +msgid "Wage Type" +msgstr "Tipo de Pago" + +msgctxt "field:staff.payroll.mandatory_wage,write_date:" +msgid "Write Date" +msgstr "Modificado por Usuario" + +msgctxt "field:staff.payroll.mandatory_wage,write_uid:" +msgid "Write User" +msgstr "Modificado por Usuario" + +msgctxt "field:staff.payroll.period,create_date:" +msgid "Create Date" +msgstr "Fecha de Creación" + +msgctxt "field:staff.payroll.period,create_uid:" +msgid "Create User" +msgstr "Creado por Usuario" + +msgctxt "field:staff.payroll.period,description:" +msgid "Description" +msgstr "Descripción" + +msgctxt "field:staff.payroll.period,end:" +msgid "End" +msgstr "Fecha Final" + +msgctxt "field:staff.payroll.period,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:staff.payroll.period,name:" +msgid "Name" +msgstr "Nombre" + +msgctxt "field:staff.payroll.period,rec_name:" +msgid "Record Name" +msgstr "Nombre" + +msgctxt "field:staff.payroll.period,sequence:" +msgid "Sequence" +msgstr "Secuencia" + +msgctxt "field:staff.payroll.period,start:" +msgid "Start" +msgstr "Fecha Inicial" + +msgctxt "field:staff.payroll.period,state:" +msgid "State" +msgstr "Estado" + +msgctxt "field:staff.payroll.period,write_date:" +msgid "Write Date" +msgstr "Modificado por Usuario" + +msgctxt "field:staff.payroll.period,write_uid:" +msgid "Write User" +msgstr "Módificado por Usuario" + +msgctxt "field:staff.payroll_global.start,company:" +msgid "Company" +msgstr "Compañia" + +msgctxt "field:staff.payroll_global.start,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:staff.payroll_group.start,company:" +msgid "Company" +msgstr "Compañia" + +msgctxt "field:staff.payroll_group.start,description:" +msgid "Description" +msgstr "Descripción" + +msgctxt "field:staff.payroll_group.start,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:staff.payroll_group.start,period:" +msgid "Period" +msgstr "Período" + +msgctxt "field:staff.payroll_group.start,wage_types:" +msgid "Wage Types" +msgstr "Tipos de Pago" + +msgctxt "field:staff.position,workday_definition:" +msgid "Workday Definition" +msgstr "Definición Dia Laboral" + +msgctxt "field:staff.wage_type,active:" +msgid "Active" +msgstr "Activo" + +msgctxt "field:staff.wage_type,code:" +msgid "Code" +msgstr "Código" + +msgctxt "field:staff.wage_type,company:" +msgid "Company" +msgstr "Empresa" + +msgctxt "field:staff.wage_type,concepts_salary:" +msgid "Concepts Salary" +msgstr "Conceptos Salario" + +msgctxt "field:staff.wage_type,contract_finish:" +msgid "Contract Finish" +msgstr "Finalización de Contrato" + +msgctxt "field:staff.wage_type,create_date:" +msgid "Create Date" +msgstr "Fecha de Creación" + +msgctxt "field:staff.wage_type,create_uid:" +msgid "Create User" +msgstr "Creado por Usuario" + +msgctxt "field:staff.wage_type,credit_account:" +msgid "Credit Account" +msgstr "Cuenta Crédito" + +msgctxt "field:staff.wage_type,debit_account:" +msgid "Debit Account" +msgstr "Cuenta Débito" + +msgctxt "field:staff.wage_type,deduction_account:" +msgid "Deduction Account" +msgstr "Cuenta de la Deducción" + +msgctxt "field:staff.wage_type,default_quantity:" +msgid "Default Quantity" +msgstr "Cantidad por Defecto" + +msgctxt "field:staff.wage_type,definition:" +msgid "Definition" +msgstr "Definición" + +msgctxt "field:staff.wage_type,expense_formula:" +msgid "Expense Formula" +msgstr "Fórmula del Gasto" + +msgctxt "field:staff.wage_type,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:staff.wage_type,limit_days:" +msgid "Limit Days" +msgstr "Limite Días" + +msgctxt "field:staff.wage_type,name:" +msgid "Name" +msgstr "Nombre" + +msgctxt "field:staff.wage_type,parent:" +msgid "Parent" +msgstr "Padre" + +msgctxt "field:staff.wage_type,party_required:" +msgid "Party Required" +msgstr "Tercero obligatorio" + +msgctxt "field:staff.wage_type,rec_name:" +msgid "Record Name" +msgstr "Nombre" + +msgctxt "field:staff.wage_type,receipt:" +msgid "Receipt" +msgstr "Recibo" + +msgctxt "field:staff.wage_type,salary_constitute:" +msgid "Salary Constitute" +msgstr "Constituye Salario" + +msgctxt "field:staff.wage_type,sequence:" +msgid "Sequence" +msgstr "Secuencia" + +msgctxt "field:staff.wage_type,type_concept:" +msgid "Type Concept" +msgstr "Tipo de Concepto" + +msgctxt "field:staff.wage_type,unit_price_formula:" +msgid "Unit Price Formula" +msgstr "Fórmula del Precio Unitario" + +msgctxt "field:staff.wage_type,uom:" +msgid "UOM" +msgstr "UdM" + +msgctxt "field:staff.wage_type,write_date:" +msgid "Write Date" +msgstr "Fecha de Modificación" + +msgctxt "field:staff.wage_type,write_uid:" +msgid "Write User" +msgstr "Modificado por Usuario" + +msgctxt "field:staff.wage_type-staff.wage_type,child:" +msgid "Child" +msgstr "Hijo" + +msgctxt "field:staff.wage_type-staff.wage_type,create_date:" +msgid "Create Date" +msgstr "Fecha de Creación" + +msgctxt "field:staff.wage_type-staff.wage_type,create_uid:" +msgid "Create User" +msgstr "Creado por Usuario" + +msgctxt "field:staff.wage_type-staff.wage_type,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:staff.wage_type-staff.wage_type,parent:" +msgid "Parent" +msgstr "Padre" + +msgctxt "field:staff.wage_type-staff.wage_type,rec_name:" +msgid "Record Name" +msgstr "Nombre" + +msgctxt "field:staff.wage_type-staff.wage_type,write_date:" +msgid "Write Date" +msgstr "Modificado por Usuario" + +msgctxt "field:staff.wage_type-staff.wage_type,write_uid:" +msgid "Write User" +msgstr "Modificado por Usuario" + +msgctxt "field:staff.workday_definition,create_date:" +msgid "Create Date" +msgstr "Fecha de Creación" + +msgctxt "field:staff.workday_definition,create_uid:" +msgid "Create User" +msgstr "Creado por Usuario" + +msgctxt "field:staff.workday_definition,id:" +msgid "ID" +msgstr "ID" + +msgctxt "field:staff.workday_definition,note:" +msgid "Note" +msgstr "Nota" + +msgctxt "field:staff.workday_definition,position:" +msgid "Position" +msgstr "Cargo" + +msgctxt "field:staff.workday_definition,rec_name:" +msgid "Record Name" +msgstr "Nombre" + +msgctxt "field:staff.workday_definition,restday:" +msgid "Restday" +msgstr "Descanso" + +msgctxt "field:staff.workday_definition,weekday:" +msgid "Weekday" +msgstr "Día de la Semana" + +msgctxt "field:staff.workday_definition,workday:" +msgid "Workday" +msgstr "Dia laboral" + +msgctxt "field:staff.workday_definition,write_date:" +msgid "Write Date" +msgstr "Modificado por Usuario" + +msgctxt "field:staff.workday_definition,write_uid:" +msgid "Write User" +msgstr "Módificado por Usuario" + +msgctxt "help:staff.configuration,default_hour_workday:" +msgid "In hours" +msgstr "En horas" + +msgctxt "help:staff.configuration,default_liquidation_period:" +msgid "In days" +msgstr "En días" + +msgctxt "help:staff.configuration,default_restday:" +msgid "In hours" +msgstr "En horas" + +msgctxt "help:staff.configuration,week_hours_work:" +msgid "In hours" +msgstr "En horas" + +msgctxt "help:staff.payroll,kind:" +msgid "Special allow overlap dates with another payroll" +msgstr "Periodo especial permiter sobrelapar fechas" + +msgctxt "help:staff.payroll,number:" +msgid "Secuence" +msgstr "Secuencia" + +msgctxt "help:staff.wage_type,expense_formula:" +msgid "Python expression for eval(expr)" +msgstr "Expresión Python" + +msgctxt "help:staff.wage_type,unit_price_formula:" +msgid "Python expression for eval(expr)" +msgstr "Expresión Python" + +msgctxt "model:employee_category-staff.wages,name:" +msgid "Employee Category - Wages Default" +msgstr "Tipos de pago por defecto" + +msgctxt "model:ir.action,name:" +msgid "Create Lines Parties Payroll" +msgstr "Nómina Consolidada" + +msgctxt "model:ir.action,name:act_create_mandatory_wages" +msgid "Create Mandatory Wages" +msgstr "Crear Conceptos Obligatorios" + +msgctxt "model:ir.action,name:act_employee_category_tree" +msgid "Employee Category" +msgstr "Categoría de Empleado" + +msgctxt "model:ir.action,name:act_payroll_form" +msgid "Payroll" +msgstr "Nomina" + +msgctxt "model:ir.action,name:act_payroll_open_period" +msgid "Open Period" +msgstr "Abrir Periodo" + +msgctxt "model:ir.action,name:act_payroll_preliquidation" +msgid "Preliquidation" +msgstr "Preliquidación" + +msgctxt "model:ir.action,name:act_payroll_recompute" +msgid "Recompute Payroll" +msgstr "Recalcular Valores" + +msgctxt "model:ir.action,name:act_period_tree" +msgid "Period" +msgstr "Período" + +msgctxt "model:ir.action,name:act_wage_type_tree" +msgid "Wage Type" +msgstr "Tipo de Pago" + +msgctxt "model:ir.action,name:report_staff_payroll" +msgid "Payroll" +msgstr "Nomina" + +msgctxt "model:ir.action,name:wizard_create_payroll_group" +msgid "Create Payroll Group" +msgstr "Crear Nómina de Grupo" + +msgctxt "model:ir.action.act_window.domain,name:act_payroll_form_domain_all" +msgid "All" +msgstr "Todo" + +msgctxt "model:ir.action.act_window.domain,name:act_payroll_form_domain_draft" +msgid "Draft" +msgstr "Borrador" + +msgctxt "" +"model:ir.action.act_window.domain,name:act_payroll_form_domain_processed" +msgid "Processed" +msgstr "Procesado" + +msgctxt "model:ir.sequence,name:sequence_staff_payroll" +msgid "Payroll" +msgstr "Nomina" + +msgctxt "model:ir.sequence.type,name:sequence_type_payroll" +msgid "Staff Payroll" +msgstr "Nomina" + +msgctxt "model:ir.ui.menu,name:" +msgid "Payroll Global Wizard" +msgstr "Nómina Consolidada" + +msgctxt "model:ir.ui.menu,name:menu_create_payroll_group" +msgid "Create Payroll Group" +msgstr "Crear Nómina de Grupo" + +msgctxt "model:ir.ui.menu,name:menu_staff_configuration_employee_category" +msgid "Employee Categories" +msgstr "Categorías de Empleados" + +msgctxt "model:ir.ui.menu,name:menu_staff_payroll" +msgid "Payroll" +msgstr "Nomina" + +msgctxt "model:ir.ui.menu,name:menu_staff_period" +msgid "Period" +msgstr "Período" + +msgctxt "model:ir.ui.menu,name:menu_staff_wage_type" +msgid "Wage Type" +msgstr "Tipo de Pago" + +msgctxt "model:staff.configuration,name:" +msgid "Staff Configuration" +msgstr "Configuración" + +msgctxt "model:staff.employee_category,name:" +msgid "Employee Category" +msgstr "Categoría de Empleado" + +msgctxt "model:staff.payroll,name:" +msgid "Staff Payroll" +msgstr "Nomina" + +msgctxt "model:staff.payroll.line,name:" +msgid "Payroll Line" +msgstr "Línea de Nomina" + +msgctxt "model:staff.payroll.mandatory_wage,name:" +msgid "Mandatory Wage" +msgstr "Conceptos Obligatorios" + +msgctxt "model:staff.payroll.period,name:" +msgid "Period" +msgstr "Período" + +msgctxt "model:staff.payroll_global.start,name:" +msgid "Payroll Global Start" +msgstr "Nómina Global" + +msgctxt "model:staff.payroll_group.start,name:" +msgid "Payroll Group Start" +msgstr "Crear Nómina de Grupo" + +msgctxt "model:staff.wage_type,name:" +msgid "Wage Type" +msgstr "Tipo de Pago" + +msgctxt "model:staff.wage_type-staff.wage_type,name:" +msgid "Wage Type Salary" +msgstr "Tipo de Concepto - Salario" + +msgctxt "model:staff.workday_definition,name:" +msgid "Workday Definition" +msgstr "Definición Dia Laboral" + +msgctxt "report:staff.payroll:" +msgid ":" +msgstr ":" + +msgctxt "report:staff.payroll:" +msgid "AL" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "AUTORIZÓ" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "CANT." +msgstr "CANT." + +msgctxt "report:staff.payroll:" +msgid "CARGO:" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "CIUDAD:" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "CONCEPTO" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "DE NOMINA" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "DEDUCCIÓN" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "DESCUENTO" +msgstr "DESCUENTO" + +msgctxt "report:staff.payroll:" +msgid "DIAS LABORADOS" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "DOCUMENTO:" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "ELABORÓ" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "EMPLEADO:" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "FECHA:" +msgstr "FECHA:" + +msgctxt "report:staff.payroll:" +msgid "NETO" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "NIT:" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "N°" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "PAGO" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "RECIBO" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "RECIBÍ" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "SALARIO" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "SALARIO BRUTO" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "SALARIO:" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "TERCERO" +msgstr "" + +msgctxt "report:staff.payroll:" +msgid "UDM" +msgstr "" + +msgctxt "selection:staff.payroll,kind:" +msgid "Normal" +msgstr "Normal" + +msgctxt "selection:staff.payroll,kind:" +msgid "Special" +msgstr "Especial" + +msgctxt "selection:staff.payroll,state:" +msgid "Cancel" +msgstr "Cancelar" + +msgctxt "selection:staff.payroll,state:" +msgid "Draft" +msgstr "Borrador" + +msgctxt "selection:staff.payroll,state:" +msgid "Posted" +msgstr "Contabilizado" + +msgctxt "selection:staff.payroll,state:" +msgid "Processed" +msgstr "Procesado" + +msgctxt "selection:staff.payroll.period,state:" +msgid "Closed" +msgstr "Cerrado" + +msgctxt "selection:staff.payroll.period,state:" +msgid "Draft" +msgstr "Borrador" + +msgctxt "selection:staff.payroll.period,state:" +msgid "Open" +msgstr "Abierto" + +msgctxt "selection:staff.wage_type,definition:" +msgid "Deduction" +msgstr "Deducción" + +msgctxt "selection:staff.wage_type,definition:" +msgid "Discount" +msgstr "Descuento" + +msgctxt "selection:staff.wage_type,definition:" +msgid "Payment" +msgstr "Pagos" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "" +msgstr "" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Allowance" +msgstr "Viáticos" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Bonus" +msgstr "Bonificación" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Box Family" +msgstr "Caja de Compesación Familiar" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Commission" +msgstr "Comisión" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Extras" +msgstr "Extras" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Food" +msgstr "Alimentación" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Health" +msgstr "Salud" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Other" +msgstr "Otro" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Retirement" +msgstr "Pensión" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Risk" +msgstr "ARL" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Salary" +msgstr "Salario" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Special" +msgstr "Especial" + +msgctxt "selection:staff.wage_type,type_concept:" +msgid "Transport" +msgstr "Transporte" + +msgctxt "selection:staff.workday_definition,weekday:" +msgid "Friday" +msgstr "Viernes" + +msgctxt "selection:staff.workday_definition,weekday:" +msgid "Monday" +msgstr "Lunes" + +msgctxt "selection:staff.workday_definition,weekday:" +msgid "Saturday" +msgstr "Sábado" + +msgctxt "selection:staff.workday_definition,weekday:" +msgid "Sunday" +msgstr "Domingo" + +msgctxt "selection:staff.workday_definition,weekday:" +msgid "Thursday" +msgstr "Jueves" + +msgctxt "selection:staff.workday_definition,weekday:" +msgid "Tuesday" +msgstr "Martes" + +msgctxt "selection:staff.workday_definition,weekday:" +msgid "Wednesday" +msgstr "Miércoles" + +msgctxt "view:company.employee:" +msgid "Mandatory Wages" +msgstr "Conceptos Obligatorios" + +msgctxt "view:staff.configuration:" +msgid "Staff Configuration" +msgstr "Configuración de Personal" + +msgctxt "view:staff.employee_category:" +msgid "Wages Default" +msgstr "Pagos por Defecto" + +msgctxt "view:staff.payroll.line:" +msgid "Payroll Line" +msgstr "Línea de Nomina" + +msgctxt "view:staff.payroll.line:" +msgid "Payroll Lines" +msgstr "Líneas de Nomina" + +msgctxt "view:staff.payroll.mandatory_wage:" +msgid "Mandatory Wages" +msgstr "Conceptos Obligatorios" + +msgctxt "view:staff.payroll.mandatory_wage:" +msgid "Party Payroll" +msgstr "Tercero Nómina" + +msgctxt "view:staff.payroll.period:" +msgid "Close" +msgstr "Cerrar" + +msgctxt "view:staff.payroll.period:" +msgid "Draft" +msgstr "Borrador" + +msgctxt "view:staff.payroll.period:" +msgid "Open" +msgstr "Abrir" + +msgctxt "view:staff.payroll:" +msgid "Cancel" +msgstr "Cancelar" + +msgctxt "view:staff.payroll:" +msgid "Draft" +msgstr "Borrador" + +msgctxt "view:staff.payroll:" +msgid "Information" +msgstr "Información Adicional" + +msgctxt "view:staff.payroll:" +msgid "Payroll Line" +msgstr "Línea de Nomina" + +msgctxt "view:staff.payroll:" +msgid "Post" +msgstr "Contabilizar" + +msgctxt "view:staff.payroll:" +msgid "Process" +msgstr "Procesar" + +msgctxt "view:staff.payroll_global.start:" +msgid "Payroll Global Wizard" +msgstr "Nómina Consolidada" + +msgctxt "view:staff.payroll_group.start:" +msgid "Payroll Group" +msgstr "Nomina Completa" + +msgctxt "view:staff.position:" +msgid "Create Workdays" +msgstr "Crear Dias Laborales" + +msgctxt "view:staff.wage_type:" +msgid "Wage Type" +msgstr "Tipo de Pago" + +msgctxt "view:staff.workday_definition:" +msgid "Workday Definition" +msgstr "Definición Dia Laboral" + +msgctxt "view:staff.workday_definition:" +msgid "Workday Definiton" +msgstr "Definición Dia Laboral" + +msgctxt "wizard_button:staff.payroll.global,start,end:" +msgid "Cancel" +msgstr "Cancelar" + +msgctxt "wizard_button:staff.payroll.global,start,print_:" +msgid "Print" +msgstr "Imprimir" + +msgctxt "wizard_button:staff.payroll_group,start,end:" +msgid "Cancel" +msgstr "Cancelar" + +msgctxt "wizard_button:staff.payroll_group,start,open_:" +msgid "Accept" +msgstr "Aceptar" diff --git a/payroll.odt b/payroll.odt new file mode 100644 index 0000000..763c2bf Binary files /dev/null and b/payroll.odt differ diff --git a/payroll.py b/payroll.py new file mode 100644 index 0000000..5b7f75f --- /dev/null +++ b/payroll.py @@ -0,0 +1,888 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +import datetime +from decimal import Decimal + +from trytond.model import Workflow, ModelView, ModelSQL, fields +from trytond.pyson import Bool, Eval, If, Id +from trytond.pool import Pool, PoolMeta +from trytond.transaction import Transaction +from trytond.modules.company import CompanyReport +from trytond.wizard import Wizard, StateView, StateTransition, Button + +__all__ = ['Payroll', 'PayrollLine', 'PayrollReport', 'Move', + 'PayrollGroupStart', 'PayrollGroup', 'PayrollPreliquidation', + 'PayrollRecompute'] + +STATES = {'readonly': (Eval('state') != 'draft')} + +_DEFAULT_WORK_DAY = 8 +_ZERO = Decimal('0.0') + + +class Payroll(Workflow, ModelSQL, ModelView): + "Staff Payroll" + __name__ = "staff.payroll" + _rec_name = 'number' + number = fields.Char('Number', readonly=True, help="Secuence", + select=True) + period = fields.Many2One('staff.payroll.period', 'Period', + required=True, states={ + 'readonly': Eval('state') != 'draft', + }) + employee = fields.Many2One('company.employee', 'Employee', + states=STATES, required=True, depends=['state'], select=True) + kind = fields.Selection([ + ('normal', 'Normal'), + ('special', 'Special'), + ], 'Kind', required=True, select=True, states=STATES, + help="Special allow overlap dates with another payroll") + contract = fields.Many2One('staff.contract', 'Contract', + select=True, domain=[ + ('employee', '=', Eval('employee')), + ]) + start = fields.Date('Start', states=STATES, required=True) + end = fields.Date('End', states=STATES, required=True) + date_effective = fields.Date('Date Effective', states=STATES, + required=True) + description = fields.Char('Description', states=STATES, select=True) + lines = fields.One2Many('staff.payroll.line', 'payroll', 'Wage Line', + states=STATES, depends=['employee', 'state']) + gross_payments = fields.Function(fields.Numeric('Gross Payments', + digits=(16, 2), depends=['lines', 'states']), 'on_change_with_amount') + total_deductions = fields.Function(fields.Numeric( + 'Total Deductions', digits=(16, 2), depends=['lines', 'states']), + 'on_change_with_amount') + net_payment = fields.Function(fields.Numeric('Net Payment', + digits=(16, 2), depends=['lines', 'states']), + 'get_net_payment') + total_cost = fields.Function(fields.Numeric('Total Cost', + digits=(16, 2), depends=['lines', 'states']), + 'get_total_cost') + currency = fields.Many2One('currency.currency', 'Currency', + required=False, + states={ + 'readonly': ((Eval('state') != 'draft') + | (Eval('lines', [0]) & Eval('currency'))), + }, depends=['state']) + worked_days = fields.Function(fields.Integer('Worked Days', + depends=['start', 'end', 'state']), + 'on_change_with_worked_days') + state = fields.Selection([ + ('draft', 'Draft'), + ('processed', 'Processed'), + ('cancel', 'Cancel'), + ('posted', 'Posted'), + ], 'State', readonly=True) + journal = fields.Many2One('account.journal', 'Journal', required=True, + states=STATES) + company = fields.Many2One('company.company', 'Company', required=True, + states={ + 'readonly': (Eval('state') != 'draft') | Eval('lines', [0]), + }, + domain=[ + ('id', If(Eval('context', {}).contains('company'), '=', '!='), + Eval('context', {}).get('company', 0)), + ], + depends=['state']) + move = fields.Many2One('account.move', 'Move', readonly=True) + origin = fields.Reference('Origin', selection='get_origin', + select=True, depends=['state'], + states={ + 'readonly': Eval('state') != 'draft', + }) + notes = fields.Text("Notes", states=STATES) + + @classmethod + def __setup__(cls): + super(Payroll, cls).__setup__() + cls._order = [ + ('period', 'DESC'), + ('start', 'DESC'), + ] + cls._error_messages.update({ + 'employee_without_salary': ('The employee does not have salary!'), + 'wrong_start_end': ('The date end can not smaller than date start, for employee %s'), + 'sequence_missing': ('Sequence Payroll is missing!'), + 'period_closed': ('Payroll period is closed!'), + 'payroll_exist_period': ('Already exist one payroll in this period with this contract!'), + 'wrong_date_consistent': ('The date start/end is repetead \ + or crossed with other date payroll'), + 'delete_cancel': ('Payroll "%s" must be cancelled before ' + 'deletion.'), + 'existing_move': ('Payroll "%s" has a move, must be deleted ' + 'before deletion.'), + 'bad_configuration_wage_type': ('Bad configuration of the wage type "%s".'), + }) + cls._transitions |= set(( + ('draft', 'cancel'), + ('cancel', 'draft'), + ('draft', 'processed'), + ('processed', 'posted'), + ('posted', 'draft'), + ('processed', 'draft'), + )) + cls._buttons.update({ + 'draft': { + 'invisible': Eval('state') == 'draft', + }, + 'post': { + 'invisible': Eval('state') != 'processed', + }, + 'cancel': { + 'invisible': Eval('state') != 'draft', + }, + 'process': { + 'invisible': Eval('state') != 'draft', + }, + }) + + @staticmethod + def default_company(): + return Transaction().context.get('company') + + @staticmethod + def default_kind(): + return 'normal' + + @staticmethod + def default_journal(): + Configuration = Pool().get('staff.configuration') + configuration = Configuration(1) + if configuration.default_journal: + return configuration.default_journal.id + + @staticmethod + def default_currency(): + Company = Pool().get('company.company') + if Transaction().context.get('company'): + company = Company(Transaction().context['company']) + return company.currency.id + + @classmethod + def delete(cls, records): + # Cancel before delete + cls.cancel(records) + for payroll in records: + if payroll.state != 'cancel': + cls.raise_user_error('delete_cancel', (payroll.rec_name,)) + if payroll.move: + cls.raise_user_error('existing_move', (payroll.rec_name,)) + super(Payroll, cls).delete(records) + + @classmethod + def validate(cls, payrolls): + super(Payroll, cls).validate(payrolls) + for payroll in payrolls: + payroll.check_start_end() + + @staticmethod + def default_state(): + return 'draft' + + @staticmethod + def _get_origin(): + 'Return list of Model names for origin Reference' + return [] + + @classmethod + def search_rec_name(cls, name, clause): + if clause[1].startswith('!') or clause[1].startswith('not '): + bool_op = 'AND' + else: + bool_op = 'OR' + return [bool_op, + ('employee',) + tuple(clause[1:]), + ('number',) + tuple(clause[1:]), + ] + + @classmethod + @ModelView.button + @Workflow.transition('draft') + def draft(cls, records): + pass + + @classmethod + @ModelView.button + @Workflow.transition('cancel') + def cancel(cls, records): + pass + + @classmethod + @ModelView.button + @Workflow.transition('processed') + def process(cls, records): + Payroll = Pool().get('staff.payroll') + for payroll in records: + payrolls = Payroll.search([ + ('period', '=', payroll.period.id), + ('contract', '=', payroll.contract.id), + ]) + if len(payrolls) > 1: + cls.raise_user_error('payroll_exist_period',) + return + if payroll.period.state == 'closed': + cls.raise_user_error('period_closed',) + return + payroll.set_number() + + @classmethod + @ModelView.button + @Workflow.transition('posted') + def post(cls, records): + for payroll in records: + payroll.create_move() + + @fields.depends('start', 'end') + def on_change_start(self): + if not self.start: + return + Configuration = Pool().get('staff.configuration') + configuration = Configuration(1) + period_days = configuration.default_liquidation_period + if period_days and not self.end: + self.end = self.start + datetime.timedelta(period_days - 1) + + @classmethod + def get_origin(cls): + Model = Pool().get('ir.model') + models = cls._get_origin() + models = Model.search([ + ('model', 'in', models), + ]) + return [(None, '')] + [(m.model, m.name) for m in models] + + def set_number(self): + if self.number: + return + pool = Pool() + Sequence = pool.get('ir.sequence') + Configuration = pool.get('staff.configuration') + configuration = Configuration(1) + if not configuration.staff_payroll_sequence: + self.raise_user_error('sequence_missing',) + seq = configuration.staff_payroll_sequence.id + self.write([self], {'number': Sequence.get_id(seq)}) + + @fields.depends('start', 'end') + def on_change_with_worked_days(self, name=None): + if self.start and self.end and self.employee: + return self.get_days(self.start, self.end) + + def get_salary_full(self, wage): + """ + Return a dict with sum of total amount of all wages defined + as salary on context of wage + """ + salary_full = self.compute_salary_full(wage) + return {'salary': salary_full} + + def compute_salary_full(self, wage): + wages_ids = [s.id for s in wage.concepts_salary] + wages_names = [s.name for s in wage.concepts_salary] + if wage.amount_required: + salary_full = self.employee.get_defect_amount_wage_type(wage.id) + elif wages_ids: + salary_full = sum([line.amount for line in self.lines + if line.wage_type.id in wages_ids]) + else: + salary_full = self.employee.salary or 0 + return salary_full + + def create_move(self): + pool = Pool() + Move = pool.get('account.move') + Period = pool.get('account.period') + if self.move: + return + period_id = Period.find(self.company.id, date=self.date_effective) + move_lines = self.get_moves_lines() + move, = Move.create([{ + 'journal': self.journal.id, + 'origin': str(self), + 'period': period_id, + 'date': self.date_effective, + 'state': 'draft', + 'description': self.description, + 'lines': [('create', move_lines)], + }]) + self.write([self], {'move': move.id}) + Move.post([self.move]) + + def get_moves_lines(self): + lines_moves = {} + mandatory_wages = dict([(m.wage_type.id, m.party) + for m in self.employee.mandatory_wages + ]) + + for line in self.lines: + if line.amount <= 0: + continue + if line.party: + party = line.party + else: + if mandatory_wages.get(line.wage_type.id): + party = mandatory_wages[line.wage_type.id] + else: + party = self.employee.party + + + expense = Decimal(0) + if not line.wage_type: + continue + if line.wage_type.expense_formula: + salary_args = self.get_salary_full(line.wage_type) + expense = line.wage_type.compute_expense(salary_args) + + if line.wage_type.definition == 'payment': + amount_debit = line.amount + expense + else: + if expense: + amount_debit = expense + elif line.wage_type.debit_account: + amount_debit = line.amount + + amount_credit = line.amount + expense + debit_acc = line.wage_type.debit_account + + try: + if debit_acc and amount_debit > _ZERO: + if line.wage_type.definition == 'discount': + amount_debit = amount_debit * (-1) + if debit_acc.id not in lines_moves.keys(): + lines_moves[debit_acc.id] = { + self.employee.party.id: line.get_move_line( + debit_acc, self.employee.party, ('debit', amount_debit) + )} + else: + line.update_move_line( + lines_moves[debit_acc.id][self.employee.party.id], + {'debit': amount_debit, 'credit': _ZERO} + ) + + credit_acc = line.wage_type.credit_account + if amount_credit > _ZERO: + line_credit_ready = False + if credit_acc: + if credit_acc.id not in lines_moves.keys(): + lines_moves[credit_acc.id] = { + party.id: line.get_move_line( + credit_acc, party, ('credit', amount_credit) + )} + line_credit_ready = True + else: + if party.id not in lines_moves[credit_acc.id].keys(): + lines_moves[credit_acc.id].update({ + party.id: line.get_move_line( + credit_acc, party, ('credit', amount_credit) + )}) + line_credit_ready = True + if line.wage_type.definition != 'payment': + deduction_acc = line.wage_type.deduction_account + if deduction_acc: + if deduction_acc.id not in lines_moves.keys(): + lines_moves[deduction_acc.id] = { + self.employee.party.id: line.get_move_line( + deduction_acc, self.employee.party, ('credit', -line.amount), + )} + line_credit_ready = True + else: + lines_moves[deduction_acc.id][self.employee.party.id]['credit'] -= line.amount + + if credit_acc and not line_credit_ready: + lines_moves[credit_acc.id][party.id]['credit'] += amount_credit + except: + self.raise_user_error('bad_configuration_wage_type', line.wage_type.name) + + result = [] + for r in lines_moves.values(): + _line = r.values() + if _line[0]['debit'] > 0 and _line[0]['credit'] > 0: + new_value = _line[0]['debit'] - _line[0]['credit'] + if new_value >= 0: + _line[0]['debit'] = new_value + _line[0]['credit'] = 0 + else: + _line[0]['credit'] = new_value + _line[0]['debit'] = 0 + result.extend(_line) + return result + + def _create_payroll_lines(self, wages, extras, discounts=None): + PayrollLine = Pool().get('staff.payroll.line') + config = Pool().get('staff.configuration')(1) + values = [] + salary_args = {} + for wage, party in wages: + if wage.salary_constitute: + if wage.amount_required: + salary_args = self.get_salary_full(wage) + else: + salary_args['salary'] = self.employee.salary + else: + salary_args = self.get_salary_full(wage) + + if config and config.minimum_salary and wage.type_concept == 'transport' and \ + self.employee.salary >= (config.minimum_salary * 2): + unit_value = 0 + else: + unit_value = wage.compute_unit_price(salary_args) + + discount = None + if discounts.get(wage.id): + discount = discounts.get(wage.id) + qty = self.get_line_quantity_special(wage) + if qty == 0: + qty = self.get_line_quantity(wage, self.start, self.end, + extras, discount) + values.append(self.get_line(wage, qty, unit_value, party)) + PayrollLine.create(values) + + def set_preliquidation(self, extras, discounts=None): + wage_salary = [] + wage_no_salary = [] + + for concept in self.employee.mandatory_wages: + if concept.wage_type.salary_constitute: + wage_salary.append((concept.wage_type, concept.party)) + else: + wage_no_salary.append((concept.wage_type, concept.party)) + + self._create_payroll_lines(wage_salary, extras, discounts) + self._create_payroll_lines(wage_no_salary, extras, discounts) + + def update_preliquidation(self, extras): + for line in self.lines: + if not line.wage_type.salary_constitute: + salary_args = self.get_salary_full(line.wage_type) + unit_value = line.wage_type.compute_unit_price(salary_args) + line.write([line], { + 'unit_value': unit_value, + }) + + def get_line(self, wage, qty, unit_value, party=None): + res = { + 'sequence': wage.sequence, + 'payroll': self.id, + 'wage_type': wage.id, + 'description': wage.name, + 'quantity': qty, + 'unit_value': unit_value, + 'uom': wage.uom, + 'receipt': wage.receipt, + } + if party: + res['party'] = party.id + return res + + def _get_line_quantity(self, quantity_days, wage, extras, discount): + Configuration = Pool().get('staff.configuration') + configuration = Configuration(1) + default_hour_workday = configuration.default_hour_workday or _DEFAULT_WORK_DAY + quantity = wage.default_quantity or 0 + if quantity_days < 0: + quantity_days = 0 + if wage.uom.id == Id('product', 'uom_day').pyson(): + quantity = quantity_days + if discount: + quantity -= discount + elif wage.uom.id == Id('product', 'uom_hour').pyson(): + if wage.type_concept != 'extras': + quantity = quantity_days * default_hour_workday + if discount: + quantity -= discount + else: + key_ = [key for key in extras.keys() if wage.name.lower().count(key) > 0] + if key_: + key_ext = key_[0] + extras_ = extras.get(key_ext) + else: + extras_ = extras.get((wage.name.lower())) + if extras_ and self.employee.position and self.employee.position.extras: + quantity = extras_ + return quantity + + def get_line_quantity(self, wage, start=None, end=None, extras=None, discount=None): + quantity = wage.default_quantity or 0 + quantity_days = self.get_days(start, end) + quantity = self._get_line_quantity(quantity_days, wage, extras, discount) + return quantity + + def get_line_quantity_special(self, wage): + quantity_days = 0 + if self.contract and self.date_effective and wage.type_concept == 'special': + quantity_days = (self.date_effective - self.contract.start_date).days + if quantity_days > wage.limit_days: + quantity_days = wage.limit_days + return quantity_days + + @fields.depends('lines') + def on_change_with_amount(self, name=None): + res = [] + for line in self.lines: + if not line.amount: + continue + if name == 'gross_payments': + if line.wage_type.definition == 'payment' and line.receipt: + res.append(line.amount) + else: + if line.wage_type.definition != 'payment': + res.append(line.amount) + res = self.currency.round(sum(res)) + return res + + def get_net_payment(self, name=None): + return (self.gross_payments - self.total_deductions) + + def get_total_cost(self, name): + res = sum([line.amount for line in self.lines + if line.wage_type.definition == 'payment']) + return res + + def check_start_end(self): + if self.start <= self.end: + if self.kind != 'normal': + return + if self.start >= self.period.start and \ + self.end <= self.period.end: + return + if self.start >= self.period.start and \ + self.end == None: + return + self.raise_user_error('wrong_start_end', self.employee.party.name) + + @fields.depends('period', 'start', 'end', 'employee') + def on_change_period(self): + self.start = None + self.end = None + self.contract = None + self.description = None + if self.period: + self.start = self.period.start + self.end = self.period.end + self.contract = self.search_contract_on_period( + self.employee, self.period + ) + if not self.description: + self.description = self.period.description + + @classmethod + def search_contract_on_period(cls, employee, period): + Contract = Pool().get('staff.contract') + contracts = Contract.search([ + ('employee', '=', employee.id), ['AND', + ['OR', [ + ('start_date', '>=', period.start), + ('end_date', '<=', period.end), + ('end_date', '!=', None), + ], [ + ('start_date', '<=', period.start), + ('end_date', '>=', period.start), + ('end_date', '!=', None), + ], [ + ('start_date', '<=', period.end), + ('end_date', '>=', period.end), + ('end_date', '!=', None), + ], [ + ('start_date', '<=', period.start), + ('end_date', '>=', period.end), + ('end_date', '!=', None), + ], [ + ('start_date', '<=', period.start), + ('end_date', '=', None), + ], + [ + ('start_date', '>=', period.start), + ('start_date', '<=', period.end), + ('end_date', '=', None), + ], + ]] + ]) + if not contracts: + last_date_futhermore = employee.get_last_date_futhermore() + if last_date_futhermore and last_date_futhermore > period.start: + return employee.contract + return + values = dict([(c.end_date, c) for c in contracts]) + + last_contract = values[max(values.keys())] + + if last_contract.end_date: + if (last_contract.end_date >= period.start and + last_contract.end_date <= period.end) or \ + (last_contract.end_date >= period.end and + last_contract.end_date >= period.start): + return last_contract + else: + return last_contract + + + def get_days(self, start, end): + adjust = 1 + quantity_days = (end - start).days + adjust + if quantity_days < 0: + quantity_days = 0 + return quantity_days + + def recompute_lines(self): + for line in self.lines: + if not line.wage_type.concepts_salary: + continue + salary_args = self.get_salary_full(line.wage_type) + unit_value = line.wage_type.compute_unit_price(salary_args) + line.write([line], {'unit_value': unit_value}) + + +class PayrollLine(ModelSQL, ModelView): + "Payroll Line" + __name__ = "staff.payroll.line" + sequence = fields.Integer('Sequence') + payroll = fields.Many2One('staff.payroll', 'Payroll', + ondelete='CASCADE', select=True, required=True) + description = fields.Char('Description', required=True) + wage_type = fields.Many2One('staff.wage_type', 'Wage Type', + required=True, depends=['payroll']) + uom = fields.Many2One('product.uom', 'Unit', depends=['wage_type'], + states={'readonly': Bool(Eval('wage_type'))}) + quantity = fields.Numeric('Quantity', digits=(16, 2)) + unit_value = fields.Numeric('Unit Value', digits=(16, 2), + depends=['wage_type']) + amount = fields.Function(fields.Numeric('Amount', + digits=(16, 2), depends=['unit_value', 'quantity'], + states={ + 'readonly': ~Eval('_parent_payroll'), + }), 'get_amount') + receipt = fields.Boolean('Print Receipt') + reconciled = fields.Function(fields.Boolean('Reconciled'), + 'get_reconciled') + party = fields.Many2One('party.party', 'Party', depends=['wage_type']) + + @classmethod + def __setup__(cls): + super(PayrollLine, cls).__setup__() + cls._order.insert(0, ('sequence', 'ASC')) + + @staticmethod + def default_quantity(): + return Decimal(str(1)) + + @fields.depends('wage_type', 'uom', 'quantity', 'party', + 'description', 'unit_value', 'payroll', 'receipt', 'sequence', + '_parent_payroll.employee') + def on_change_wage_type(self): + if not self.wage_type: + return + self.uom = self.wage_type.uom.id + self.description = self.wage_type.name + self.quantity = self.wage_type.default_quantity + self.receipt = self.wage_type.receipt + self.sequence = self.wage_type.sequence + parties = [] + for wage in self.payroll.employee.mandatory_wages: + if wage.wage_type.id == self.wage_type.id and wage.party: + parties.append(wage.party.id) + if parties: + self.party = parties[0] + + if self.wage_type.unit_price_formula: + salary_args = self.payroll.get_salary_full(self.wage_type) + self.unit_value = self.wage_type.compute_unit_price( + salary_args) + + def get_amount(self, name): + return self.on_change_with_amount() + + @fields.depends('quantity', 'unit_value', '_parent_payroll.currency') + def on_change_with_amount(self): + quantity = 0 + unit_value = 0 + res = _ZERO + if self.quantity and self.unit_value: + quantity = float(self.quantity) + unit_value = float(self.unit_value) + res = Decimal(str(round((quantity * unit_value), 2))) + return res + + def get_reconciled(self, name=None): + #TODO: Reconciled must be computed from move line, similar way + # to account invoice + pass + + def get_move_line(self, account, party, amount): + debit = credit = _ZERO + if amount[0] == 'debit': + debit = amount[1] + else: + credit = amount[1] + + res = { + 'description': account.name, + 'debit': debit, + 'credit': credit, + 'account': account.id, + 'party': party.id, + } + return res + + def update_move_line(self, move_line, values): + if values['debit']: + move_line['debit'] += values['debit'] + + if values['credit']: + move_line['credit'] += values['credit'] + return move_line + + +class PayrollReport(CompanyReport): + __name__ = 'staff.payroll' + + @classmethod + def get_context(cls, records, data): + report_context = super(PayrollReport, cls).get_context(records, data) + return report_context + + +class PayrollGroupStart(ModelView): + 'Payroll Group Start' + __name__ = 'staff.payroll_group.start' + period = fields.Many2One('staff.payroll.period', 'Period', + required=True, domain=[('state', '=', 'open')]) + description = fields.Char('Description', required=True) + company = fields.Many2One('company.company', 'Company', + required=True) + wage_types = fields.Many2Many('staff.wage_type', None, None, + 'Wage Types') + + @staticmethod + def default_company(): + return Transaction().context.get('company') + + @fields.depends('period', 'description') + def on_change_period(self): + if not self.period: + return + if not self.description and self.period.description: + self.description = self.period.description + + +class PayrollGroup(Wizard): + 'Payroll Group' + __name__ = 'staff.payroll_group' + start = StateView('staff.payroll_group.start', + 'staff_payroll.payroll_group_start_view_form', [ + Button('Cancel', 'end', 'tryton-cancel'), + Button('Accept', 'open_', 'tryton-ok', default=True), + ]) + open_ = StateTransition() + + def transition_open_(self): + pool = Pool() + Employee = pool.get('company.employee') + Payroll = pool.get('staff.payroll') + Contract = pool.get('staff.contract') + + #Remove employees with payroll this period + payrolls_period = Payroll.search([ + ('period', '=', self.start.period.id), + ]) + employees_w_payroll = [p.employee.id for p in payrolls_period] + + dom_employees = self.get_employees_dom(employees_w_payroll) + payroll_to_create = [] + + for employee in Employee.search(dom_employees): + if not employee.contract: + continue + + start = self.start.period.start + end = self.start.period.end + contract = Payroll.search_contract_on_period(employee, self.start.period) + if not contract: + continue + + values = self.get_values(employee, start, end) + payroll_to_create.append(values) + + wages = [(wage_type, None) for wage_type in self.start.wage_types] + if payroll_to_create: + payrolls = Payroll.create(payroll_to_create) + for payroll in payrolls: + payroll.on_change_period() + payroll.set_preliquidation({}) + if wages: + payroll._create_payroll_lines(wages, None, {}) + return 'end' + + def get_employees_dom(self, employees_w_payroll): + dom_employees = [ + ('active', '=', True), + ('id', 'not in', employees_w_payroll), + ] + return dom_employees + + def get_values(self, employee, start_date, end_date): + Payroll = Pool().get('staff.payroll') + if employee.contract.start_date and \ + employee.contract.start_date >= start_date and \ + employee.contract.start_date <= start_date: + start_date = employee.contract.start_date + if employee.contract.end_date and \ + employee.contract.end_date >= start_date and \ + employee.contract.end_date <= end_date: + end_date = employee.contract.end_date + values = { + 'employee': employee.id, + 'period': self.start.period.id, + 'start': start_date, + 'end': end_date, + 'description': self.start.description, + 'date_effective': end_date, + 'contract': Payroll.search_contract_on_period( + employee, self.start.period + ) + } + return values + + +class PayrollPreliquidation(Wizard): + 'Payroll Preliquidation' + __name__ = 'staff.payroll.preliquidation' + start_state = 'create_preliquidation' + create_preliquidation = StateTransition() + + def transition_create_preliquidation(self): + Payroll = Pool().get('staff.payroll') + ids = Transaction().context['active_ids'] + for payroll in Payroll.browse(ids): + if payroll.state != 'draft': + return + if not payroll.lines: + payroll.set_preliquidation({}) + else: + payroll.update_preliquidation({}) + return 'end' + + +class PayrollRecompute(Wizard): + 'Payroll Recompute' + __name__ = 'staff.payroll.recompute' + start_state = 'do_recompute' + do_recompute = StateTransition() + + def transition_do_recompute(self): + Payroll = Pool().get('staff.payroll') + ids = Transaction().context['active_ids'] + for payroll in Payroll.browse(ids): + if payroll.state != 'draft' or not payroll.lines: + continue + payroll.recompute_lines() + return 'end' + + +class Move: + __metaclass__ = PoolMeta + __name__ = 'account.move' + + @classmethod + def _get_origin(cls): + return super(Move, cls)._get_origin() + ['staff.payroll'] diff --git a/payroll.xml b/payroll.xml new file mode 100644 index 0000000..208fd1d --- /dev/null +++ b/payroll.xml @@ -0,0 +1,141 @@ + + + + + + + Staff Payroll + staff.payroll + + + + + + + + + + + + + Payroll + staff.payroll + staff.payroll + staff_payroll/payroll.odt + + + form_print + staff.payroll,-1 + + + + + staff.payroll.line + tree + payroll_line_tree + + + staff.payroll.line + form + payroll_line_form + + + + staff.payroll + form + payroll_form + + + staff.payroll + tree + payroll_tree + + + Payroll + staff.payroll + + + + + + + + + + + + + Draft + + + + + + Processed + + + + + + All + + + + + + + + + + + + + + + + + + + + + + + + staff.payroll_group.start + form + payroll_create_group_form + + + Create Payroll Group + staff.payroll_group + + + + + Preliquidation + staff.payroll.preliquidation + + + form_action + staff.payroll,-1 + + + + + Recompute Payroll + staff.payroll.recompute + + + form_action + staff.payroll,-1 + + + + + diff --git a/payroll_en.odt b/payroll_en.odt new file mode 100644 index 0000000..8298a0b Binary files /dev/null and b/payroll_en.odt differ diff --git a/period.py b/period.py new file mode 100644 index 0000000..e8d91e5 --- /dev/null +++ b/period.py @@ -0,0 +1,123 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +from trytond.model import ModelView, Workflow, ModelSQL, fields +from trytond.pyson import Eval +from trytond.wizard import Wizard, StateTransition +from trytond.pool import Pool +from trytond.transaction import Transaction + +__all__ = ['Period', 'OpenPeriod'] + +STATES = {'readonly': Eval('state') != 'draft'} + + +class Period(Workflow, ModelSQL, ModelView): + "Period" + __name__ = 'staff.payroll.period' + _rec_name = 'name' + name = fields.Char('Name', select=True, states=STATES, + required=True) + sequence = fields.Integer('Sequence', select=True, states=STATES, + required=True) + start = fields.Date('Start', required=True, states=STATES, select=True, + domain=[('start', '<=', Eval('end', None))], + depends=['end']) + end = fields.Date('End', required=True, states=STATES, select=True, + domain=[('end', '>=', Eval('start', None))], + depends=['start']) + state = fields.Selection([ + ('draft', 'Draft'), + ('open', 'Open'), + ('closed', 'Closed'), + ], 'State', readonly=True) + description = fields.Char('Description', select=True, states=STATES) + + @classmethod + def __setup__(cls): + super(Period, cls).__setup__() + cls._order.insert(0, ('sequence', 'ASC')) + cls._error_messages.update({ + 'wrong_start_end': ('The date end can not smaller than date start'), + 'wrong_period_overlap': ('The period overlap another period!') + }) + cls._transitions |= set(( + ('draft', 'open'), + ('open', 'draft'), + ('open', 'closed'), + )) + cls._buttons.update({ + 'open': { + 'invisible': Eval('state') != 'draft', + }, + 'close': { + 'invisible': Eval('state') != 'open', + }, + 'draft': { + 'invisible': Eval('state') != 'open', + }, + }) + + @staticmethod + def default_state(): + return 'draft' + + @classmethod + @ModelView.button + @Workflow.transition('draft') + def draft(self, periods): + pass + + @classmethod + @ModelView.button + @Workflow.transition('open') + def open(self, periods): + pass + + @classmethod + @ModelView.button + @Workflow.transition('closed') + def close(self, periods): + pass + + @classmethod + def validate(cls, periods): + super(Period, cls).validate(periods) + for period in periods: + period.check_start_end() + period.check_date_consistent() + + def check_start_end(self): + if self.start > self.end: + self.raise_user_error('wrong_start_end',) + + def check_date_consistent(self): + periods = self.search([ + ('id', '!=', self.id), ['OR', + [ + ('start', '>=', self.start), + ('start', '<=', self.end), + ], [ + ('end', '>=', self.start), + ('end', '<=', self.end), + ], [ + ('start', '<=', self.start), + ('end', '>=', self.end), + ] + ]]) + if periods: + self.raise_user_error('wrong_period_overlap',) + + +class OpenPeriod(Wizard): + "Open Period" + __name__ = 'staff.payroll.open_period' + start_state = 'open_period' + open_period = StateTransition() + + def transition_open_period(self): + ids = Transaction().context['active_ids'] + Period = Pool().get('staff.payroll.period') + if ids: + period = Period(ids[0]) + Period.write([period], {'state': 'open'}) + return 'end' diff --git a/period.xml b/period.xml new file mode 100644 index 0000000..b69cb10 --- /dev/null +++ b/period.xml @@ -0,0 +1,66 @@ + + + + + + + staff.payroll.period + form + period_form + + + staff.payroll.period + tree + period_tree + + + + + Period + staff.payroll.period + + + + + + + + + + + + + + + + + Open Period + staff.payroll.open_period + + + form_action + staff.payroll.period,-1 + + + + + + + + + + + + + + + + + + + + + + diff --git a/position.py b/position.py new file mode 100644 index 0000000..1d77832 --- /dev/null +++ b/position.py @@ -0,0 +1,70 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +from trytond.model import ModelView, ModelSQL, fields +from trytond.pool import Pool, PoolMeta +from trytond.pyson import Eval, Bool + +__all__ = ['Position', 'WorkdayDefinition'] + + +class Position: + __metaclass__ = PoolMeta + __name__ = 'staff.position' + workday_definition = fields.One2Many('staff.workday_definition', + 'position', 'Workday Definition') + + @classmethod + def __setup__(cls): + super(Position, cls).__setup__() + cls._error_messages.update({ + 'missing_config_default': ('Missing default values for ' + 'workday or restday on configuration!')}) + cls._buttons.update({ + 'create_workdays': { + 'invisible': Bool(Eval('workday_definition')), + }, + }) + + def _default_workdays(self): + pool = Pool() + Workday = pool.get('staff.workday_definition') + Config = pool.get('staff.configuration') + config = Config(1) + if not config.default_hour_workday or not config.default_hour_workday: + self.raise_user_error('missing_config_default') + for day in Workday.weekday.selection: + values = { + 'position': self.id, 'weekday': day[0], + 'workday': config.default_hour_workday, + 'restday': config.default_restday + } + Workday.create([values]) + + @classmethod + @ModelView.button + def create_workdays(cls, records): + for rec in records: + rec._default_workdays() + + +class WorkdayDefinition(ModelSQL, ModelView): + 'Workday Definition' + __name__ = 'staff.workday_definition' + position = fields.Many2One('staff.position', 'Position', + required=True, ondelete='CASCADE') + workday = fields.Numeric('Workday', digits=(2, 2), required=True) + restday = fields.Numeric('Restday', digits=(2, 2), required=True) + weekday = fields.Selection([ + ('monday', 'Monday'), + ('tuesday', 'Tuesday'), + ('wednesday', 'Wednesday'), + ('thursday', 'Thursday'), + ('friday', 'Friday'), + ('saturday', 'Saturday'), + ('sunday', 'Sunday'), + ], 'Weekday', required=True) + note = fields.Text('Note') + + @classmethod + def __setup__(cls): + super(WorkdayDefinition, cls).__setup__() diff --git a/position.xml b/position.xml new file mode 100644 index 0000000..4c5bc21 --- /dev/null +++ b/position.xml @@ -0,0 +1,24 @@ + + + + + + staff.position + + position_form + + + + staff.workday_definition + form + workday_definition_form + + + staff.workday_definition + tree + workday_definition_tree + + + + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..861a9f5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..649b9d9 --- /dev/null +++ b/setup.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. + +from setuptools import setup +import re +import os +import io +try: + from configparser import ConfigParser +except ImportError: + from ConfigParser import ConfigParser + +MODULE = 'staff_payroll' +PREFIX = 'trytonpsk' +MODULE2PREFIX = {} + + +def read(fname): + return io.open( + os.path.join(os.path.dirname(__file__), fname), + 'r', encoding='utf-8').read() + + +def get_require_version(name): + if minor_version % 2: + require = '%s >= %s.%s.dev0, < %s.%s' + else: + require = '%s >= %s.%s, < %s.%s' + require %= (name, major_version, minor_version, + major_version, minor_version + 1) + return require + +config = ConfigParser() +config.readfp(open('tryton.cfg')) +info = dict(config.items('tryton')) +for key in ('depends', 'extras_depend', 'xml'): + if key in info: + info[key] = info[key].strip().splitlines() +version = info.get('version', '0.0.1') +major_version, minor_version, _ = version.split('.', 2) +major_version = int(major_version) +minor_version = int(minor_version) +name = '%s_%s' % (PREFIX, MODULE) + +download_url = 'http://downloads.tryton.org/%s.%s/' % ( + major_version, minor_version) +if minor_version % 2: + version = '%s.%s.dev0' % (major_version, minor_version) + download_url = ( + 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % ( + name[8:], name, version)) + +requires = ['python-sql >= 0.4'] +for dep in info.get('depends', []): + if not re.match(r'(ir|res)(\W|$)', dep): + requires.append(get_require_version('trytond_%s' % dep)) +requires.append(get_require_version('trytond')) + +tests_require = [get_require_version('proteus')] +dependency_links = [] +if minor_version % 2: + # Add development index for testing with proteus + dependency_links.append('https://trydevpi.tryton.org/') + +setup(name=name, + version=version, + description='Tryton module for staff payroll', + long_description=read('README'), + author='Presik Technologies', + author_email='gerente@presik.com', + url='http://www.presik.com/', + download_url="https://bitbucket.org/presik/%s" % name, + keywords='tryton %s' % MODULE, + package_dir={'trytond.modules.%s' % MODULE: '.'}, + packages=[ + 'trytond.modules.%s' % MODULE, + 'trytond.modules.%s.tests' % MODULE, + ], + package_data={ + 'trytond.modules.%s' % MODULE: (info.get('xml', []) + + ['tryton.cfg', 'view/*.xml', 'locale/*.po', '*.odt', '*.ods']), + }, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Plugins', + 'Framework :: Tryton', + 'Intended Audience :: Developers', + 'Intended Audience :: Financial and Insurance Industry', + 'Intended Audience :: Legal Industry', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Natural Language :: Bulgarian', + 'Natural Language :: Catalan', + 'Natural Language :: Chinese (Simplified)', + 'Natural Language :: Czech', + 'Natural Language :: Dutch', + 'Natural Language :: English', + 'Natural Language :: French', + 'Natural Language :: German', + 'Natural Language :: Hungarian', + 'Natural Language :: Italian', + 'Natural Language :: Portuguese (Brazilian)', + 'Natural Language :: Russian', + 'Natural Language :: Slovenian', + 'Natural Language :: Spanish', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Topic :: Office/Business', + 'Topic :: Office/Business :: Financial :: Accounting', + ], + license='GPL-3', + install_requires=None, + dependency_links=dependency_links, + zip_safe=False, + entry_points=""" + [trytond.modules] + %s = trytond.modules.%s + """ % (MODULE, MODULE), + test_suite='tests', + test_loader='trytond.test_loader:Loader', + tests_require=tests_require, + use_2to3=True, + convert_2to3_doctests=[ + 'tests/scenario.rst', + ], + ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..47711f2 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,6 @@ +#This file is part of Tryton. The COPYRIGHT file at the top level of +#this repository contains the full copyright notices and license terms. + +from .test_document_expiration import suite + +__all__ = ['suite'] diff --git a/tryton.cfg b/tryton.cfg new file mode 100644 index 0000000..7262bb5 --- /dev/null +++ b/tryton.cfg @@ -0,0 +1,17 @@ +[tryton] +version=5.0.2 +depends: + party + currency + company + account + bank + staff +xml: + period.xml + wage_type.xml + payroll.xml + position.xml + employee.xml + category.xml + configuration.xml diff --git a/view/category_form.xml b/view/category_form.xml new file mode 100644 index 0000000..ea219ae --- /dev/null +++ b/view/category_form.xml @@ -0,0 +1,14 @@ + + +
+