Initial commit

This commit is contained in:
Guillem Barba 2013-08-06 09:52:20 +02:00
commit 048d7f1e39
142 changed files with 18577 additions and 0 deletions

1
CHANGELOG Normal file
View File

@ -0,0 +1 @@
* Initial release

14
COPYRIGHT Normal file
View File

@ -0,0 +1,14 @@
Copyright (C) 2013 NaN Projectes de Programari Lliure, S.L.
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 <http://www.gnu.org/licenses/>.

29
INSTALL Normal file
View File

@ -0,0 +1,29 @@
Installing nantic_farm
======================
Prerequisites
-------------
* Python 2.6 or later (http://www.python.org/)
* trytond (http://www.tryton.org/)
Installation
------------
Once you've downloaded and unpacked the nantic_farm 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 farm.

674
LICENSE Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 <http://www.gnu.org/licenses/>.
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:
<program> Copyright (C) <year> <name of author>
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
<http://www.gnu.org/licenses/>.
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
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

12
MANIFEST.in Normal file
View File

@ -0,0 +1,12 @@
include INSTALL
include README
include COPYRIGHT
include CHANGELOG
include LICENSE
include tryton.cfg
include *.xml
include view/*.xml
include locale/*.po
include doc/*
include icons/*
include tests/*.rst

35
README Normal file
View File

@ -0,0 +1,35 @@
nantic_farm
===========
The farm module of the Tryton application platform.
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/

73
__init__.py Normal file
View File

@ -0,0 +1,73 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.pool import Pool
from .animal import *
from .animal_group import *
from .events import *
from .production import *
from .quality import *
from .specie import *
from .stock import *
from .user import *
def register():
Pool.register(
Specie,
SpecieModel,
SpecieFarmLine,
Breed,
Menu,
ActWindow,
Tag,
RemovalType,
RemovalReason,
FarrowingProblem,
Animal,
AnimalTag,
AnimalWeight,
Male,
FemaleCycle,
Female,
AnimalGroup,
AnimalGroupTag,
AnimalGroupWeight,
Location,
LocationSiloLocation,
LotAnimal,
LotAnimalGroup,
Lot,
User,
UserLocation,
EventOrder,
AbstractEvent,
MoveEvent,
FeedInventory,
FeedInventoryLocation,
FeedProvisionalInventory,
FeedProvisionalInventoryLocation,
FeedInventoryLine,
# FeedLocationDate
FeedEvent,
MedicationEvent,
TransformationEvent,
RemovalEvent,
SemenExtractionEvent,
SemenExtractionEventQualityTest,
SemenExtractionDose,
SemenExtractionDelivery,
InseminationEvent,
PregnancyDiagnosisEvent,
AbortEvent,
AbortEventFemaleCycle,
FarrowingEvent,
FarrowingEventFemaleCycle,
FarrowingEventAnimalGroup,
FosterEvent,
WeaningEvent,
WeaningEventFemaleCycle,
Move,
BOM,
QualityTest,
module='farm', type_='model')

931
animal.py Normal file
View File

@ -0,0 +1,931 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from datetime import date, datetime, timedelta
from decimal import Decimal
import logging
from trytond.model import ModelView, ModelSQL, fields
from trytond.pyson import Equal, Eval, Greater, Id, Not
from trytond.transaction import Transaction
from trytond.pool import Pool, PoolMeta
__all__ = ['Tag', 'Animal', 'AnimalTag', 'AnimalWeight', 'Male', 'Female',
'FemaleCycle']
__metaclass__ = PoolMeta
_STATES_MALE_FIELD = {
'invisible': Not(Equal(Eval('type'), 'male')),
}
_DEPENDS_MALE_FIELD = ['type']
_STATES_FEMALE_FIELD = {
'invisible': Not(Equal(Eval('type'), 'female')),
}
_DEPENDS_FEMALE_FIELD = ['type']
_STATES_INDIVIDUAL_FIELD = {
'invisible': Not(Equal(Eval('type'), 'individual')),
}
_DEPENDS_INDIVIDUAL_FIELD = ['type']
class Tag(ModelSQL, ModelView):
'Farm Tags'
__name__ = 'farm.tag'
name = fields.Char('Name', required=True)
animals = fields.Many2Many('farm.animal-farm.tag', 'tag', 'animal',
'Animals')
animal_group = fields.Many2Many('farm.animal.group-farm.tag', 'tag',
'group', 'Groups')
@classmethod
def __setup__(cls):
super(Tag, cls).__setup__()
cls._sql_constraints += [
('name_uniq', 'UNIQUE (name)',
'The Name of the Tag must be unique.'),
]
class AnimalMixin:
@classmethod
def _create_and_done_first_stock_move(cls, records):
"""
It creates the first stock.move for animal's lot, and then confirms,
assigns and set done it to get stock in initial location (Farm).
"""
Move = Pool().get('stock.move')
new_moves = []
for record in records:
move = record._get_first_move()
move.save()
new_moves.append(move)
Move.assign(new_moves)
Move.do(new_moves)
return new_moves
def _get_first_move(self):
"""
Prepare values to create the first stock.move for animal's lot to
get stock in initial location (Farm).
"""
pool = Pool()
Company = pool.get('company.company')
Move = Pool().get('stock.move')
context = Transaction().context
company = Company(context['company'])
if self.origin == 'purchased':
from_location = company.party.supplier_location
if not from_location:
self.raise_user_error('missing_supplier_location',
company.party.rec_name)
else: # raised
from_location = self.initial_location.warehouse.production_location
if not from_location:
self.raise_user_error('missing_production_location',
self.initial_location.warehouse.rec_name)
move_date = self.arrival_date or date.today()
return Move(
product=self.lot.product,
uom=self.lot.product.default_uom,
quantity=getattr(self, 'initial_quantity', 1),
from_location=from_location,
to_location=self.initial_location,
planned_date=move_date,
effective_date=move_date,
company=company,
lot=self.lot,
# TODO: es suficient agafar el cost_price del producte o s'hauria
# de permetre a l'usuari especificar un preu de cost inicial?
unit_price=self.lot.product.cost_price,
origin=self)
class Animal(ModelSQL, ModelView, AnimalMixin):
"Farm Animal"
__name__ = 'farm.animal'
type = fields.Selection([
('male', 'Male'),
('female', 'Female'),
('individual', 'Individual'),
], 'Type', required=True, readonly=True, select=True)
specie = fields.Many2One('farm.specie', 'Specie', required=True,
readonly=True, select=True)
breed = fields.Many2One('farm.specie.breed', 'Breed', required=True,
domain=[('specie', '=', Eval('specie'))], depends=['specie'])
lot = fields.One2One('stock.lot-farm.animal', 'animal', 'lot',
string='Lot', required=True, readonly=True, domain=[
('animal_type', '=', Eval('type')),
], depends=['type'])
number = fields.Function(fields.Char('Number'),
'get_number', 'set_number')
# location is updated in do() of stock.move
location = fields.Many2One('stock.location', 'Location', readonly=True,
domain=[('type', '!=', 'warehouse')],
help='Indicates where the animal currently resides.')
farm = fields.Function(fields.Many2One('stock.location', 'Current Farm',
on_change_with=['location'], depends=['location']),
'on_change_with_farm', searcher='search_farm')
origin = fields.Selection([
('purchased', 'Purchased'),
('raised', 'Raised'),
], 'Origin', required=True, readonly=True,
help='Raised means that this animal was born in the farm. Otherwise, '
'it was purchased.')
arrival_date = fields.Date('Arrival Date', states={
'readonly': Greater(Eval('id', 0), 0),
}, depends=['id'],
help="The date this animal arrived (if it was purchased) or when it "
"was born.")
purchase_shipment = fields.Many2One('stock.shipment.in',
'Purchase Shipment', readonly=True,
states={'invisible': Not(Equal(Eval('origin'), 'purchased'))},
depends=['origin'])
initial_location = fields.Many2One('stock.location', 'Initial Location',
required=True, domain=[('type', '=', 'storage')],
states={'readonly': Greater(Eval('id', 0), 0)}, depends=['id'],
context={'restrict_by_specie_animal_type': True},
help="The Location where the animal was reached or where it was "
"allocated when it was purchased.\nIt is used as historical "
"information and to get Serial Number.")
birthdate = fields.Date('Birthdate')
removal_date = fields.Date('Removal Date', readonly=True,
help='Get information from the corresponding removal event.')
removal_reason = fields.Many2One('farm.removal.reason', 'Removal Reason',
readonly=True)
weights = fields.One2Many('farm.animal.weight', 'animal',
'Weight Records', readonly=False, order=[('timestamp', 'DESC')])
current_weight = fields.Function(fields.Many2One('farm.animal.weight',
'Current Weight', on_change_with=['weights']),
'on_change_with_current_weight')
tags = fields.Many2Many('farm.animal-farm.tag', 'animal', 'tag', 'Tags')
notes = fields.Text('Notes')
active = fields.Boolean('Active')
# Individual Fields
sex = fields.Selection([
('male', "Male"),
('female', "Female"),
('undetermined', "Undetermined"),
], 'Sex', required=True, states=_STATES_INDIVIDUAL_FIELD,
depends=_DEPENDS_INDIVIDUAL_FIELD)
purpose = fields.Selection([
(None, ''),
('sale', 'Sale'),
('replacement', 'Replacement'),
('unknown', 'Unknown'),
], 'Purpose', states=_STATES_INDIVIDUAL_FIELD,
depends=_DEPENDS_INDIVIDUAL_FIELD)
# We can't use the 'required' attribute in field because it's
# checked on view before execute 'create()' function where this
# field is filled in.
@classmethod
def __setup__(cls):
super(Animal, cls).__setup__()
cls._error_messages.update({
'missing_supplier_location': ('Supplier Location of '
'company\'s party "%s" is empty but it is required to '
'create the arrival stock move for a new animal.'),
'missing_production_location': ('The warehouse location "%s" '
'doesn\'t have set production location, but it is '
'required to create the arrival stock move for a new '
'animal.'),
'no_farm_specie_farm_line_available': ('The specified farm '
'"%(farm)s" is not configured as farm with '
'"%(animal_type)s" for the specie "%(specie)s"'),
'no_sequence_in_farm_line': ('The required sequence '
'"%(sequence_field)s" is not set in the farm line '
'"%(farm_line)s".'),
'invalid_animal_destination': ('The event "%(event)s" is '
'trying to move the animal "%(animal)s" to location '
'"%(location)s", but the location\'s warehouse is not '
'configured as a farm for this kind of animals.'),
'no_product_in_specie': ('The required product '
'"%(product_field)s" is not set in the farm "%(farm)s".'),
})
@staticmethod
def default_specie():
return Transaction().context.get('specie')
@staticmethod
def default_type():
return Transaction().context.get('animal_type')
@staticmethod
def default_origin():
return 'purchased'
@staticmethod
def default_arrival_date():
return date.today()
@staticmethod
def default_sex():
sex = Transaction().context.get('animal_type', 'undetermined')
if sex in ('group', 'individual'):
sex = 'undetermined'
return sex
@staticmethod
def default_active():
return True
def get_rec_name(self, name):
name = self.lot.number
if not self.active:
name += ' (*)'
return name
@classmethod
def search_rec_name(cls, name, clause):
return [('lot.number',) + clause[1:]]
def get_number(self, name):
return self.lot.number
@classmethod
def set_number(cls, animals, name, value):
Lot = Pool().get('stock.lot')
lots = [animal.lot for animal in animals if animal.lot]
if lots:
Lot.write(lots, {
'number': value,
})
def on_change_with_farm(self, name=None):
return (self.location and self.location.warehouse and
self.location.warehouse.id or None)
@classmethod
def search_farm(cls, name, clause):
return [('location.warehouse',) + tuple(clause[1:])]
def on_change_with_current_weight(self, name=None):
if self.weights:
return self.weights[0].id
def check_in_location(self, location, timestamp):
with Transaction().set_context(
locations=[location.id],
stock_date_end=timestamp.date()):
return self.lot.quantity == 1
def check_allowed_location(self, location, event_rec_name):
for farm_line in self.specie.farm_lines:
if farm_line.farm.id == location.warehouse.id:
if getattr(farm_line, 'has_%s' % self.type):
return
self.raise_user_error('invalid_animal_destination', {
'event': event_rec_name,
'animal': self.rec_name,
'location': location.rec_name,
})
@classmethod
def copy(cls, animals, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.update({
'lot': None,
'number': None,
'location': None,
'purchase_shipment': None,
'removal_reason': None,
'removal_date': None,
'weights': None,
})
return super(Animal, cls).copy(animals, default=default)
@classmethod
def create(cls, vlist):
pool = Pool()
Location = pool.get('stock.location')
Lot = pool.get('stock.lot')
context = Transaction().context
vlist = [x.copy() for x in vlist]
for vals in vlist:
logging.getLogger(cls.__name__).debug("Create vals: %s" % vals)
if not vals.get('specie'):
vals['specie'] = cls.default_specie()
if not vals.get('type'):
vals['type'] = cls.default_type()
if vals['type'] in ('male', 'female'):
vals['sex'] = vals['type']
if not vals.get('number'):
location = Location(vals['initial_location'])
vals['number'] = cls._calc_number(vals['specie'],
location.warehouse.id, vals['type'])
if vals.get('lot'):
lot = Lot(vals['lot'])
Lot.write([lot], cls._get_lot_values(vals, False))
else:
new_lot, = Lot.create([cls._get_lot_values(vals, True)])
vals['lot'] = new_lot.id
new_animals = super(Animal, cls).create(vlist)
if not context.get('no_create_stock_move'):
cls._create_and_done_first_stock_move(new_animals)
return new_animals
@classmethod
def _calc_number(cls, specie_id, farm_id, type):
pool = Pool()
FarmLine = pool.get('farm.specie.farm_line')
Location = pool.get('stock.location')
Sequence = pool.get('ir.sequence')
Specie = pool.get('farm.specie')
sequence_fieldname = '%s_sequence' % type
farm_lines = FarmLine.search([
('specie', '=', specie_id),
('farm', '=', farm_id),
('has_' + type, '=', True),
])
if not farm_lines:
cls.raise_user_error('no_farm_specie_farm_line_available', {
'farm': Location(farm_id).rec_name,
'animal_type': type,
'specie': Specie(specie_id).rec_name,
})
farm_line, = farm_lines
sequence = getattr(farm_line, sequence_fieldname, False)
if not sequence:
cls.raise_user_error('no_sequence_in_farm_line', {
'sequence_field': getattr(FarmLine,
sequence_fieldname).string,
'farm_line': farm_line.rec_name,
})
return Sequence.get_id(sequence.id)
@classmethod
def _get_lot_values(cls, animal_vals, create):
"""
Prepare values to create the stock.lot for the new animal.
animal_vals: dictionary with values to create farm.animal
It returns a dictionary with values to create stock.lot
"""
Specie = Pool().get('farm.specie')
if not animal_vals:
return {}
specie = Specie(animal_vals['specie'])
product_fieldname = '%s_product' % animal_vals['type']
product = getattr(specie, product_fieldname, False)
if not product:
cls.raise_user_error('no_product_in_specie', {
'product_field': getattr(Specie, product_fieldname).string,
'specie': specie.rec_name,
})
return {
'number': animal_vals['number'],
'product': product.id,
'animal_type': animal_vals['type'],
}
@classmethod
def delete(cls, animals):
pool = Pool()
Lot = pool.get('stock.lot')
lots = [a.lot for a in animals]
result = super(Animal, cls).delete(animals)
if lots:
Lot.delete(lots)
return result
class AnimalTag(ModelSQL):
'Animal - Tag'
__name__ = 'farm.animal-farm.tag'
animal = fields.Many2One('farm.animal', 'Animal', required=True)
tag = fields.Many2One('farm.tag', 'Tag', required=True)
class AnimalWeight(ModelSQL, ModelView):
'Farm Animal Weight Record'
__name__ = 'farm.animal.weight'
_order = [('timestamp', 'DESC')]
animal = fields.Many2One('farm.animal', 'Animal',
required=True,
ondelete='CASCADE')
timestamp = fields.DateTime('Date & Time', required=True)
uom = fields.Many2One('product.uom', "UoM",
required=True,
domain=[('category', '=', Id('product', 'uom_cat_weight'))])
unit_digits = fields.Function(fields.Integer('Unit Digits',
on_change_with=['uom']),
'on_change_with_unit_digits')
weight = fields.Numeric('Weight',
digits=(16, Eval('unit_digits', 2)),
required=True,
depends=['unit_digits'])
@staticmethod
def default_timestamp():
return datetime.now()
@staticmethod
def default_uom():
return Pool().get('ir.model.data').get_id('product', 'uom_kilogram')
@staticmethod
def default_unit_digits():
return 2
def get_rec_name(self, name):
return '%s %s (%s)' % (self.weight, self.uom.symbol, self.timestamp)
@classmethod
def search_rec_name(cls, name, clause):
operand = clause[2]
operand = operand.replace('%', '')
try:
operand = Decimal(operand)
except:
return [('weight', '=', 0)]
operator = clause[1]
operator = operator.replace('ilike', '=').replace('like', '=')
return [('weight', operator, operand)]
def on_change_with_unit_digits(self, name=None):
if self.uom:
return self.uom.digits
return 2
class Male:
__name__ = 'farm.animal'
extractions = fields.One2Many('farm.semen_extraction.event',
'animal', 'Semen Extractions', states=_STATES_MALE_FIELD,
depends=_DEPENDS_MALE_FIELD)
last_extraction = fields.Date('Last Extraction', readonly=True,
states=_STATES_MALE_FIELD, depends=_DEPENDS_MALE_FIELD)
def update_last_extraction(self, validated_event=None):
if not self.extractions:
self.last_extraction = None
self.save()
return None
last_extraction = None
reversed_extractions = list(self.extractions)
reversed_extractions.reverse()
for extraction_event in reversed_extractions:
if (extraction_event.state == 'validated' or
validated_event and extraction_event == validated_event):
last_extraction = extraction_event.timestamp.date()
break
self.last_extraction = last_extraction
self.save()
return last_extraction
class Female:
__name__ = 'farm.animal'
cycles = fields.One2Many('farm.animal.female_cycle', 'animal', 'Cycles',
readonly=True, order=[
('sequence', 'ASC'),
('ordination_date', 'ASC'),
],
states=_STATES_FEMALE_FIELD, depends=_DEPENDS_FEMALE_FIELD)
current_cycle = fields.Many2One('farm.animal.female_cycle',
'Current Cycle', readonly=True, states=_STATES_FEMALE_FIELD,
depends=_DEPENDS_FEMALE_FIELD)
state = fields.Selection([
(None, ''),
('prospective', 'Prospective'),
('unmated', 'Unmated'),
('mated', 'Mated'),
('removed', 'Removed'),
],
'Status', readonly=True, states=_STATES_FEMALE_FIELD,
depends=_DEPENDS_FEMALE_FIELD,
help='According to NPPC Production and Financial Standards there are '
'four status for breeding sows. The status change is event driven: '
'arrival date, entry date mating event and removal event')
first_mating = fields.Function(fields.Date('First Mating',
states=_STATES_FEMALE_FIELD, depends=_DEPENDS_FEMALE_FIELD,
help='Date of first mating. This will change the status of the '
'sow to "mated"'),
'get_first_mating')
days_from_insemination = fields.Function(fields.Integer('Inseminated Days',
help='Number of days from last Insemination Event. -1 if there '
'isn\'t any Insemination Event.'),
'get_days_from_insemination', searcher='search_days_from_insemination')
last_produced_group = fields.Function(fields.Many2One('farm.animal.group',
'Last Produced Group', domain=[
('specie', '=', Eval('specie')),
], depends=['specie']),
'get_last_produced_group')
days_from_farrowing = fields.Function(fields.Integer('Unpregnant Days',
help='Number of days from last Farrowing Event. -1 if there '
'isn\'t any Farrowing Event.'),
'get_days_from_farrowing', searcher='search_days_from_farrowing')
@staticmethod
def default_state():
'''
Specific for Female animals.
'''
if Transaction().context.get('animal_type') == 'female':
return 'prospective'
return None
def is_lactating(self):
return (self.current_cycle and self.current_cycle.state == 'lactating'
or False)
# TODO: call when cycle is created, deleted or its ordination_date or
# sequence are modifyied
def update_current_cycle(self):
current_cycle = self.cycles and self.cycles[-1].id or None
self.current_cycle = current_cycle
self.save()
return current_cycle
# TODO: call in removal event, when cycle is added (but probably it's
# called from cycle)
def update_state(self):
if self.type != 'female':
return
if (self.removal_date and self.removal_date <= date.today()):
state = 'removed'
elif (not self.cycles or len(self.cycles) == 1 and
not self.cycles[0].weaning_event and
self.cycles[0].state == 'unmated'):
state = 'prospective'
elif self.current_cycle.state == 'unmated':
state = 'unmated'
else:
state = 'mated'
self.state = state
self.save()
return state
def get_first_mating(self, name):
InseminationEvent = Pool().get('farm.insemination.event')
if self.type != 'female':
return None
first_inseminations = InseminationEvent.search([
('animal', '=', self.id),
], limit=1, order=[('timestamp', 'ASC')])
if not first_inseminations:
return None
first_insemination, = first_inseminations
return first_insemination.timestamp.date()
def get_days_from_insemination(self, name):
InseminationEvent = Pool().get('farm.insemination.event')
last_valid_insemination = InseminationEvent.search([
('animal', '=', self.id),
('state', '=', 'validated'),
], order=[('timestamp', 'DESC')], limit=1)
if not last_valid_insemination:
return -1
days_from_insemination = (date.today() -
last_valid_insemination[0].timestamp.date()).days
return days_from_insemination
@classmethod
def search_days_from_insemination(cls, name, clause):
InseminationEvent = Pool().get('farm.insemination.event')
event_filter, operator = cls._get_filter_search_days(name, clause)
animal_ids = set()
for event in InseminationEvent.search(event_filter):
animal_ids.add(event.animal.id)
return [
('type', '=', 'female'),
('id', operator, list(animal_ids)),
]
def get_last_produced_group(self, name):
FarrowingEvent = Pool().get('farm.farrowing.event')
last_farrowing_events = FarrowingEvent.search([
('animal', '=', self),
('state', '=', 'validated'),
('produced_group', '!=', False),
],
order=[
('timestamp', 'DESC'),
], limit=1)
if last_farrowing_events:
return last_farrowing_events[0].produced_group
return None
def get_days_from_farrowing(self, name):
FarrowingEvent = Pool().get('farm.farrowing.event')
last_valid_farrowing = FarrowingEvent.search([
('animal', '=', self.id),
('state', '=', 'validated'),
], order=[('timestamp', 'DESC')], limit=1)
if not last_valid_farrowing:
return -1
days_from_farrowing = (date.today() -
last_valid_farrowing[0].timestamp.date()).days
return days_from_farrowing
@classmethod
def search_days_from_farrowing(cls, name, clause):
FarrowingEvent = Pool().get('farm.farrowing.event')
event_filter, operator = cls._get_filter_search_days(name, clause)
animal_ids = set()
for event in FarrowingEvent.search(event_filter):
animal_ids.add(event.animal.id)
return [
('type', '=', 'female'),
('id', operator, list(animal_ids)),
]
@classmethod
def _get_filter_search_days(cls, name, clause):
event_filter = []
include_oposite = False
if isinstance(clause[2], bool) and clause[2] is False:
if clause[1] == '=':
include_oposite = True
# else: "!= False" => inseminated sometimes
elif isinstance(clause[2], int):
# third element is a number of days
operator = False
n_days = False
if clause[1] in ('<', '<='):
operator = '>'
if clause[1] == '<':
n_days = clause[2]
else:
n_days = clause[2] + 1
elif clause[1] in ('>', '>='):
operator = '<'
include_oposite = True
if clause[1] == '>':
n_days = clause[2] + 1
else:
n_days = clause[2]
elif clause[1] in ('=', '!='):
operator = clause[1]
n_days = clause[2]
if operator and n_days:
date_lim = date.today() - timedelta(days=n_days)
if operator == '=':
event_filter = ['AND', [
('timestamp', '>=',
date_lim.strftime('%Y-%m-%d 00:00:00')),
], [
('timestamp', '<=',
date_lim.strftime('%Y-%m-%d 23:59:59')),
], ]
elif operator == '!=':
include_oposite = True
event_filter = ['OR', [
('timestamp', '<',
date_lim.strftime('%Y-%m-%d 00:00:00')),
], [
('timestamp', '>',
date_lim.strftime('%Y-%m-%d 23:59:59')),
], ]
else:
event_filter = [
('timestamp', operator,
date_lim.strftime('%Y-%m-%d 23:59:59')),
]
op = 'in'
if include_oposite:
if event_filter:
event_filter.insert(0, 'NOT')
op = 'not in'
return event_filter, op
@classmethod
def copy(cls, females, default=None):
if default is None:
default = {}
else:
default = default.copy()
default['cycles'] = None
default['current_cycle'] = None
default['state'] = cls.default_state()
return super(Female, cls).copy(females, default)
class FemaleCycle(ModelSQL, ModelView):
'Farm Female Cycle'
__name__ = 'farm.animal.female_cycle'
_rec_name = 'sequence'
_order = [
('animal', 'ASC'),
('sequence', 'ASC'),
('ordination_date', 'ASC'),
]
animal = fields.Many2One('farm.animal', 'Female', required=True,
ondelete='CASCADE', domain=[('type', '=', 'female')])
sequence = fields.Integer('Num. cycle', required=True)
ordination_date = fields.DateTime('Date for ordination', required=True,
readonly=True)
state = fields.Selection([
('mated', 'Mated'),
('pregnant', 'Pregnant'),
('lactating', 'Lactating'),
('unmated', 'Unmated'),
], 'State', readonly=True, required=True)
# Female events fields
insemination_events = fields.One2Many('farm.insemination.event',
'female_cycle', 'Insemination Events')
days_between_weaning_and_insemination = fields.Function(
fields.Integer('Unmated Days', help='Number of days between previous '
'Weaning Event and first Insemination Event.'),
'get_days_between_weaning_and_insemination')
diagnosis_events = fields.One2Many('farm.pregnancy_diagnosis.event',
'female_cycle', 'Diagnosis Events')
pregnant = fields.Function(fields.Boolean('Pregnant',
on_change_with=['diagnosis_events', 'abort_event'],
depends=['diagnosis_events', 'abort_event'],
help='A female will be considered pregnant if there are more than'
' one diagnosis and the last one has a positive result.'),
'on_change_with_pregnant')
abort_event = fields.One2One('farm.abort.event-farm.animal.female_cycle',
'cycle', 'event', string='Abort Event', readonly=True, domain=[
('animal', '=', Eval('animal')),
], depends=['animal'])
farrowing_event = fields.One2One(
'farm.farrowing.event-farm.animal.female_cycle', 'cycle', 'event',
string='Farrowing Event', readonly=True, domain=[
('animal', '=', Eval('animal')),
], depends=['animal'])
live = fields.Function(fields.Integer('Live'),
'get_farrowing_event_field')
dead = fields.Function(fields.Integer('Dead'),
'get_farrowing_event_field')
foster_events = fields.One2Many('farm.foster.event', 'female_cycle',
'Foster Events')
fostered = fields.Function(fields.Integer('Fostered',
on_change_with=['foster_events'], depends=['foster_events'],
help='Diference between Fostered Input and Output. A negative '
'number means that he has given more than taken.'),
'on_change_with_fostered')
weaning_event = fields.One2One(
'farm.weaning.event-farm.animal.female_cycle', 'cycle', 'event',
string='Weaning Event', readonly=True, domain=[
('animal', '=', Eval('animal')),
], depends=['animal'])
weaned = fields.Function(fields.Integer('Weaned Quantity'),
'get_weaned')
removed = fields.Function(fields.Integer('Removed Quantity',
help='Number of removed animals from Produced Group. Diference '
'between born live and weaned, computing Fostered diference.'),
'get_removed')
days_between_farrowing_weaning = fields.Function(
fields.Integer('Lactating Days',
help='Number of days between Farrowing and Weaning.'),
'get_lactating_days')
@staticmethod
def default_sequence(animal_id=None):
'''
If 'animal_id' is not found in context it return 0.
Otherwise, if the last cycle is completed (has 'farrowing event'), it
returns its sequence plus one, if it's not completed it returns its
sequence.
'''
FemaleCycle = Pool().get('farm.animal.female_cycle')
animal_id = animal_id or Transaction().context.get('animal')
if not animal_id:
return 0
cycles = FemaleCycle.search([
('animal', '=', animal_id),
],
order=[
('sequence', 'DESC'),
('ordination_date', 'DESC'),
], limit=1)
if not cycles:
return 1
if cycles[0].farrowing_event:
return cycles[0].sequence + 1
return cycles[0].sequence
@staticmethod
def default_ordination_date():
return datetime.now()
@staticmethod
def default_state():
return 'unmated'
def get_rec_name(self, name):
logging.getLogger(self.__name__).debug("FemaleCycle.get_rec_name()")
state_labels = FemaleCycle.state.selection
logging.getLogger(self.__name__).debug("state_labels: ", state_labels)
return "%s (%s)" % (self.sequence, state_labels[self.state])
# TODO: call in weaning, farrowing, abort, pregnancy_diagnosis and
# insemination event (in 'valid()' and 'cancel()')
def update_state(self, validated_event):
'''
Sorted rules:
- A cycle will be considered 'unmated'
if weaning_event_id != False and weaning_event.state == 'validated'
or if abort_event != False has abort_event.state == 'validated'
or has not any validated event in insemination_event_ids.
- A female will be considered 'lactating'
if farrowing_event_id!=False and farrowing_event.state=='validated'
- A female will be considered 'pregnant' if there are more than one
diagnosis in 'validated' state and the last one has a positive result
- A female will be considered 'mated' if there are any items in
insemination_event_ids with 'validated' state.
'''
def check_event(event_to_check):
return (type(event_to_check) == type(validated_event) and
event_to_check == validated_event or
event_to_check.state == 'validated')
state = 'unmated'
if (self.abort_event and check_event(self.abort_event) or
self.weaning_event and check_event(self.weaning_event)):
state = 'unmated'
elif self.farrowing_event and check_event(self.farrowing_event):
if self.farrowing_event.live > 0:
state = 'lactating'
else:
state = 'unmated'
elif self.pregnant:
state = 'pregnant'
else:
for insemination_event in self.insemination_events:
if check_event(insemination_event):
state = 'mated'
break
self.state = state
self.save()
self.animal.update_state()
return state
def get_days_between_weaning_and_insemination(self, name):
if not self.insemination_events:
return None
previous_cycles = self.search([
('animal', '=', self.animal.id),
('sequence', '<=', self.sequence),
('ordination_date', '<', self.ordination_date)
],
order=[
('sequence', 'DESC'),
('ordination_date', 'DESC'),
], limit=1)
if not previous_cycles or not previous_cycles[0].weaning_event:
return None
weaning_date = previous_cycles[0].weaning_event.timestamp.date()
insemination_date = self.insemination_events[0].timestamp.date()
return (insemination_date - weaning_date).days
def on_change_with_pregnant(self, name=None):
if self.abort_event:
return False
if not self.diagnosis_events:
return False
if self.farrowing_event:
return False
# relation to cycle is set on event validate so it's not required to
# check the state
return self.diagnosis_events[-1].result == 'positive'
def get_farrowing_event_field(self, name):
return (self.farrowing_event and getattr(self.farrowing_event, name)
or 0)
def on_change_with_fostered(self, name=None):
return sum(e.quantity for e in self.foster_events)
def get_weaned(self, name):
return self.weaning_event and self.weaning_event.quantity or 0
def get_removed(self, name):
return self.live + self.fostered + self.weaned
def get_days_between_farrowing_weaning(self, name):
if not self.farrowing_event or not self.weaning_event:
return None
return (self.weaning_event.timestamp.date() -
self.farrowing_event.timestamp.date()).days
@classmethod
def create(cls, vlist):
vlist = [v.copy() for v in vlist]
for vals in vlist:
if not vals.get('sequence') and vals.get('animal'):
vals['sequence'] = cls.default_sequence(vals['animal'])
return super(FemaleCycle, cls).create(vlist)

278
animal.xml Normal file
View File

@ -0,0 +1,278 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!--
farm.tag
-->
<!-- Views -->
<record model="ir.ui.view" id="farm_tag_form_view">
<field name="model">farm.tag</field>
<field name="type">form</field>
<field name="name">farm_tag_form</field>
</record>
<record model="ir.ui.view" id="farm_tag_list_view">
<field name="model">farm.tag</field>
<field name="type">tree</field>
<field name="name">farm_tag_list</field>
</record>
<!-- Actions -->
<record model="ir.action.act_window" id="act_farm_tag">
<field name="name">Tags</field>
<field name="res_model">farm.tag</field>
<field name="search_value"></field>
<!-- <field name="domain">[]</field> -->
<!-- <field name="context">{}</field> -->
</record>
<record model="ir.action.act_window.view" id="act_farm_tag_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_tag_list_view"/>
<field name="act_window" ref="act_farm_tag"/>
</record>
<record model="ir.action.act_window.view" id="act_farm_tag_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_tag_form_view"/>
<field name="act_window" ref="act_farm_tag"/>
</record>
<!-- Permissions -->
<record model="ir.model.access" id="access_farm_tag">
<field name="model" search="[('model', '=', 'farm.tag')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_farm_tag_admin">
<field name="model" search="[('model', '=', 'farm.tag')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!--
farm.animal
-->
<!-- Views -->
<record model="ir.ui.view" id="farm_animal_form_view">
<field name="model">farm.animal</field>
<field name="type">form</field>
<field name="name">farm_animal_form</field>
</record>
<record model="ir.ui.view" id="farm_animal_list_view">
<field name="model">farm.animal</field>
<field name="type">tree</field>
<field name="name">farm_animal_list</field>
</record>
<!-- Actions -->
<record model="ir.action.act_window" id="act_farm_animal_male">
<field name="name">Males</field>
<field name="res_model">farm.animal</field>
<field name="search_value"></field>
<field name="domain">[('type', '=', 'male')]</field>
<!-- <field name="context">{}</field> -->
</record>
<record model="ir.action.act_window.view"
id="act_farm_animal_male_view_list">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_animal_list_view"/>
<field name="act_window" ref="act_farm_animal_male"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_animal_male_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_animal_form_view"/>
<field name="act_window" ref="act_farm_animal_male"/>
</record>
<record model="ir.action.act_window" id="act_farm_animal_female">
<field name="name">Females</field>
<field name="res_model">farm.animal</field>
<field name="search_value"></field>
<field name="domain">[('type', '=', 'female')]</field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_animal_female_view_list">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_animal_list_view"/>
<field name="act_window" ref="act_farm_animal_female"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_animal_female_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_animal_form_view"/>
<field name="act_window" ref="act_farm_animal_female"/>
</record>
<record model="ir.action.act_window" id="act_farm_animal_individual">
<field name="name">Individuals</field>
<field name="res_model">farm.animal</field>
<field name="search_value"></field>
<field name="domain">[('type', '=', 'individual')]</field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_animal_individual_view_list">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_animal_list_view"/>
<field name="act_window" ref="act_farm_animal_individual"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_animal_individual_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_animal_form_view"/>
<field name="act_window" ref="act_farm_animal_individual"/>
</record>
<!-- Permissions -->
<record model="ir.model.access" id="access_farm_animal_farm">
<field name="model" search="[('model', '=', 'farm.animal')]"/>
<field name="group" ref="group_farm"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_farm_animal_admin">
<field name="model" search="[('model', '=', 'farm.animal')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_farm_animal_males">
<field name="model" search="[('model', '=', 'farm.animal')]"/>
<field name="group" ref="group_farm_males"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_farm_animal_females">
<field name="model" search="[('model', '=', 'farm.animal')]"/>
<field name="group" ref="group_farm_females"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_farm_animal_individuals">
<field name="model" search="[('model', '=', 'farm.animal')]"/>
<field name="group" ref="group_farm_individuals"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_farm_animal_groups">
<field name="model" search="[('model', '=', 'farm.animal')]"/>
<field name="group" ref="group_farm_groups"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!-- Sequences -->
<record model="ir.sequence.type" id="sequence_type_animal">
<field name="name">Farm Animal</field>
<field name="code">farm.animal</field>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_animal_group_farm">
<field name="sequence_type" ref="sequence_type_animal"/>
<field name="group" ref="group_farm"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_group_group_animal_admin">
<field name="sequence_type" ref="sequence_type_animal"/>
<field name="group" ref="group_farm"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_group_group_animal_males">
<field name="sequence_type" ref="sequence_type_animal"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_group_group_animal_females">
<field name="sequence_type" ref="sequence_type_animal"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_group_group_animal_individuals">
<field name="sequence_type" ref="sequence_type_animal"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_group_group_animal_groups">
<field name="sequence_type" ref="sequence_type_animal"/>
<field name="group" ref="group_farm_groups"/>
</record>
<!--
farm.animal.weigh
-->
<!-- Views -->
<record model="ir.ui.view" id="farm_animal_weight_form_view">
<field name="model">farm.animal.weight</field>
<field name="type">form</field>
<field name="name">farm_animal_weight_form</field>
</record>
<record model="ir.ui.view" id="farm_animal_weight_list_view">
<field name="model">farm.animal.weight</field>
<field name="type">tree</field>
<field name="name">farm_animal_weight_list</field>
</record>
<!-- Permissions -->
<record model="ir.model.access" id="access_farm_animal_weight">
<field name="model" search="[('model', '=', 'farm.animal.weight')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_farm_animal_weight_admin">
<field name="model" search="[('model', '=', 'farm.animal.weight')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!--
farm.animal.female_cycle
-->
<record model="ir.ui.view" id="farm_animal_female_cycle_form_view">
<field name="model">farm.animal.female_cycle</field>
<field name="type">form</field>
<field name="name">farm_animal_female_cycle_form</field>
</record>
<record model="ir.ui.view" id="farm_animal_female_cycle_list_view">
<field name="model">farm.animal.female_cycle</field>
<field name="type">tree</field>
<field name="name">farm_animal_female_cycle_list</field>
</record>
<!-- Menus -->
<menuitem action="act_farm_tag" id="menu_farm_tag"
parent="menu_configuration" sequence="2"/>
</data>
</tryton>

377
animal_group.py Normal file
View File

@ -0,0 +1,377 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from datetime import date, datetime
from decimal import Decimal
from trytond.model import ModelView, ModelSQL, fields
from trytond.pyson import Equal, Eval, Greater, Id, Not
from trytond.transaction import Transaction
from trytond.pool import Pool
from .animal import AnimalMixin
__all__ = ['AnimalGroup', 'AnimalGroupTag', 'AnimalGroupWeight']
class AnimalGroup(ModelSQL, ModelView, AnimalMixin):
'Group of Farm Animals'
__name__ = 'farm.animal.group'
specie = fields.Many2One('farm.specie', 'Specie', required=True,
readonly=True)
breed = fields.Many2One('farm.specie.breed', 'Breed', required=True,
domain=[('specie', '=', Eval('specie'))], depends=['specie'])
lot = fields.One2One('stock.lot-farm.animal.group', 'animal_group',
'lot', 'Lot', required=True, readonly=True,
domain=[('animal_type', '=', 'group')])
number = fields.Function(fields.Char('Number'),
'get_number', 'set_number')
farms = fields.Function(fields.Many2Many('stock.location', None, None,
'Current Farms', readonly=True, domain=[
('type', '=', 'warehouse'),
],
help='Farms where this group can be found. It is used for access '
'management.'),
'get_farms', searcher='search_farms')
origin = fields.Selection([
('purchased', 'Purchased'),
('raised', 'Raised'),
], 'Origin', required=True, readonly=True,
help='Raised means that this group was born in the farm. Otherwise, '
'it was purchased.')
arrival_date = fields.Date('Arrival Date', states={
'readonly': Greater(Eval('id', 0), 0),
}, depends=['id'],
help="The date this group arrived (if it was purchased) or when it "
"was born.")
purchase_shipment = fields.Many2One('stock.shipment.in',
'Purchase Shipment', readonly=True,
states={'invisible': Not(Equal(Eval('origin'), 'purchased'))},
depends=['origin'])
initial_location = fields.Many2One('stock.location', "Initial Location",
required=True, domain=[('type', '=', 'storage')],
states={'readonly': Greater(Eval('id', 0), 0)}, depends=['id'],
context={'restrict_by_specie_animal_type': True},
help="The Location where the group was reached or where it was "
"allocated when it was purchased.\nIt is used as historical "
"information and to get Serial Number.")
initial_quantity = fields.Integer('Initial Quantity', required=True,
states={'readonly': Greater(Eval('id', 0), 0)}, depends=['id'],
help="The number of animals in group when it was reached or "
"purchased.\nIt is used as historical information and to create the "
"initial move.")
removal_date = fields.Date('Removal Date', readonly=True)
weights = fields.One2Many('farm.animal.group.weight', 'group',
'Weight Records', readonly=True)
current_weight = fields.Function(fields.Many2One(
'farm.animal.group.weight', 'Current Weight',
on_change_with=['weights']),
'on_change_with_current_weight')
tags = fields.Many2Many('farm.animal.group-farm.tag', 'group', 'tag',
'Tags')
notes = fields.Text('Notes')
active = fields.Boolean('Active')
# # TODO: Extra
# 'type': fields.selection([('static','Static'),('dynamic','Dynamic')],
# help='Static = all-in, all-out. Dynamic = continuous flow')
# # Stages a dynamic group may be in.
# 'stage': fields.many2one('farm.animal.group.stage')
@classmethod
def __setup__(cls):
super(AnimalGroup, cls).__setup__()
cls._sql_constraints += [
('initial_quantity_positive', 'check (initial_quantity > 0)',
'In Groups, the initial quantity must be positive (greater or '
'equals 1)'),
]
cls._error_messages.update({
'missing_supplier_location': ('Supplier Location of '
'company\'s party "%s" is empty but it is required to '
'create the arrival stock move for a new group.'),
'missing_production_location': ('The warehouse location "%s" '
'doesn\'t have set production location, but it is '
'required to create the arrival stock move for a new '
'group.'),
'no_farm_specie_farm_line_available': ('The specified farm '
'"%(farm)s" is not configured as farm with groups for '
'the specie "%(specie)s".'),
'invalid_group_destination': ('The event "%(event)s" is '
'trying to move the group "%(group)s" to location '
'"%(location)s", but the location\'s warehouse is not '
'configured as a farm for this kind of animals.'),
})
@staticmethod
def default_specie():
return Transaction().context.get('specie')
@staticmethod
def default_origin():
return 'purchased'
@staticmethod
def default_arrival_date():
return date.today()
@staticmethod
def default_active():
return True
def get_rec_name(self, name):
name = self.lot.number
if not self.active:
name += ' (*)'
return name
@classmethod
def search_rec_name(cls, name, clause):
return [('lot.number',) + clause[1:]]
def get_number(self, name):
return self.lot.number
@classmethod
def set_number(cls, instances, name, value):
Lot = Pool().get('stock.lot')
lots = [group.lot for group in instances if group.lot]
if lots:
Lot.write(lots, {
'number': value,
})
@classmethod
def get_farms(cls, animal_groups, name):
pool = Pool()
Location = pool.get('stock.location')
Lot = pool.get('stock.lot')
warehouses = Location.search([
('type', '=', 'warehouse'),
])
qbl = Lot.quantity_by_location([ag.lot for ag in animal_groups],
location_ids=[w.id for w in warehouses], with_childs=True)
res = {}
for animal_group in animal_groups:
ag_lot_id = animal_group.lot.id
res[animal_group.id] = [l for l in qbl[ag_lot_id]
if qbl[ag_lot_id][l] > 0.0]
return res
@classmethod
def search_farms(cls, name, domain=None):
pool = Pool()
Location = pool.get('stock.location')
Product = pool.get('product.product')
Specie = pool.get('farm.specie')
if not domain:
return []
specie_id = cls.default_specie()
product_ids = None
if specie_id:
specie = Specie(specie_id)
if not specie.group_product:
return []
product_ids = [specie.group_product.id]
specie_warehouse_ids = [l.farm.id for l in specie.farm_lines
if l.has_group]
warehouses = Location.search([
('id', 'in', specie_warehouse_ids),
('type', '=', 'warehouse'),
('warehouse',) + domain[1:],
])
else:
warehouses = Location.search([
('type', '=', 'warehouse'),
('warehouse',) + domain[1:],
])
if not warehouses:
return []
pbl = Product.products_by_location([w.id for w in warehouses],
product_ids=product_ids, with_childs=True,
grouping=('product', 'lot'))
lot_ids = set()
for (location_id, product_id, lot_id), quantity in pbl.iteritems():
if quantity > 0.0:
lot_ids.add(lot_id)
return [('lot', 'in', list(lot_ids))]
def on_change_with_current_weight(self, name=None):
if self.weights:
return self.weights[0].id
def check_in_location(self, location, timestamp, quantity=1):
with Transaction().set_context(
locations=[location.id],
stock_date_end=timestamp.date()):
return self.lot.quantity >= quantity
def check_allowed_location(self, location, event_rec_name):
for farm_line in self.specie.farm_lines:
if farm_line.farm.id == location.warehouse.id:
if farm_line.has_group:
return
self.raise_user_error('invalid_group_destination', {
'event': event_rec_name,
'group': self.rec_name,
'location': location.rec_name,
})
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.update({
'lot': False,
'farms': False,
'origin': False,
'arrival_date': False,
'purchase_shipment': False,
'removal_date': False,
'weights': False,
})
return super(AnimalGroup, cls).copy(records, default)
@classmethod
def create(cls, vlist):
pool = Pool()
Location = pool.get('stock.location')
Lot = pool.get('stock.lot')
context = Transaction().context
vlist = [x.copy() for x in vlist]
for vals in vlist:
if not vals.get('specie'):
vals['specie'] = context.get('specie')
if not vals.get('number'):
location = Location(vals['initial_location'])
vals['number'] = cls._calc_number(vals['specie'],
location.warehouse.id)
if vals.get('lot'):
lot = Lot(vals['lot'])
Lot.write([lot], cls._get_lot_values(vals, False))
else:
new_lot, = Lot.create([cls._get_lot_values(vals, True)])
vals['lot'] = new_lot.id
new_groups = super(AnimalGroup, cls).create(vlist)
if not context.get('no_create_stock_move'):
cls._create_and_done_first_stock_move(new_groups)
return new_groups
@classmethod
def _calc_number(cls, specie_id, farm_id):
pool = Pool()
FarmLine = pool.get('farm.specie.farm_line')
Location = pool.get('stock.location')
Sequence = pool.get('ir.sequence')
Specie = pool.get('farm.specie')
farm_lines = FarmLine.search([
('specie', '=', specie_id),
('farm', '=', farm_id),
('has_group', '=', True),
])
if not farm_lines:
cls.raise_user_error('no_farm_specie_farm_line_available', {
'farm': Location(farm_id).rec_name,
'specie': Specie(specie_id).rec_name,
})
sequence = farm_lines[0].group_sequence
return sequence and Sequence.get_id(sequence.id) or ''
@classmethod
def _get_lot_values(cls, group_vals, create):
"""
Prepare values to create the stock.lot for the new group.
group_vals: dictionary with values to create farm.animal.group
It returns a dictionary with values to create stock.lot
"""
Specie = Pool().get('farm.specie')
if not group_vals:
return {}
specie = Specie(group_vals['specie'])
return {
'number': group_vals['number'],
'product': (specie.group_product and specie.group_product.id or
None),
'animal_type': 'group',
}
@classmethod
def delete(cls, groups):
pool = Pool()
Lot = pool.get('stock.lot')
lots = [g.lot for g in groups]
result = super(AnimalGroup, cls).delete(groups)
if lots:
Lot.delete(lots)
return result
class AnimalGroupTag(ModelSQL):
'Animal Group - Tag'
__name__ = 'farm.animal.group-farm.tag'
group = fields.Many2One('farm.animal.group', 'Group', required=True)
tag = fields.Many2One('farm.tag', 'Tag', required=True)
class AnimalGroupWeight(ModelSQL, ModelView):
'Farm Animal Group Weight Record'
__name__ = 'farm.animal.group.weight'
_order = ('timestamp', 'DESC')
group = fields.Many2One('farm.animal.group', 'Group', required=True,
ondelete='CASCADE')
timestamp = fields.DateTime('Date & Time', required=True)
quantity = fields.Integer('Number of individuals', required=True)
uom = fields.Many2One('product.uom', "UoM", required=True,
domain=[('category', '=', Id('product', 'uom_cat_weight'))])
unit_digits = fields.Function(fields.Integer('Unit Digits',
on_change_with=['uom']),
'on_change_with_unit_digits')
weight = fields.Numeric('Weight',
digits=(16, Eval('unit_digits', 2)),
required=True,
depends=['unit_digits'])
@staticmethod
def default_timestamp():
return datetime.now()
@staticmethod
def default_uom():
return Pool().get('ir.model.data').get_id('product', 'uom_kilogram')
@staticmethod
def default_unit_digits():
return 2
def get_rec_name(self, name):
return '%s %s (%s)' % (self.weight, self.uom.symbol, self.timestamp)
@classmethod
def search_rec_name(cls, name, clause):
operand = clause[2]
operand = operand.replace('%', '')
try:
operand = Decimal(operand)
except:
return [('weight', '=', 0)]
operator = clause[1]
operator = operator.replace('ilike', '=').replace('like', '=')
return [('weight', operator, operand)]
def on_change_with_unit_digits(self, name=None):
if self.uom:
return self.uom.digits
return 2

115
animal_group.xml Normal file
View File

@ -0,0 +1,115 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!--
farm.animal.group
-->
<!-- Views -->
<record model="ir.ui.view" id="farm_animal_group_form_view">
<field name="model">farm.animal.group</field>
<field name="type">form</field>
<field name="name">farm_animal_group_form</field>
</record>
<record model="ir.ui.view" id="farm_animal_group_list_view">
<field name="model">farm.animal.group</field>
<field name="type">tree</field>
<field name="name">farm_animal_group_list</field>
</record>
<!-- Actions -->
<record model="ir.action.act_window" id="act_farm_animal_group">
<field name="name">Animal Group</field>
<field name="res_model">farm.animal.group</field>
<field name="search_value"></field>
<!-- <field name="domain">[]</field> -->
<!-- <field name="context">{}</field> -->
</record>
<record model="ir.action.act_window.view" id="act_farm_animal_group_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_animal_group_list_view"/>
<field name="act_window" ref="act_farm_animal_group"/>
</record>
<record model="ir.action.act_window.view" id="act_farm_animal_group_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_animal_group_form_view"/>
<field name="act_window" ref="act_farm_animal_group"/>
</record>
<!-- Permissions -->
<record model="ir.model.access" id="access_farm_animal_group">
<field name="model" search="[('model', '=', 'farm.animal.group')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_farm_animal_group_admin">
<field name="model" search="[('model', '=', 'farm.animal.group')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!-- Sequences -->
<record model="ir.sequence.type" id="sequence_type_animal_group">
<field name="name">Farm Animal Group</field>
<field name="code">farm.animal.group</field>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_animal_group_group_admin">
<field name="sequence_type" ref="sequence_type_animal_group"/>
<field name="group" ref="res.group_admin"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_animal_group_group_animal_admin">
<field name="sequence_type" ref="sequence_type_animal_group"/>
<field name="group" ref="group_farm_admin"/>
</record>
<!--
farm.animal.group.weight
-->
<!-- Views -->
<record model="ir.ui.view" id="farm_animal_group_weight_form_view">
<field name="model">farm.animal.group.weight</field>
<field name="type">form</field>
<field name="name">farm_animal_group_weight_form</field>
</record>
<record model="ir.ui.view" id="farm_animal_group_weight_list_view">
<field name="model">farm.animal.group.weight</field>
<field name="type">tree</field>
<field name="name">farm_animal_group_weight_list</field>
</record>
<!-- Permissions -->
<record model="ir.model.access" id="access_farm_animal_group_weight">
<field name="model"
search="[('model', '=', 'farm.animal.group.weight')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access"
id="access_farm_animal_group_weight_admin">
<field name="model"
search="[('model', '=', 'farm.animal.group.weight')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!-- Menus -->
<!--<menuitem action="act_farm_animal_group" id="menu_farm_animal_group" parent="menu_farm" sequence="1"/>-->
</data>
</tryton>

6
doc/index.rst Normal file
View File

@ -0,0 +1,6 @@
Farm Module
###########
The Farm module defines the following models: animal,

19
events/__init__.py Normal file
View File

@ -0,0 +1,19 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from .abstract_event import *
from .move_event import *
from .feed_event import *
from .feed_inventory import *
from .medication_event import *
from .transformation_event import *
from .removal_event import *
from .semen_extraction_event import *
from .insemination_event import *
from .pregnancy_diagnosis_event import *
from .abort_event import *
from .farrowing_event import *
from .foster_event import *
from .weaning_event import *
from .event_order import *

92
events/abort_event.py Normal file
View File

@ -0,0 +1,92 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import fields, ModelView, ModelSQL, Workflow
from trytond.pyson import Equal, Eval, If
from .abstract_event import AbstractEvent, _STATES_VALIDATED, \
_DEPENDS_VALIDATED
__all__ = ['AbortEvent', 'AbortEventFemaleCycle']
class AbortEvent(AbstractEvent):
'''Farm Abort Event'''
__name__ = 'farm.abort.event'
_table = 'farm_abort_event'
female_cycle = fields.One2One('farm.abort.event-farm.animal.female_cycle',
'event', 'cycle', string='Female Cycle', readonly=True, domain=[
('animal', '=', Eval('animal')),
], states=_STATES_VALIDATED,
depends=_DEPENDS_VALIDATED + ['animal'])
@classmethod
def __setup__(cls):
super(AbortEvent, cls).__setup__()
cls.animal.domain += [
('type', '=', 'female'),
('current_cycle', '!=', False),
If(Equal(Eval('state'), 'draft'),
('current_cycle.state', '=', 'pregnant'),
()),
]
@staticmethod
def default_animal_type():
return 'female'
@staticmethod
def valid_animal_types():
return ['female']
def get_rec_name(self, name):
cycle = (self.female_cycle and self.female_cycle.sequence or
self.animal.current_cycle and self.animal.current_cycle.sequence
or None)
if cycle:
return "%s on cycle %s %s" % (self.animal.rec_name, cycle,
self.timestamp)
return super(AbortEvent, self).get_rec_name(name)
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
"""
Updates the state of female
"""
for diagnosis_event in events:
diagnosis_event.female_cycle = diagnosis_event.animal.current_cycle
diagnosis_event.save()
diagnosis_event.female_cycle.update_state(diagnosis_event)
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.update({
'female_cycle': None,
})
return super(AbortEvent, cls).copy(records, default=default)
class AbortEventFemaleCycle(ModelSQL):
"Abort Event - Female Cycle"
__name__ = 'farm.abort.event-farm.animal.female_cycle'
event = fields.Many2One('farm.abort.event', 'Abort Event', required=True,
ondelete='RESTRICT')
cycle = fields.Many2One('farm.animal.female_cycle', 'Female Cycle',
required=True, ondelete='RESTRICT')
@classmethod
def __setup__(cls):
super(AbortEventFemaleCycle, cls).__setup__()
cls._sql_constraints += [
('event_unique', 'UNIQUE(event)',
'The Abort Event must be unique.'),
('cycle_unique', 'UNIQUE(cycle)',
'The Female Cycle must be unique.'),
]

59
events/abort_event.xml Normal file
View File

@ -0,0 +1,59 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- farm.abort.event -->
<record model="ir.ui.view"
id="farm_abort_event_form_view">
<field name="model">farm.abort.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_abort_event_form</field>
</record>
<record model="ir.ui.view"
id="farm_abort_event_list_view">
<field name="model">farm.abort.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_abort_event_list</field>
</record>
<record model="ir.model.button"
id="validate_abort_event_button">
<field name="name">validate_event</field>
<field name="model" search="[('model', '=', 'farm.abort.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_abort_event_button_group_farm_admin">
<field name="button" ref="validate_abort_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_abort_event_button_group_farm_females">
<field name="button" ref="validate_abort_event_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.action.act_window" id="act_farm_abort_event">
<field name="name">Abort Events</field>
<field name="res_model">farm.abort.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context">{'animal_type': 'female'}</field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_abort_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_abort_event_list_view"/>
<field name="act_window" ref="act_farm_abort_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_abort_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_abort_event_form_view"/>
<field name="act_window" ref="act_farm_abort_event"/>
</record>
</data>
</tryton>

208
events/abstract_event.py Normal file
View File

@ -0,0 +1,208 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from datetime import datetime
from trytond.model import fields, ModelSQL, ModelView, Workflow
from trytond.pyson import Equal, Eval, If, Id, Not
from trytond.transaction import Transaction
__all__ = ['AbstractEvent']
_EVENT_STATES = [
('draft', 'Draft'),
('validated', 'Validated'),
#('cancel', 'Cancelled'),
]
_STATES_WRITE_DRAFT = {
'readonly': Not(Equal(Eval('state'), 'draft')),
}
_DEPENDS_WRITE_DRAFT = ['state']
_STATES_VALIDATED = {
'required': Equal(Eval('state'), 'validated'),
}
_DEPENDS_VALIDATED = ['state']
_STATES_VALIDATED_ADMIN = {
'required': Equal(Eval('state'), 'validated'),
'invisible': Not(Eval('groups', []).contains(
Id('farm', 'group_farm_admin'))),
}
_DEPENDS_VALIDATED_ADMIN = ['state']
class AbstractEvent(ModelSQL, ModelView, Workflow):
'Event'
__name__ = 'farm.event'
_order = [
('timestamp', 'ASC'),
('id', 'DESC'),
]
animal_type = fields.Selection([
('male', 'Male'),
('female', 'Female'),
('individual', 'Individual'),
('group', 'Group'),
], "Animal Type", required=True, readonly=True, select=True)
specie = fields.Many2One('farm.specie', 'Specie', required=True,
readonly=True, select=True)
farm = fields.Many2One('stock.location', 'Farm', required=True,
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT,
domain=[
('type', '=', 'warehouse'),
],
context={
'restrict_by_specie_animal_type': True,
})
order = fields.Many2One('farm.event.order', 'Order', readonly=True)
animal = fields.Many2One('farm.animal', 'Animal', domain=[
('specie', '=', Eval('specie')),
('type', '=', Eval('animal_type')),
If(Equal(Eval('state'), 'draft'),
('farm', '=', Eval('farm')),
()),
],
select=True, states={
'invisible': Equal(Eval('animal_type'), 'group'),
'required': Not(Equal(Eval('animal_type'), 'group')),
'readonly': Not(Equal(Eval('state'), 'draft')),
},
on_change=['animal_type', 'animal'],
depends=['specie', 'animal_type', 'farm', 'state'])
animal_group = fields.Many2One('farm.animal.group', 'Group', domain=[
('specie', '=', Eval('specie')),
('farms', 'in', [Eval('farm')]),
],
select=True, states={
'invisible': Not(Equal(Eval('animal_type'), 'group')),
'required': Equal(Eval('animal_type'), 'group'),
'readonly': Not(Equal(Eval('state'), 'draft')),
},
on_change=['animal_type', 'animal_group'],
depends=['specie', 'animal_type', 'farm', 'state'])
lot = fields.Function(fields.Many2One('stock.lot', 'Lot'),
'get_lot')
# TODO: Used for permission management and filtering. If dot notation
# doesn't work implement it
#current_farms = fields.Function(fields.Many2Many('stock.warehouse',
# None, None, 'Current Farms', help='The farms (warehouses) '
# 'where the animal or group is now.'), 'get_current_warehouse')
timestamp = fields.DateTime('Date & Time', required=True,
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT)
employee = fields.Many2One('party.party', 'Employee',
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT,
help='Employee that did the job.')
notes = fields.Text('Notes')
state = fields.Selection(_EVENT_STATES, 'State', required=True,
readonly=True, select=True)
@classmethod
def __setup__(cls):
super(AbstractEvent, cls).__setup__()
cls._error_messages.update({
'invalid_state_to_delete': ("The event '%s' can't be deleted "
"because is not in 'Draft' state."),
})
cls._buttons.update({
#'cancel': {
# 'invisible': Eval('state') == 'cancel',
# },
#'draft': {
# 'invisible': Eval('state') == 'draft',
# },
'validate_event': {
'invisible': Eval('state') != 'draft',
},
})
cls._transitions = set((
#('draft', 'cancel'),
('draft', 'validated'),
#('validated', 'cancel'),
#('cancel', 'draft'),
))
@staticmethod
def default_specie():
return Transaction().context.get('specie')
@staticmethod
def default_animal_type():
return Transaction().context.get('animal_type')
@staticmethod
def default_timestamp():
return Transaction().context.get('timestamp') or datetime.now()
@staticmethod
def default_employee():
return Transaction().context.get('employee')
@staticmethod
def default_state():
return 'draft'
def get_rec_name(self, name):
if self.animal_type == 'group':
return "%s %s" % (self.animal_group.rec_name, self.timestamp)
else:
return "%s %s" % (self.animal.rec_name, self.timestamp)
def get_lot(self, name):
if self.animal_type == 'group':
return self.animal_group.lot.id
return self.animal.lot.id
@staticmethod
def valid_animal_types():
raise NotImplementedError(
"Please Implement valid_animal_types() method")
def on_change_animal(self):
if (self.animal_type == 'group' or not self.animal or
not self.animal.farm):
return {}
return {
'farm': self.animal.farm.id
}
def on_change_animal_group(self):
if (self.animal_type != 'group' or not self.animal_group or
not self.animal_group.farms):
return {}
return {
'farm': self.animal_group.farms[0].id
}
@classmethod
def copy(cls, events, default=None):
if default is None:
default = {}
default['state'] = 'draft'
return super(AbstractEvent, cls).copy(events, default)
@classmethod
def delete(cls, events):
for event in events:
if event.state != 'draft':
cls.raise_user_error('invalid_state_to_delete', event.rec_name)
return super(AbstractEvent, cls).delete(events)
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, events):
pass
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
"""
Tests if animal or group is in warehouse in the timestamp of event
"""
raise NotImplementedError("Please Implement validate_event() method")
#@classmethod
#@ModelView.button
#@Workflow.transition('cancel')
#def cancel(cls, events):
# raise NotImplementedError("Please Implement cancel() method")

49
events/abstract_event.xml Normal file
View File

@ -0,0 +1,49 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- farm.event -->
<record model="ir.ui.view" id="farm_abstract_event_form_view">
<field name="model">farm.event</field>
<field name="type">form</field>
<field name="name">farm_event_form</field>
</record>
<record model="ir.ui.view" id="farm_abstract_event_list_view">
<field name="model">farm.event</field>
<field name="type">tree</field>
<field name="name">farm_event_list</field>
</record>
<!--
<record model="ir.model.button" id="farm_event_cancel_button">
<field name="name">cancel</field>
<field name="model" search="[('model', '=', 'farm.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="farm_event_cancel_button_group_farm_admin">
<field name="button" ref="farm_event_cancel_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button" id="farm_event_draft_button">
<field name="name">draft</field>
<field name="model" search="[('model', '=', 'farm.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="farm_event_draft_button_group_farm_admin">
<field name="button" ref="farm_event_draft_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button" id="farm_event_validate_button">
<field name="name">valid</field>
<field name="model" search="[('model', '=', 'farm.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="farm_event_validate_button_group_farm_admin">
<field name="button" ref="farm_event_validate_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
-->
</data>
</tryton>

281
events/event_order.py Normal file
View File

@ -0,0 +1,281 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
import logging
from datetime import datetime
from trytond.model import fields, ModelSQL, ModelView, Workflow
from trytond.pyson import Bool, Equal, Eval, Get, Not
from trytond.pool import Pool
from trytond.transaction import Transaction
__all__ = ['EventOrder']
# The fields of the header are readonly if there are lines defined because they
# are used in the lines' domain
_STATES_HEADER = {
'readonly': (Bool(Eval('medication_events')) |
Bool(Eval('insemination_events')) |
Bool(Eval('pregnancy_diagnosis_events')) |
Bool(Eval('abort_events')) |
Bool(Eval('farrowing_events')) |
Bool(Eval('foster_events')) |
Bool(Eval('weaning_events'))),
}
_DEPENDS_HEADER = ['medication_events', 'insemination_events',
'pregnancy_diagnosis_events', 'abort_events', 'farrowing_events',
'foster_events', 'weaning_events']
_DOMAIN_LINES = [
('animal_type', '=', Eval('animal_type')),
('specie', '=', Eval('specie')),
('farm', '=', Eval('farm')),
('timestamp', '=', Eval('timestamp')),
('employee', '=', Eval('employee')),
]
_DEPENDS_LINES = ['animal_type', 'specie', 'event_type', 'farm', 'timestamp',
'employee']
def _STATES_LINES(event_type):
return {
'invisible': Not(Equal(Eval('event_type'), event_type)),
}
class EventOrder(ModelSQL, ModelView, Workflow):
'Farm Events Work Order'
__name__ = 'farm.event.order'
_order = [('name', 'ASC')]
name = fields.Char("Reference", select=True)
animal_type = fields.Selection([
('male', 'Male'),
('female', 'Female'),
('individual', 'Individual'),
('group', 'Group'),
], "Animal Type", required=True, readonly=True, select=True,
states={
'invisible': Bool(Get(Eval('context', {}), 'animal_type')),
})
specie = fields.Many2One('farm.specie', 'Specie', required=True,
readonly=True, select=True, states={
'invisible': Bool(Get(Eval('context', {}), 'specie')),
})
event_type = fields.Selection([
('medication', 'Medications'),
('insemination', 'Inseminations'),
('pregnancy_diagnosis', 'Pregnancy Diagnosis'),
('abort', 'Aborts'),
('farrowing', 'Farrowings'),
('foster', 'Fosters'),
('weaning', 'Weanings'),
], "Event Type", required=True, readonly=True, select=True,
states={
'invisible': Bool(Get(Eval('context', {}), 'event_type')),
})
farm = fields.Many2One('stock.location', 'Farm', required=True,
domain=[
('type', '=', 'warehouse'),
],
context={
'restrict_by_specie_animal_type': True,
},
states=_STATES_HEADER, depends=_DEPENDS_HEADER)
timestamp = fields.DateTime('Date & Time', required=True,
states=_STATES_HEADER, depends=_DEPENDS_HEADER)
employee = fields.Many2One('party.party', 'Employee',
states=_STATES_HEADER, depends=_DEPENDS_HEADER,
help='Employee that did the job.')
# Generic Events
medication_events = fields.One2Many('farm.medication.event', 'order',
'Medication Events', domain=_DOMAIN_LINES,
states=_STATES_LINES('medication'), depends=_DEPENDS_LINES)
# Female Events
insemination_events = fields.One2Many('farm.insemination.event', 'order',
'Insemination Events', domain=_DOMAIN_LINES,
states=_STATES_LINES('insemination'), depends=_DEPENDS_LINES)
pregnancy_diagnosis_events = fields.One2Many(
'farm.pregnancy_diagnosis.event', 'order',
'Pregnancy Diagnosis Events', domain=_DOMAIN_LINES,
states=_STATES_LINES('pregnancy_diagnosis'), depends=_DEPENDS_LINES)
abort_events = fields.One2Many('farm.abort.event', 'order', 'Abort Events',
domain=_DOMAIN_LINES, states=_STATES_LINES('abort'),
depends=_DEPENDS_LINES)
farrowing_events = fields.One2Many('farm.farrowing.event', 'order',
'Farrowing Events', domain=_DOMAIN_LINES,
states=_STATES_LINES('farrowing'), depends=_DEPENDS_LINES)
foster_events = fields.One2Many('farm.foster.event', 'order',
'Foster Events', domain=_DOMAIN_LINES,
states=_STATES_LINES('foster'), depends=_DEPENDS_LINES)
weaning_events = fields.One2Many('farm.weaning.event', 'order',
'Weaning Events', domain=_DOMAIN_LINES,
states=_STATES_LINES('weaning'), depends=_DEPENDS_LINES)
@classmethod
def __setup__(cls):
super(EventOrder, cls).__setup__()
cls._sql_constraints += [
('name_required', 'CHECK (name IS NOT NULL)',
'The Reference of the Event Order is required.'),
('name_uniq', 'UNIQUE (name)',
'The Reference of the Event Order must be unique.'),
]
cls._error_messages.update({
'incompatible_animal_and_event_type': ('The Animal and Event '
'Type of Event Order "%s" are incompatibles.'),
'no_farm_specie_farm_line_available': ('The specified farm '
'"%(farm)s" is not configured as farm with '
'"%(animal_type)s" for the specie "%(specie)s"'),
})
cls._buttons.update({
'draft': {},
'confirm': {},
'cancel': {},
})
@staticmethod
def default_animal_type():
return Transaction().context.get('animal_type')
@staticmethod
def default_specie():
return Transaction().context.get('specie')
@staticmethod
def default_event_type():
return Transaction().context.get('event_type')
@staticmethod
def default_timestamp():
return Transaction().context.get('timestamp') or datetime.now()
@staticmethod
def default_employee():
return Transaction().context.get('employee')
@classmethod
def validate(cls, orders):
super(EventOrder, cls).validate(orders)
for order in orders:
order.check_animal_and_event_type()
def check_animal_and_event_type(self):
if self.event_type not in self.event_types_by_animal_type(
self.animal_type, True):
self.raise_user_error('incompatible_animal_and_event_type',
self.rec_name)
@staticmethod
def event_types_by_animal_type(animal_type, include_generic):
res = []
if animal_type == 'generic' or include_generic:
res.append('medication')
if animal_type == 'female':
res += [
'insemination',
'pregnancy_diagnosis',
'abort',
'farrowing',
'foster',
'weaning',
]
return res
@classmethod
def create(cls, vlist):
vlist = [x.copy() for x in vlist]
for vals in vlist:
logging.getLogger(cls.__name__).debug("Create vals: %s" % vals)
if not vals.get('specie'):
vals['specie'] = cls.default_specie()
if not vals.get('animal_type'):
vals['animal_type'] = cls.default_animal_type()
if not vals.get('name'):
vals['name'] = cls._calc_name(vals['specie'], vals['farm'],
vals['animal_type'])
return super(EventOrder, cls).create(vlist)
@classmethod
def copy(cls, orders, default=None):
if default is None:
default = {}
default.update({
'timestamp': datetime.datetime.now(),
'medication_events': None,
'insemination_events': None,
'pregnancy_diagnosis_events': None,
'abort_events': None,
'farrowing_events': None,
'foster_events': None,
'weaning_events': None,
})
res = []
for order in orders:
new_default = default.copy()
new_default.update({
'name': cls._calc_name(order.specie.id, order.farm.id,
order.animal_type),
})
new_order, = super(EventOrder, cls).copy([order],
default=new_default)
res.append(new_order)
return res
@classmethod
def _calc_name(cls, specie_id, farm_id, animal_type):
pool = Pool()
FarmLine = pool.get('farm.specie.farm_line')
Location = pool.get('stock.location')
Sequence = pool.get('ir.sequence')
Specie = pool.get('farm.specie')
farm_lines = FarmLine.search([
('specie', '=', specie_id),
('farm', '=', farm_id),
('has_' + animal_type, '=', True),
])
if not farm_lines:
cls.raise_user_error('no_farm_specie_farm_line_available', {
'farm': Location(farm_id).rec_name,
'animal_type': animal_type,
'specie': Specie(specie_id).rec_name,
})
farm_line, = farm_lines
return Sequence.get_id(farm_line.event_order_sequence.id)
@classmethod
@ModelView.button
def draft(cls, orders):
pool = Pool()
for order in orders:
Event = pool.get('farm.%s.event' % order.event_type)
events = Event.search([
('order', '=', order.id),
('state', '=', 'cancel'),
])
Event.draft(events)
@classmethod
@ModelView.button
def confirm(cls, orders):
pool = Pool()
for order in orders:
Event = pool.get('farm.%s.event' % order.event_type)
events = Event.search([
('order', '=', order.id),
('state', '=', 'draft'),
])
Event.validate_event(events)
@classmethod
@ModelView.button
def cancel(cls, orders):
pool = Pool()
for order in orders:
Event = pool.get('farm.%s.event' % order.event_type)
events = Event.search([
('order', '=', order.id),
('state', '=', 'validated'), # also in 'draft'?
])
Event.cancel(events)

212
events/event_order.xml Normal file
View File

@ -0,0 +1,212 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- Sequences -->
<record model="ir.sequence.type" id="sequence_type_event_order">
<field name="name">Farm Event Order</field>
<field name="code">farm.event.order</field>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_event_order_group_farm">
<field name="sequence_type" ref="sequence_type_event_order"/>
<field name="group" ref="group_farm"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_group_group_event_order_admin">
<field name="sequence_type" ref="sequence_type_event_order"/>
<field name="group" ref="group_farm"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_group_group_event_order_males">
<field name="sequence_type" ref="sequence_type_event_order"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_group_group_event_order_females">
<field name="sequence_type" ref="sequence_type_event_order"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_group_group_event_order_individuals">
<field name="sequence_type" ref="sequence_type_event_order"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_group_group_event_order_groups">
<field name="sequence_type" ref="sequence_type_event_order"/>
<field name="group" ref="group_farm_groups"/>
</record>
<!-- farm.event.order -->
<record model="ir.ui.view" id="farm_event_order_form_view">
<field name="model">farm.event.order</field>
<field name="type">form</field>
<field name="name">farm_event_order_form</field>
</record>
<record model="ir.ui.view" id="farm_event_order_tree_view">
<field name="model">farm.event.order</field>
<field name="type">tree</field>
<field name="name">farm_event_order_list</field>
</record>
<record model="ir.model.button" id="draft_farm_event_order_button">
<field name="name">draft</field>
<field name="model"
search="[('model', '=', 'farm.event.order')]"/>
</record>
<record model="ir.model.button-res.group"
id="draft_farm_event_order_button_group_farm_admin">
<field name="button" ref="draft_farm_event_order_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="draft_farm_event_order_button_group_farm_males">
<field name="button" ref="draft_farm_event_order_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="draft_farm_event_order_button_group_farm_females">
<field name="button" ref="draft_farm_event_order_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="draft_farm_event_order_button_group_farm_individuals">
<field name="button" ref="draft_farm_event_order_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="draft_farm_event_order_button_group_farm_groups">
<field name="button" ref="draft_farm_event_order_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.model.button"
id="confirm_farm_event_order_button">
<field name="name">confirm</field>
<field name="model" search="[('model', '=', 'farm.event.order')]"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_farm_event_order_button_group_farm_admin">
<field name="button" ref="confirm_farm_event_order_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_farm_event_order_button_group_farm_males">
<field name="button" ref="confirm_farm_event_order_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_farm_event_order_button_group_farm_females">
<field name="button" ref="confirm_farm_event_order_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_farm_event_order_button_group_farm_individuals">
<field name="button" ref="confirm_farm_event_order_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_farm_event_order_button_group_farm_groups">
<field name="button" ref="confirm_farm_event_order_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.model.button" id="cancel_farm_event_order_button">
<field name="name">cancel</field>
<field name="model" search="[('model', '=', 'farm.event.order')]"/>
</record>
<record model="ir.model.button-res.group"
id="cancel_farm_event_order_button_group_farm_admin">
<field name="button" ref="cancel_farm_event_order_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="cancel_farm_event_order_button_group_farm_males">
<field name="button" ref="cancel_farm_event_order_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="cancel_farm_event_order_button_group_farm_females">
<field name="button" ref="cancel_farm_event_order_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="cancel_farm_event_order_button_group_farm_individuals">
<field name="button" ref="cancel_farm_event_order_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="cancel_farm_event_order_button_group_farm_groups">
<field name="button" ref="cancel_farm_event_order_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.action.act_window" id="act_farm_event_order">
<field name="name">Work Orders</field>
<field name="res_model">farm.event.order</field>
<field name="search_value"></field>
</record>
<record model="ir.action.act_window.view" id="act_farm_event_order_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_event_order_tree_view"/>
<field name="act_window" ref="act_farm_event_order"/>
</record>
<record model="ir.action.act_window.view" id="act_farm_event_order_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_event_order_form_view"/>
<field name="act_window" ref="act_farm_event_order"/>
</record>
<record model="ir.model.access" id="access_farm_event_order_farm">
<field name="model" search="[('model', '=', 'farm.event.order')]"/>
<field name="group" ref="group_farm"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_farm_event_order_admin">
<field name="model" search="[('model', '=', 'farm.event.order')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_farm_event_order_males">
<field name="model" search="[('model', '=', 'farm.event.order')]"/>
<field name="group" ref="group_farm_males"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_farm_event_order_females">
<field name="model" search="[('model', '=', 'farm.event.order')]"/>
<field name="group" ref="group_farm_females"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access"
id="access_farm_event_order_individuals">
<field name="model" search="[('model', '=', 'farm.event.order')]"/>
<field name="group" ref="group_farm_individuals"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_farm_event_order_groups">
<field name="model" search="[('model', '=', 'farm.event.order')]"/>
<field name="group" ref="group_farm_groups"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
</data>
</tryton>

221
events/farrowing_event.py Normal file
View File

@ -0,0 +1,221 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import fields, ModelView, ModelSQL, Workflow
from trytond.pyson import And, Bool, Equal, Eval, Id, If, Not
from trytond.pool import Pool
from trytond.transaction import Transaction
from .abstract_event import AbstractEvent, _STATES_WRITE_DRAFT, \
_DEPENDS_WRITE_DRAFT, _STATES_VALIDATED, _DEPENDS_VALIDATED
__all__ = ['FarrowingProblem', 'FarrowingEvent', 'FarrowingEventFemaleCycle',
'FarrowingEventAnimalGroup']
class FarrowingProblem(ModelSQL, ModelView):
'''Farrowing Event Problem'''
__name__ = 'farm.farrowing.problem'
_order_name = 'name'
name = fields.Char('Name', required=True, translate=True)
class FarrowingEvent(AbstractEvent):
'''Farm Farrowing Event'''
__name__ = 'farm.farrowing.event'
_table = 'farm_farrowing_event'
live = fields.Integer('Live', states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT)
stillborn = fields.Integer('Stillborn', states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT)
mummified = fields.Integer('Mummified', states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT)
dead = fields.Function(fields.Integer('Dead'),
'get_dead')
problem = fields.Many2One('farm.farrowing.problem', 'Problem',
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT)
female_cycle = fields.One2One(
'farm.farrowing.event-farm.animal.female_cycle', 'event', 'cycle',
string='Female Cycle', readonly=True, domain=[
('animal', '=', Eval('animal')),
],
states=_STATES_VALIDATED, depends=_DEPENDS_VALIDATED + ['animal'])
produced_group = fields.One2One('farm.farrowing.event-farm.animal.group',
'event', 'animal_group', string='Produced Group', domain=[
('specie', '=', Eval('specie')),
('initial_quantity', '=', Eval('live')),
], readonly=True,
states={
'required': And(Equal(Eval('state'), 'validated'),
Bool(Eval('live', 0))),
},
depends=['specie', 'live', 'state'])
move = fields.Many2One('stock.move', 'Stock Move', readonly=True, domain=[
('lot.animal_group', '=', Eval('produced_group')),
],
states={
'required': And(Equal(Eval('state'), 'validated'),
Bool(Eval('live', 0))),
'invisible': Not(Eval('groups', []).contains(
Id('farm', 'group_farm_admin'))),
},
depends=['produced_group', 'live', 'state'])
@classmethod
def __setup__(cls):
super(FarrowingEvent, cls).__setup__()
cls.animal.domain += [
('type', '=', 'female'),
('current_cycle', '!=', False),
If(Equal(Eval('state'), 'draft'),
('current_cycle.state', '=', 'pregnant'),
()),
]
cls._error_messages.update({
'event_without_dead_nor_live': ('The farrowing event "%s" has '
'0 in Dead and Live. It has to have some unit in some of '
'these fields.'),
})
cls._sql_constraints += [
('live_not_negative', 'CHECK(live >= 0)',
'The value of "Live" must to be positive.'),
('stillborn_not_negative', 'CHECK(stillborn >= 0)',
'The value of "Stillborn" must to be positive.'),
('mummified_not_negative', 'CHECK(mummified >= 0)',
'The value of "Mummified" must to be positive.'),
]
cls._buttons['validate_event']['readonly'] = And(
Equal(Eval('live', 0), 0),
Equal(Eval('dead', 0), 0))
@staticmethod
def default_animal_type():
return 'female'
@staticmethod
def valid_animal_types():
return ['female']
def get_rec_name(self, name):
cycle = (self.female_cycle and self.female_cycle.sequence or
self.animal.current_cycle and self.animal.current_cycle.sequence
or None)
if cycle:
return "%s on cycle %s %s" % (self.animal.rec_name, cycle,
self.timestamp)
return super(FarrowingEvent, self).get_rec_name(name)
def get_dead(self, name):
return (self.stillborn or 0) + (self.mummified or 0)
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
Move = Pool().get('stock.move')
todo_moves = []
for farrowing_event in events:
if farrowing_event.dead == 0 and farrowing_event.live == 0:
cls.raise_user_error('event_without_dead_nor_live',
farrowing_event.rec_name)
current_cycle = farrowing_event.animal.current_cycle
farrowing_event.female_cycle = current_cycle
if farrowing_event.live != 0:
with Transaction().set_context(
no_create_stock_move=True):
produced_group = farrowing_event._get_produced_group()
produced_group.save()
farrowing_event.produced_group = produced_group
move = farrowing_event._get_event_move()
move.save()
farrowing_event.move = move
todo_moves.append(move)
farrowing_event.save()
current_cycle.update_state(farrowing_event)
Move.assign(todo_moves)
Move.do(todo_moves)
def _get_produced_group(self):
"""
Prepare values to create the produced group in female's farrowing
"""
AnimalGroup = Pool().get('farm.animal.group')
return AnimalGroup(
specie=self.specie,
breed=self.animal.breed,
initial_location=self.animal.location,
initial_quantity=self.live)
def _get_event_move(self):
pool = Pool()
Move = pool.get('stock.move')
context = Transaction().context
return Move(
product=self.specie.group_product.id,
uom=self.specie.group_product.default_uom.id,
quantity=self.live,
from_location=self.farm.production_location.id,
to_location=self.animal.location.id,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=self.produced_group.lot.id,
unit_price=self.produced_group.lot.product.cost_price,
origin=self)
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.update({
'female_cycle': None,
'produced_group': None,
'move': None,
})
return super(FarrowingEvent, cls).copy(records, default=default)
class FarrowingEventFemaleCycle(ModelSQL):
"Farrowing Event - Female Cycle"
__name__ = 'farm.farrowing.event-farm.animal.female_cycle'
event = fields.Many2One('farm.farrowing.event', 'Farrowing Event',
required=True, ondelete='RESTRICT')
cycle = fields.Many2One('farm.animal.female_cycle', 'Female Cycle',
required=True, ondelete='RESTRICT')
@classmethod
def __setup__(cls):
super(FarrowingEventFemaleCycle, cls).__setup__()
cls._sql_constraints += [
('event_unique', 'UNIQUE(event)',
'The Farrowing Event must be unique.'),
('cycle_unique', 'UNIQUE(cycle)',
'The Female Cycle must be unique.'),
]
class FarrowingEventAnimalGroup(ModelSQL):
"Farrowing Event - AnimalGroup"
__name__ = 'farm.farrowing.event-farm.animal.group'
event = fields.Many2One('farm.farrowing.event', 'Farrowing Event',
required=True, ondelete='RESTRICT')
animal_group = fields.Many2One('farm.animal.group', 'Group', required=True,
ondelete='RESTRICT')
@classmethod
def __setup__(cls):
super(FarrowingEventAnimalGroup, cls).__setup__()
cls._sql_constraints += [
('event_unique', 'UNIQUE(event)',
'The Farrowing Event must be unique.'),
('animal_group_unique', 'UNIQUE(animal_group)',
'The Animal Group must be unique.'),
]

121
events/farrowing_event.xml Normal file
View File

@ -0,0 +1,121 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- Farrowing Event Problems -->
<record id="farrowing_problem_extreme_difficulty"
model="farm.farrowing.problem">
<field name="name">Extreme difficulty</field>
</record>
<record id="farrowing_problem_force_required"
model="farm.farrowing.problem">
<field name="name">Force required</field>
</record>
<record id="farrowing_problem_needed_assistance"
model="farm.farrowing.problem">
<field name="name">Needed assistance</field>
</record>
<record id="farrowing_problem_minor_problems"
model="farm.farrowing.problem">
<field name="name">Minor problems</field>
</record>
<record id="farrowing_no_problems" model="farm.farrowing.problem">
<field name="name">No problems</field>
</record>
<!-- farm.farrowing.problem -->
<record model="ir.ui.view" id="farm_farrowing_problem_form_view">
<field name="model">farm.farrowing.problem</field>
<field name="type">form</field>
<field name="name">farm_farrowing_problem_form</field>
</record>
<record model="ir.ui.view" id="farm_farrowing_problem_list_view">
<field name="model">farm.farrowing.problem</field>
<field name="type">tree</field>
<field name="name">farm_farrowing_problem_list</field>
</record>
<record model="ir.action.act_window" id="act_farm_farrowing_problem">
<field name="name">Farrowing Problems</field>
<field name="res_model">farm.farrowing.problem</field>
<field name="search_value"></field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_farrowing_problem_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_farrowing_problem_list_view"/>
<field name="act_window" ref="act_farm_farrowing_problem"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_farrowing_problem_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_farrowing_problem_form_view"/>
<field name="act_window" ref="act_farm_farrowing_problem"/>
</record>
<!-- farm.farrowing.event -->
<record model="ir.ui.view"
id="farm_farrowing_event_form_view">
<field name="model">farm.farrowing.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_farrowing_event_form</field>
</record>
<record model="ir.ui.view"
id="farm_farrowing_event_list_view">
<field name="model">farm.farrowing.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_farrowing_event_list</field>
</record>
<record model="ir.model.button"
id="validate_farrowing_event_button">
<field name="name">validate_event</field>
<field name="model"
search="[('model', '=', 'farm.farrowing.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_farrowing_event_button_group_farm_admin">
<field name="button" ref="validate_farrowing_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_farrowing_event_button_group_farm_females">
<field name="button" ref="validate_farrowing_event_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.action.act_window" id="act_farm_farrowing_event">
<field name="name">Farrowing Events</field>
<field name="res_model">farm.farrowing.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context">{'animal_type': 'female'}</field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_farrowing_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_farrowing_event_list_view"/>
<field name="act_window" ref="act_farm_farrowing_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_farrowing_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_farrowing_event_form_view"/>
<field name="act_window" ref="act_farm_farrowing_event"/>
</record>
<!-- Menus -->
<menuitem action="act_farm_farrowing_problem"
id="menu_farm_farrowing_problem"
parent="menu_configuration" sequence="12"/>
</data>
</tryton>

View File

@ -0,0 +1,228 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import fields, ModelView, Workflow
from trytond.pyson import Bool, Equal, Eval, Id, If, Not, Or
from trytond.pool import Pool
from trytond.transaction import Transaction
from .abstract_event import AbstractEvent, _STATES_WRITE_DRAFT, \
_DEPENDS_WRITE_DRAFT, _STATES_VALIDATED_ADMIN, _DEPENDS_VALIDATED_ADMIN
__all__ = ['FeedAbstractEvent']
# It mustn't to be *registered* because of 'ir.model' nor 'ir.model.field' was
# created (no views related). It is implemented by Feed and Medication Event
class FeedAbstractEvent(AbstractEvent):
'Feed Abstract Event'
__name__ = 'farm.feed.abstract.event'
_table = False
location = fields.Many2One('stock.location', 'Location', required=True,
domain=[
('type', '=', 'storage'),
('warehouse', '=', Eval('farm')),
],
states={
'readonly': Or(
Not(Bool(Eval('farm', 0))),
Not(Equal(Eval('state'), 'draft')),
),
}, depends=['farm', 'state'],
context={'restrict_by_specie_animal_type': True})
feed_location = fields.Many2One('stock.location', 'Feed Source',
required=True, domain=[
('type', '=', 'storage'),
('warehouse', '=', Eval('farm')),
],
states={
'readonly': Or(
Not(Bool(Eval('farm', 0))),
Not(Equal(Eval('state'), 'draft')),
),
}, depends=['farm', 'state'])
feed_product = fields.Many2One('product.product', 'Feed', required=True,
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT)
feed_lot = fields.Many2One('stock.lot', 'Feed Lot', domain=[
('product', '=', Eval('feed_product')),
], states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT + ['feed_product'])
uom = fields.Many2One('product.uom', "UOM",
domain=[('category', '=', Id('product', 'uom_cat_weight'))],
on_change_with=['feed_product'], states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT + ['feed_product'])
unit_digits = fields.Function(fields.Integer('Unit Digits',
on_change_with=['uom']),
'on_change_with_unit_digits')
quantity = fields.Numeric('Quantity', digits=(16, Eval('unit_digits', 2)),
states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT + ['unit_digits'])
# TODO: start/end_date required?
start_date = fields.Date('Start Date', states=_STATES_WRITE_DRAFT,
help='Start date of the period in which the given quantity of product '
'was consumed.')
end_date = fields.Date('End Date', states=_STATES_WRITE_DRAFT,
help='Start date of the period in which the given quantity of product '
'was consumed.')
move = fields.Many2One('stock.move', 'Stock Move', readonly=True,
states=_STATES_VALIDATED_ADMIN, depends=_DEPENDS_VALIDATED_ADMIN)
@classmethod
def __setup__(cls):
super(FeedAbstractEvent, cls).__setup__()
# TODO: location domain must to take date from event to allow feed
# events (from inventories) of moved animals
#cls.animal.domain += [
# If(Equal(Eval('state'), 'draft'),
# If(Bool(Eval('location', 0)),
# ('location', '=', Eval('location')),
# ('location.type', '=', 'storage')),
# ()),
# ]
if 'state' not in cls.animal.depends:
cls.animal.depends.append('state')
if 'location' not in cls.animal.depends:
cls.animal.depends.append('location')
cls._sql_constraints += [
('check_start_date_end_date',
'CHECK(end_date >= start_date)',
'The End Date must be after the Start Date'),
]
cls._error_messages.update({
'animal_not_in_location': ('The feed event of animal '
'"%(animal)s" is trying to feed it in location '
'"%(location)s" but it isn\'t there at '
'"%(timestamp)s".'),
'group_not_in_location': ('The feed event of group '
'"%(group)s" is trying to move animals from location '
'"%(location)s" but there isn\'t any there at '
'"%(timestamp)s".'),
'not_enought_feed_lot': ('The feed event "%(event)s" is '
'trying to move %(quantity)s of lot "%(lot)s" from silo '
'"%(location)s" but there isn\'t enought quantity there '
'at "%(timestamp)s".'),
'not_enought_feed_product': ('The feed event "%(event)s" is '
'trying to move %(quantity)s of product "%(product)s" '
'from silo "%(location)s" but there isn\'t enought '
'quantity there at "%(timestamp)s".'),
})
@staticmethod
def valid_animal_types():
return ['male', 'female', 'individual', 'group']
def get_rec_name(self, name):
return "%s %s to %s in %s at %s" % (self.quantity, self.uom.symbol,
self.animal and self.animal.rec_name or self.animal_group.rec_name,
self.location.rec_name, self.timestamp)
def on_change_animal(self):
res = super(FeedAbstractEvent, self).on_change_animal()
res['location'] = (self.animal and self.animal.location.id or
None)
return res
def on_change_with_uom(self, name=None):
if self.feed_product:
return self.feed_product.default_uom.id
def on_change_with_unit_digits(self, name=None):
if self.uom:
return self.uom.digits
return 2
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
"""
Create an stock move
"""
Move = Pool().get('stock.move')
Uom = Pool().get('product.uom')
todo_moves = []
for feed_event in events:
assert not feed_event.move, ('%s "%s" already has a related stock '
'move: "%s"' % (type(feed_event), feed_event.id,
feed_event.move.id))
if feed_event.animal_type != 'group':
if not feed_event.animal.check_in_location(
feed_event.location,
feed_event.timestamp):
cls.raise_user_error('animal_not_in_location', {
'animal': feed_event.animal.rec_name,
'location': feed_event.location.rec_name,
'timestamp': feed_event.timestamp,
})
else:
if not feed_event.animal_group.check_in_location(
feed_event.location, feed_event.timestamp):
cls.raise_user_error('group_not_in_location', {
'group': feed_event.animal_group.rec_name,
'location': feed_event.location.rec_name,
'timestamp': feed_event.timestamp,
})
quantity = feed_event.quantity
if feed_event.uom.id != feed_event.feed_product.default_uom.id:
# TODO: it uses compute_price() because quantity is a Decimal
# quantity in feed_product default uom. The method is not for
# this purpose but it works
quantity = Uom.compute_price(
feed_event.feed_product.default_uom, feed_event.quantity,
feed_event.uom)
with Transaction().set_context(
locations=[feed_event.feed_location.id],
stock_date_end=feed_event.timestamp.date()):
if feed_event.feed_lot:
if feed_event.feed_lot.quantity < quantity:
cls.raise_user_error('not_enought_feed_lot', {
'event': feed_event.rec_name,
'lot': feed_event.feed_lot.rec_name,
'location': feed_event.feed_location.rec_name,
'quantity': feed_event.quantity,
'timestamp': feed_event.timestamp,
})
else:
if feed_event.feed_product.quantity < quantity:
cls.raise_user_error('not_enought_feed_product', {
'event': feed_event._rec_name,
'product': feed_event.feed_product._rec_name,
'location': feed_event.feed_location._rec_name,
'quantity': feed_event.quantity,
'timestamp': feed_event.timestamp,
})
new_move = feed_event._get_event_move()
new_move.save()
todo_moves.append(new_move)
feed_event.move = new_move
feed_event.save()
Move.assign(todo_moves)
Move.do(todo_moves)
def _get_event_move(self):
pool = Pool()
Move = pool.get('stock.move')
context = Transaction().context
return Move(
product=self.feed_product.id,
uom=self.uom.id,
quantity=float(self.quantity),
from_location=self.feed_location,
to_location=self.farm.production_location,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=self.feed_lot and self.feed_lot,
origin=self)
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default['move'] = None
return super(FeedAbstractEvent, cls).copy(records, default=default)

57
events/feed_event.py Normal file
View File

@ -0,0 +1,57 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
#from trytond.model import fields
from trytond.model import fields, ModelSQL, ModelView, Workflow
from trytond.pyson import Eval
from .feed_abstract_event import FeedAbstractEvent
__all__ = ['FeedEvent']
class FeedEvent(FeedAbstractEvent, ModelSQL, ModelView, Workflow):
'Feed Event'
__name__ = 'farm.feed.event'
_table = 'farm_feed_event'
feed_inventory = fields.Many2One('farm.feed.inventory', 'Inventory',
readonly=True,
help='The inventory that generated this event automatically.')
@classmethod
def __setup__(cls):
super(FeedEvent, cls).__setup__()
cls.feed_location.domain += [
('silo', '=', True),
('locations_to_fed', 'in', [Eval('location')]),
]
cls.feed_location.depends += ['location']
cls.feed_location.on_change = ['feed_location', 'feed_product',
'feed_lot', 'uom']
cls._sql_constraints += [
('quantity_positive', 'check ( quantity != 0.0 )',
'In Feed Events, the quantity must be positive (greater or '
'equal to 1)'),
]
def on_change_feed_location(self):
if not self.feed_location or not self.feed_location.current_lot:
return {
'feed_product': None,
'feed_lot': None,
'feed_uom': None,
}
return {
'feed_product': self.feed_location.current_lot.product.id,
'feed_lot': self.feed_location.current_lot.id,
'uom': self.feed_location.current_lot.product.default_uom.id,
}
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default['feed_inventory'] = None
return super(FeedEvent, cls).copy(records, default=default)

73
events/feed_event.xml Normal file
View File

@ -0,0 +1,73 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- farm.feed.event -->
<record model="ir.ui.view" id="farm_feed_event_form_view">
<field name="model">farm.feed.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_feed_event_form</field>
</record>
<record model="ir.ui.view" id="farm_feed_event_list_view">
<field name="model">farm.feed.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_feed_event_list</field>
</record>
<record model="ir.model.button" id="validate_feed_event_button">
<field name="name">validate_event</field>
<field name="model" search="[('model', '=', 'farm.feed.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_feed_event_button_group_farm_admin">
<field name="button" ref="validate_feed_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_feed_event_button_group_farm_males">
<field name="button" ref="validate_feed_event_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="validate_feed_event_button_group_farm_females">
<field name="button" ref="validate_feed_event_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="validate_feed_event_button_group_farm_individuals">
<field name="button" ref="validate_feed_event_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="validate_feed_event_button_group_farm_groups">
<field name="button" ref="validate_feed_event_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.action.act_window" id="act_farm_feed_event">
<field name="name">Feed Events</field>
<field name="res_model">farm.feed.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context"></field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_feed_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_feed_event_list_view"/>
<field name="act_window" ref="act_farm_feed_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_feed_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_feed_event_form_view"/>
<field name="act_window" ref="act_farm_feed_event"/>
</record>
</data>
</tryton>

1099
events/feed_inventory.py Normal file

File diff suppressed because it is too large Load Diff

284
events/feed_inventory.xml Normal file
View File

@ -0,0 +1,284 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- farm.feed.inventory -->
<record model="ir.ui.view" id="farm_feed_inventory_form_view">
<field name="model">farm.feed.inventory</field>
<field name="type">form</field>
<field name="name">farm_feed_inventory_form</field>
</record>
<record model="ir.ui.view" id="farm_feed_inventory_list_view">
<field name="model">farm.feed.inventory</field>
<field name="type">tree</field>
<field name="name">farm_feed_inventory_list</field>
</record>
<record model="ir.model.button" id="confirm_feed_inventory_button">
<field name="name">confirm</field>
<field name="model"
search="[('model', '=', 'farm.feed.inventory')]"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_feed_inventory_button_group_farm_admin">
<field name="button" ref="confirm_feed_inventory_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_feed_inventory_button_group_farm_males">
<field name="button" ref="confirm_feed_inventory_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_feed_inventory_button_group_farm_females">
<field name="button" ref="confirm_feed_inventory_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_feed_inventory_button_group_farm_individuals">
<field name="button" ref="confirm_feed_inventory_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_feed_inventory_button_group_farm_groups">
<field name="button" ref="confirm_feed_inventory_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.action.act_window" id="act_farm_feed_inventory">
<field name="name">Inventory</field>
<field name="res_model">farm.feed.inventory</field>
<field name="search_value"></field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_feed_inventory_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_feed_inventory_list_view"/>
<field name="act_window" ref="act_farm_feed_inventory"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_feed_inventory_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_feed_inventory_form_view"/>
<field name="act_window" ref="act_farm_feed_inventory"/>
</record>
<record model="ir.model.access" id="access_farm_feed_inventory">
<field name="model"
search="[('model', '=', 'farm.feed.inventory')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_farm_feed_inventory_admin">
<field name="model"
search="[('model', '=', 'farm.feed.inventory')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!-- farm.feed.provisional_inventory -->
<record model="ir.ui.view"
id="farm_feed_provisional_inventory_form_view">
<field name="model">farm.feed.provisional_inventory</field>
<field name="type">form</field>
<field name="name">farm_feed_provisional_inventory_form</field>
</record>
<record model="ir.ui.view"
id="farm_feed_provisional_inventory_list_view">
<field name="model">farm.feed.provisional_inventory</field>
<field name="type">tree</field>
<field name="name">farm_feed_provisional_inventory_list</field>
</record>
<record model="ir.model.button"
id="draft_feed_provisional_inventory_button">
<field name="name">draft</field>
<field name="model"
search="[('model', '=', 'farm.feed.provisional_inventory')]"/>
</record>
<record model="ir.model.button-res.group"
id="draft_feed_provisional_inventory_button_group_farm_admin">
<field name="button"
ref="draft_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="draft_feed_provisional_inventory_button_group_farm_males">
<field name="button"
ref="draft_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="draft_feed_provisional_inventory_button_group_farm_females">
<field name="button"
ref="draft_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="draft_feed_provisional_inventory_button_group_farm_individuals">
<field name="button"
ref="draft_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="draft_feed_provisional_inventory_button_group_farm_groups">
<field name="button"
ref="draft_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.model.button"
id="confirm_feed_provisional_inventory_button">
<field name="name">confirm</field>
<field name="model"
search="[('model', '=', 'farm.feed.provisional_inventory')]"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_feed_provisional_inventory_button_group_farm_admin">
<field name="button"
ref="confirm_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_feed_provisional_inventory_button_group_farm_males">
<field name="button"
ref="confirm_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_feed_provisional_inventory_button_group_farm_females">
<field name="button"
ref="confirm_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_feed_provisional_inventory_button_group_farm_individuals">
<field name="button"
ref="confirm_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="confirm_feed_provisional_inventory_button_group_farm_groups">
<field name="button"
ref="confirm_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.model.button"
id="cancel_feed_provisional_inventory_button">
<field name="name">cancel</field>
<field name="model"
search="[('model', '=', 'farm.feed.provisional_inventory')]"/>
</record>
<record model="ir.model.button-res.group"
id="cancel_feed_provisional_inventory_button_group_farm_admin">
<field name="button"
ref="cancel_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="cancel_feed_provisional_inventory_button_group_farm_males">
<field name="button"
ref="cancel_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="cancel_feed_provisional_inventory_button_group_farm_females">
<field name="button"
ref="cancel_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="cancel_feed_provisional_inventory_button_group_farm_individuals">
<field name="button"
ref="cancel_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="cancel_feed_provisional_inventory_button_group_farm_groups">
<field name="button"
ref="cancel_feed_provisional_inventory_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.action.act_window"
id="act_farm_feed_provisional_inventory">
<field name="name">Provisional Inventory</field>
<field name="res_model">farm.feed.provisional_inventory</field>
<field name="search_value"></field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_feed_provisional_inventory_view1">
<field name="sequence" eval="10"/>
<field name="view"
ref="farm_feed_provisional_inventory_list_view"/>
<field name="act_window"
ref="act_farm_feed_provisional_inventory"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_feed_provisional_inventory_view2">
<field name="sequence" eval="20"/>
<field name="view"
ref="farm_feed_provisional_inventory_form_view"/>
<field name="act_window"
ref="act_farm_feed_provisional_inventory"/>
</record>
<record model="ir.model.access"
id="access_farm_feed_provisional_inventory">
<field name="model"
search="[('model', '=', 'farm.feed.provisional_inventory')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access"
id="access_farm_feed_provisional_inventory_admin">
<field name="model"
search="[('model', '=', 'farm.feed.provisional_inventory')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!-- farm.feed.inventory.line -->
<record model="ir.ui.view" id="farm_feed_inventory_line_form_view">
<field name="model">farm.feed.inventory.line</field>
<field name="type">form</field>
<field name="name">farm_feed_inventory_line_form</field>
</record>
<record model="ir.ui.view" id="farm_feed_inventory_line_list_view">
<field name="model">farm.feed.inventory.line</field>
<field name="type">tree</field>
<field name="name">farm_feed_inventory_line_list</field>
</record>
<record model="ir.model.access" id="access_farm_feed_inventory_line">
<field name="model"
search="[('model', '=', 'farm.feed.inventory.line')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access"
id="access_farm_feed_inventory_line_admin">
<field name="model"
search="[('model', '=', 'farm.feed.inventory.line')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
</data>
</tryton>

219
events/foster_event.py Normal file
View File

@ -0,0 +1,219 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import fields, ModelView, Workflow
from trytond.pyson import And, Bool, Equal, Eval, If
from trytond.pool import Pool
from trytond.transaction import Transaction
from .abstract_event import AbstractEvent, _STATES_WRITE_DRAFT, \
_DEPENDS_WRITE_DRAFT, _STATES_VALIDATED, _DEPENDS_VALIDATED, \
_STATES_VALIDATED_ADMIN, _DEPENDS_VALIDATED_ADMIN
__all__ = ['FosterEvent']
class FosterEvent(AbstractEvent):
'''Farm Foster Event'''
__name__ = 'farm.foster.event'
_table = 'farm_foster_event'
farrowing_group = fields.Function(fields.Many2One('farm.animal.group',
'Farrowing Group'),
'get_farrowing_group')
quantity = fields.Integer('Fosters',
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT,
help='If this quantity is negative it is a Foster Out.')
pair_female = fields.Many2One('farm.animal', 'Pair Female', domain=[
('specie', '=', Eval('specie')),
('type', '=', 'female'),
('farm', '=', Eval('farm')),
('id', '!=', Eval('animal')),
('current_cycle', '!=', False),
If(Equal(Eval('state'), 'draft'),
('current_cycle.state', '=', 'lactating'),
()),
], states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT + ['specie', 'farm', 'animal'])
pair_event = fields.Many2One('farm.foster.event', 'Pair Foster Event',
readonly=True, domain=[
('animal', '=', Eval('pair_female')),
('id', '!=', Eval('id')),
],
states={
'required': And(Equal(Eval('state'), 'validated'),
Bool(Eval('pair_female', 0))),
}, depends=['pair_female', 'id', 'state'])
female_cycle = fields.Many2One('farm.animal.female_cycle', 'Female Cycle',
readonly=True, domain=[
('animal', '=', Eval('animal')),
],
states=_STATES_VALIDATED, depends=_DEPENDS_VALIDATED + ['animal'])
move = fields.Many2One('stock.move', 'Stock Move', readonly=True,
states=_STATES_VALIDATED_ADMIN, depends=_DEPENDS_VALIDATED_ADMIN)
@classmethod
def __setup__(cls):
super(FosterEvent, cls).__setup__()
cls.animal.domain += [
('farm', '=', Eval('farm')),
('type', '=', 'female'),
('current_cycle', '!=', False),
If(Equal(Eval('state'), 'draft'),
('current_cycle.state', '=', 'lactating'),
()),
]
if 'farm' not in cls.animal.depends:
cls.animal.depends.append('farm')
cls._error_messages.update({
'farrowing_group_not_in_location': ('The farrowing group of '
'foster event "%(event)s" doesn\'t have %(quantity)s '
'units in location "%(location)s" at "%(timestamp)s".'),
'pair_farrowing_group_not_in_location': ('The farrowing group '
'of the pair female of foster event "%(event)s" doesn\'t '
'have %(quantity)s units in location "%(location)s" at '
'"%(timestamp)s".'),
})
@staticmethod
def default_animal_type():
return 'female'
@staticmethod
def valid_animal_types():
return ['female']
def get_rec_name(self, name):
cycle = (self.female_cycle and self.female_cycle.sequence or
self.animal.current_cycle and self.animal.current_cycle.sequence
or None)
if cycle:
return "%s on cycle %s %s" % (self.animal.rec_name, cycle,
self.timestamp)
return super(FosterEvent, self).get_rec_name(name)
def get_farrowing_group(self, name):
'''
Return the farm.animal.group produced on Farrowing Event of this event
cycle
'''
farrowing_event = (self.female_cycle and
self.female_cycle.farrowing_event or
self.animal.current_cycle.farrowing_event)
if (farrowing_event and farrowing_event.state == 'validated' and
farrowing_event.produced_group):
return farrowing_event.produced_group.id
return None
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
'''
If no pair_animal_id is given, we should create, move animals from and
to specie_id.foster_location_id.
If pair_animal_id is given, create a symmetric event for that animal
(exchanging animal_id != pair_animal_id and
fostered_in != fostered_out)
'''
Move = Pool().get('stock.move')
todo_moves = []
for foster_event in events:
assert (not foster_event.move and not foster_event.pair_event), (
'Foster Event %s already has related pair event or stock move'
% foster_event.id)
farrowing_group = foster_event.farrowing_group
pair_farrowing_group = (foster_event.pair_female and
foster_event.pair_female.last_produced_group)
if (foster_event.quantity < 0 and
not farrowing_group.check_in_location(
foster_event.animal.location,
foster_event.timestamp,
- foster_event.quantity)):
cls.raise_user_error('farrowing_group_not_in_location', {
'event': foster_event.rec_name,
'location': foster_event.from_location.rec_name,
'quantity': - foster_event.quantity,
'timestamp': foster_event.timestamp,
})
elif (foster_event.quantity and pair_farrowing_group and
not pair_farrowing_group.check_in_location(
foster_event.pair_female.location,
foster_event.timestamp,
foster_event.quantity)):
cls.raise_user_error('pair_farrowing_group_not_in_location', {
'event': foster_event.rec_name,
'location': foster_event.pair_female.location.rec_name,
'quantity': - foster_event.quantity,
'timestamp': foster_event.timestamp,
})
current_cycle = foster_event.animal.current_cycle
foster_event.female_cycle = current_cycle
if foster_event.pair_female:
pair_event = foster_event._get_pair_event()
pair_event.save()
foster_event.pair_event = pair_event
todo_moves.append(pair_event.move)
new_move = foster_event._get_event_move()
new_move.save()
foster_event.move = new_move
todo_moves.append(new_move)
foster_event.save()
Move.assign(todo_moves)
Move.do(todo_moves)
def _get_pair_event(self):
pair_event, = FosterEvent.copy([self], {
'animal': self.pair_female.id,
'quantity': - self.quantity,
'pair_female': self.animal.id,
})
pair_event.pair_event = self
pair_event.female_cycle = pair_event.animal.current_cycle
pair_new_move = pair_event._get_event_move()
pair_new_move.save()
pair_event.move = pair_new_move
pair_event.state = 'validated'
return pair_event
def _get_event_move(self):
pool = Pool()
Move = pool.get('stock.move')
context = Transaction().context
if self.quantity > 0: # Foster In
from_location = self.specie.foster_location
to_location = self.animal.location
else:
from_location = self.animal.location
to_location = self.specie.foster_location
return Move(
product=self.farrowing_group.lot.product,
uom=self.farrowing_group.lot.product.default_uom,
quantity=abs(self.quantity),
from_location=from_location,
to_location=to_location,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=self.farrowing_group.lot,
origin=self)
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.update({
'pair_event': None,
'female_cycle': None,
'move': None,
})
return super(FosterEvent, cls).copy(records, default=default)

62
events/foster_event.xml Normal file
View File

@ -0,0 +1,62 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- farm.foster.event -->
<record model="ir.ui.view"
id="farm_foster_event_form_view">
<field name="model">farm.foster.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_foster_event_form</field>
</record>
<record model="ir.ui.view"
id="farm_foster_event_list_view">
<field name="model">farm.foster.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_foster_event_list</field>
</record>
<record model="ir.model.button"
id="validate_foster_event_button">
<field name="name">validate_event</field>
<field name="model"
search="[('model', '=', 'farm.foster.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_foster_event_button_group_farm_admin">
<field name="button" ref="validate_foster_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_foster_event_button_group_farm_females">
<field name="button" ref="validate_foster_event_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.action.act_window" id="act_farm_foster_event">
<field name="name">Foster Events</field>
<field name="res_model">farm.foster.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context">{'animal_type': 'female'}</field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_foster_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_foster_event_list_view"/>
<field name="act_window" ref="act_farm_foster_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_foster_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_foster_event_form_view"/>
<field name="act_window" ref="act_farm_foster_event"/>
</record>
</data>
</tryton>

View File

@ -0,0 +1,214 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import fields, ModelView, Workflow
from trytond.pyson import Bool, Equal, Eval, If
from trytond.pool import Pool
from trytond.transaction import Transaction
from .abstract_event import AbstractEvent, _STATES_WRITE_DRAFT, \
_DEPENDS_WRITE_DRAFT, _STATES_VALIDATED, _DEPENDS_VALIDATED, \
_STATES_VALIDATED_ADMIN, _DEPENDS_VALIDATED_ADMIN
__all__ = ['InseminationEvent']
class InseminationEvent(AbstractEvent):
'''Farm Insemination Event'''
__name__ = 'farm.insemination.event'
_table = 'farm_insemination_event'
dose_bom = fields.Many2One('production.bom', 'Dose', domain=[
('semen_dose', '=', True),
('specie', '=', Eval('specie')),
], states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT + ['specie'])
dose_product = fields.Function(fields.Many2One('product.product',
'Dose Product', on_change_with=['dose_bom'], depends=['dose_bom']),
'on_change_with_dose_product')
# TODO: (no fer) add flag in context to restrict lots with stock in 'farm'
# locations
dose_lot = fields.Many2One('stock.lot', 'Dose Lot', domain=[
If(Bool(Eval('dose_product')),
('product', '=', Eval('dose_product', 0)),
()),
], states=_STATES_WRITE_DRAFT,
on_change_with=['farm', 'timestamp', 'dose_product'],
depends=_DEPENDS_WRITE_DRAFT + ['dose_product'])
female_cycle = fields.Many2One('farm.animal.female_cycle', 'Female Cycle',
readonly=True, domain=[
('animal', '=', Eval('animal')),
],
states=_STATES_VALIDATED, depends=_DEPENDS_VALIDATED + ['animal'])
move = fields.Many2One('stock.move', 'Stock Move', readonly=True,
states=_STATES_VALIDATED_ADMIN,
depends=_DEPENDS_VALIDATED_ADMIN + ['dose_lot'])
@classmethod
def __setup__(cls):
super(InseminationEvent, cls).__setup__()
cls.animal.domain += [
('type', '=', 'female'),
If(Equal(Eval('state'), 'draft'),
['OR', [
('current_cycle', '=', False),
], [
('current_cycle.state', 'in', ('mated', 'unmated')),
], ],
[]),
]
cls._error_messages.update({
'dose_not_in_farm': ('There isn\'t any unit of dose '
'"%(dose)s" selected in the insemination event '
'"%(event)s" in the farm "%(farm)s" at "%(timestamp)s".'),
})
@staticmethod
def default_animal_type():
return 'female'
@staticmethod
def valid_animal_types():
return ['female']
def get_rec_name(self, name):
cycle = (self.female_cycle and self.female_cycle.sequence or
self.animal.current_cycle and self.animal.current_cycle.sequence
or None)
if cycle:
return "%s on cycle %s %s" % (self.animal.rec_name, cycle,
self.timestamp)
return super(InseminationEvent, self).get_rec_name(name)
def on_change_with_dose_product(self, name=None):
return (self.dose_bom and self.dose_bom.output_products and
self.dose_bom.output_products[0].id or None)
def on_change_with_dose_lot(self, name=None):
Lot = Pool().get('stock.lot')
if not self.dose_product:
return
with Transaction().set_context(
locations=[self.farm.storage_location.id],
stock_date_end=self.timestamp.date()):
lots = Lot.search([
('product', '=', self.dose_product.id),
('quantity', '>', 0),
])
if len(lots) == 1:
return lots[0].id
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
"""
- If the female has no open cycles start a new one.
- Allow the event only if the cycle has state in ('mated','unmated')
Creates and validates a production move with:
In move:
- What: Female Product + Lot (animal_id),
From: Current Location, To: Production Location
- What: Dose Product + (optional) Lot (dose_product_id, dose_lot_id),
From: warehouse_id.lot_stock_id.id, To: Production Location
Out move:
- What: Female Product + Lot (female_id),
From: Production Location, To: Current Location
"""
pool = Pool()
FemaleCycle = pool.get('farm.animal.female_cycle')
Move = pool.get('stock.move')
todo_moves = []
for insemination_event in events:
assert not insemination_event.move, ('Insemination Event "%s" '
'already has the related stock move: "%s".' % (
insemination_event.id, insemination_event.move.id))
if not insemination_event._check_dose_in_farm():
cls.raise_user_error('dose_not_in_farm', {
'event': insemination_event.rec_name,
'dose': (insemination_event.dose_lot and
insemination_event.dose_lot.rec_name or
insemination_event.dose_product and
insemination_event.dose_product.rec_name or
insemination_event.specie.semen_product and
insemination_event.specie.semen_product.rec_name),
'farm': insemination_event.farm.rec_name,
'timestamp': insemination_event.timestamp,
})
current_cycle = insemination_event.animal.current_cycle
# It creates a new cycle if a diagnosis event has been done
if not current_cycle or current_cycle.diagnosis_events:
current_cycle = FemaleCycle(animal=insemination_event.animal)
current_cycle.save()
insemination_event.animal.current_cycle = current_cycle
insemination_event.animal.save()
insemination_event.female_cycle = current_cycle
event_move = insemination_event._get_event_move()
event_move.save()
insemination_event.move = event_move
todo_moves.append(event_move)
insemination_event.save()
current_cycle.update_state(insemination_event)
Move.assign(todo_moves)
Move.do(todo_moves)
def _check_dose_in_farm(self):
if self.dose_lot:
with Transaction().set_context(
locations=[self.farm.storage_location.id],
stock_date_end=self.timestamp.date()):
return self.dose_lot.quantity > 0
product = self.dose_product or self.specie.semen_product
if product.consumable:
return True
with Transaction().set_context(
stock_date_end=self.timestamp.date(),
locations=[self.farm.storage_location.id]):
return product.quantity > 0
def _get_event_move(self):
pool = Pool()
Move = pool.get('stock.move')
context = Transaction().context
if self.dose_bom:
return Move(
product=self.dose_product.id,
uom=self.dose_product.default_uom.id,
quantity=1,
from_location=self.farm.storage_location.id,
to_location=self.farm.production_location.id,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=self.dose_lot and self.dose_lot.id or None,
origin=self,
)
else:
return Move(
product=self.specie.semen_product.id,
uom=self.specie.semen_product.default_uom.id,
quantity=1,
from_location=self.farm.storage_location.id,
to_location=self.farm.production_location.id,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
origin=self,
)
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.update({
'female_cycle': None,
'move': None,
})
return super(InseminationEvent, cls).copy(records, default=default)

View File

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- farm.insemination.event -->
<record model="ir.ui.view" id="farm_insemination_event_form_view">
<field name="model">farm.insemination.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_insemination_event_form</field>
</record>
<record model="ir.ui.view" id="farm_insemination_event_list_view">
<field name="model">farm.insemination.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_insemination_event_list</field>
</record>
<record model="ir.model.button"
id="validate_insemination_event_button">
<field name="name">validate_event</field>
<field name="model"
search="[('model', '=', 'farm.insemination.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_insemination_event_button_group_farm_admin">
<field name="button" ref="validate_insemination_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_insemination_event_button_group_farm_females">
<field name="button" ref="validate_insemination_event_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.action.act_window" id="act_farm_insemination_event">
<field name="name">Insemination Events</field>
<field name="res_model">farm.insemination.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context">{'animal_type': 'female'}</field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_insemination_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_insemination_event_list_view"/>
<field name="act_window" ref="act_farm_insemination_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_insemination_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_insemination_event_form_view"/>
<field name="act_window" ref="act_farm_insemination_event"/>
</record>
</data>
</tryton>

View File

@ -0,0 +1,38 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from .feed_abstract_event import FeedAbstractEvent
__all__ = ['MedicationEvent']
class MedicationEvent(FeedAbstractEvent):
'Medication Event'
__name__ = 'farm.medication.event'
_table = 'farm_medication_event'
@classmethod
def __setup__(cls):
super(MedicationEvent, cls).__setup__()
cls._error_messages.update({
'animal_not_in_location': ('The medication event of animal '
'"%(animal)s" is trying to move it from location '
'"%(location)s" but it isn\'t there at '
'"%(timestamp)s".'),
'group_not_in_location': ('The medication event of group '
'"%(group)s" is trying to move animals from location '
'"%(location)s" but there isn\'t any there at '
'"%(timestamp)s".'),
'not_enought_feed_lot': ('The medication event "%(event)s" is '
'trying to move %(quantity)s of lot "%(lot)s" from silo '
'"%(location)s" but there isn\'t enought quantity there '
'at "%(timestamp)s".'),
'not_enought_feed_product': ('The medication event '
'"%(event)s" is trying to move %(quantity)s of product '
'"%(product)s" from silo "%(location)s" but there isn\'t '
'enought quantity there at "%(timestamp)s".'),
})
cls._sql_constraints += [
('quantity_positive', 'check ( quantity != 0.0 )',
'In Medication Events, the quantity must be positive (greater '
'or equal to 1)'),
]

View File

@ -0,0 +1,74 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- farm.medication.event -->
<record model="ir.ui.view" id="farm_medication_event_form_view">
<field name="model">farm.medication.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_medication_event_form</field>
</record>
<record model="ir.ui.view" id="farm_medication_event_list_view">
<field name="model">farm.medication.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_medication_event_list</field>
</record>
<record model="ir.model.button" id="validate_medication_event_button">
<field name="name">validate_event</field>
<field name="model"
search="[('model', '=', 'farm.medication.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_medication_event_button_group_farm_admin">
<field name="button" ref="validate_medication_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_medication_event_button_group_farm_males">
<field name="button" ref="validate_medication_event_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="validate_medication_event_button_group_farm_females">
<field name="button" ref="validate_medication_event_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="validate_medication_event_button_group_farm_individuals">
<field name="button" ref="validate_medication_event_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="validate_medication_event_button_group_farm_groups">
<field name="button" ref="validate_medication_event_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.action.act_window" id="act_farm_medication_event">
<field name="name">Medication Events</field>
<field name="res_model">farm.medication.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context"></field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_medication_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_medication_event_list_view"/>
<field name="act_window" ref="act_farm_medication_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_medication_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_medication_event_form_view"/>
<field name="act_window" ref="act_farm_medication_event"/>
</record>
</data>
</tryton>

267
events/move_event.py Normal file
View File

@ -0,0 +1,267 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import fields, ModelView, Workflow
from trytond.pyson import Bool, Equal, Eval, Id, If, Not, Or
from trytond.pool import Pool
from trytond.transaction import Transaction
from .abstract_event import AbstractEvent, _STATES_WRITE_DRAFT, \
_DEPENDS_WRITE_DRAFT, _STATES_VALIDATED_ADMIN, _DEPENDS_VALIDATED_ADMIN
__all__ = ['MoveEvent']
class MoveEvent(AbstractEvent):
'''Farm Move Event'''
__name__ = 'farm.move.event'
_table = 'farm_move_event'
from_location = fields.Many2One('stock.location', 'Origin',
required=True, domain=[
('warehouse', '=', Eval('farm')),
('type', '=', 'storage'),
],
states={
'readonly': Or(
Not(Bool(Eval('farm', 0))),
Not(Equal(Eval('state'), 'draft')),
),
}, depends=['farm', 'state'],
context={'restrict_by_specie_animal_type': True})
to_location = fields.Many2One('stock.location', 'Destination',
required=True, domain=[
('type', '=', 'storage'),
('id', '!=', Eval('from_location')),
],
states={
'readonly': Or(
Not(Bool(Eval('from_location', 0))),
Not(Equal(Eval('state'), 'draft')),
),
}, depends=['from_location', 'state'],
context={'restrict_by_specie_animal_type': True})
quantity = fields.Integer('Quantity', required=True,
states={
'invisible': Not(Equal(Eval('animal_type'), 'group')),
'readonly': Not(Equal(Eval('state'), 'draft')),
},
on_change_with=['animal_type', 'animal_group', 'timestamp',
'from_location'],
depends=['animal_type', 'animal_group', 'from_location', 'state'])
# TODO: register price? ajuntar explicacio amb la resta de 'cost_price'
#cost_price = fields.Numeric('Cost Price', required=True, digits=(16, 4),
# states={,
# 'readonly': Not(Equal(Eval('state'), 'draft')),
# }, on_change_with=['animal_type', 'animal_group'],
# depends=['state', 'animal_type', 'animal_group'],
# help='Product\'s cost of Animal or Group for analytical accounting.')
uom = fields.Many2One('product.uom', "UOM",
domain=[('category', '=', Id('product', 'uom_cat_weight'))],
states={
'readonly': Not(Equal(Eval('state'), 'draft')),
'required': Bool(Eval('weight')),
}, depends=['state', 'weight'])
unit_digits = fields.Function(fields.Integer('Unit Digits',
on_change_with=['uom']),
'on_change_with_unit_digits')
weight = fields.Numeric('Weight', digits=(16, Eval('unit_digits', 2)),
states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT + ['unit_digits'])
move = fields.Many2One('stock.move', 'Stock Move', readonly=True, domain=[
('lot', '=', Eval('lot')),
],
states=_STATES_VALIDATED_ADMIN,
depends=_DEPENDS_VALIDATED_ADMIN + ['lot'])
weight_record = fields.Reference('Weight Record', selection=[
(None, ''),
('farm.animal.weight', 'Animal Weight'),
('farm.animal.group.weight', 'Group Weight'),
],
readonly=True, states={
'invisible': Not(Eval('groups', []).contains(
Id('farm', 'group_farm_admin'))),
}, depends=['state', 'weight'])
@classmethod
def __setup__(cls):
super(MoveEvent, cls).__setup__()
cls.animal.domain += [
If(Equal(Eval('state'), 'draft'),
If(Bool(Eval('from_location', 0)),
('location', '=', Eval('from_location')),
('location.type', '=', 'storage')),
('location', '=', Eval('to_location'))),
]
if 'state' not in cls.animal.depends:
cls.animal.depends.append('state')
if 'from_location' not in cls.animal.depends:
cls.animal.depends.append('from_location')
if 'to_location' not in cls.animal.depends:
cls.animal.depends.append('to_location')
if 'from_location' not in cls.animal.on_change:
cls.animal.on_change.append('from_location')
cls._error_messages.update({
'animal_not_in_location': ('The move event of animal '
'"%(animal)s" is trying to move it from location '
'"%(from_location)s" but it isn\'t there at '
'"%(timestamp)s".'),
'group_not_in_location': ('The move event of group '
'"%(group)s" is trying to move %(quantity)s animals '
'from location "%(from_location)s" but there isn\'t '
'enough there at "%(timestamp)s".'),
})
cls._sql_constraints += [
('quantity_positive', 'check ( quantity != 0 )',
'In Move Events, the quantity can\'t be zero'),
('quantity_1_for_animals',
("check ( animal_type = 'group' or "
"(quantity = 1 or quantity = -1))"),
'In Move Events, the quantity must be 1 for Animals (not '
'Groups).'),
('weight_0_or_positive', "check ( weight >= 0.0 )",
'In Move Events, the weight can\'t be zero'),
]
@staticmethod
def default_quantity():
return 1
@staticmethod
def default_uom():
return Pool().get('ir.model.data').get_id('product', 'uom_kilogram')
@staticmethod
def default_unit_digits():
return 2
@staticmethod
def valid_animal_types():
return ['male', 'female', 'individual', 'group']
def on_change_animal(self):
res = super(MoveEvent, self).on_change_animal()
res['from_location'] = (self.animal and self.animal.location.id or
None)
return res
def on_change_with_quantity(self):
if self.animal_type != 'group':
return 1
if not self.animal_group or not self.from_location:
return None
with Transaction().set_context(
locations=[self.from_location.id],
stock_date_end=self.timestamp.date()):
return self.animal_group.lot.quantity or None
def on_change_with_unit_digits(self, name=None):
if self.uom:
return self.uom.digits
return 2
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
"""
Create an stock move and, if weight > 0.0, a farm.animal[.group].weight
"""
Move = Pool().get('stock.move')
todo_moves = []
for move_event in events:
assert not move_event.move, ('Move Event "%s" already has a '
'related stock move: "%s"' % (move_event.id,
move_event.move.id))
if move_event.animal_type != 'group':
if not move_event.animal.check_in_location(
move_event.from_location,
move_event.timestamp):
cls.raise_user_error('animal_not_in_location', {
'animal': move_event.animal.rec_name,
'from_location':
move_event.from_location.rec_name,
'timestamp': move_event.timestamp,
})
move_event.animal.check_allowed_location(
move_event.to_location, move_event.rec_name)
else:
if not move_event.animal_group.check_in_location(
move_event.from_location,
move_event.timestamp,
move_event.quantity):
cls.raise_user_error('group_not_in_location', {
'group': move_event.animal_group.rec_name,
'from_location':
move_event.from_location.rec_name,
'quantity': move_event.quantity,
'timestamp': move_event.timestamp,
})
move_event.animal_group.check_allowed_location(
move_event.to_location, move_event.rec_name)
new_move = move_event._get_event_move()
new_move.save()
todo_moves.append(new_move)
move_event.move = new_move
if move_event.weight:
assert not move_event.weight_record, ('Move Event "%s" '
'already has a related weight record: "%s"'
% (move_event.id, move_event.weight_record))
new_weight_record = move_event._get_weight_record()
new_weight_record.save()
move_event.weight_record = new_weight_record
move_event.save()
Move.assign(todo_moves)
Move.do(todo_moves)
def _get_event_move(self):
pool = Pool()
Move = pool.get('stock.move')
context = Transaction().context
lot = (self.animal_type != 'group' and self.animal.lot or
self.animal_group.lot)
return Move(
product=lot.product,
uom=lot.product.default_uom,
quantity=self.quantity,
from_location=self.from_location,
to_location=self.to_location,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=lot,
origin=self)
def _get_weight_record(self):
pool = Pool()
AnimalWeight = pool.get('farm.animal.weight')
AnimalGroupWeight = pool.get('farm.animal.group.weight')
if self.animal_type != 'group':
return AnimalWeight(
animal=self.animal.id,
timestamp=self.timestamp,
uom=self.uom,
weight=self.weight,
)
else:
return AnimalGroupWeight(
group=self.animal_group.id,
timestamp=self.timestamp,
quantity=self.quantity,
uom=self.uom,
weight=self.weight,
)
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.updat({
'move': None,
'weight_record': None,
})
return super(MoveEvent, cls).copy(records, default=default)

73
events/move_event.xml Normal file
View File

@ -0,0 +1,73 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- farm.move.event -->
<record model="ir.ui.view" id="farm_move_event_form_view">
<field name="model">farm.move.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_move_event_form</field>
</record>
<record model="ir.ui.view" id="farm_move_event_list_view">
<field name="model">farm.move.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_move_event_list</field>
</record>
<record model="ir.model.button" id="validate_move_event_button">
<field name="name">validate_event</field>
<field name="model" search="[('model', '=', 'farm.move.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_move_event_button_group_farm_admin">
<field name="button" ref="validate_move_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_move_event_button_group_farm_males">
<field name="button" ref="validate_move_event_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="validate_move_event_button_group_farm_females">
<field name="button" ref="validate_move_event_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="validate_move_event_button_group_farm_individuals">
<field name="button" ref="validate_move_event_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="validate_move_event_button_group_farm_groups">
<field name="button" ref="validate_move_event_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.action.act_window" id="act_farm_move_event">
<field name="name">Move Events</field>
<field name="res_model">farm.move.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context"></field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_move_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_move_event_list_view"/>
<field name="act_window" ref="act_farm_move_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_move_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_move_event_form_view"/>
<field name="act_window" ref="act_farm_move_event"/>
</record>
</data>
</tryton>

View File

@ -0,0 +1,83 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import fields, ModelView, Workflow
from trytond.pyson import Equal, Eval, If
from .abstract_event import AbstractEvent, _STATES_WRITE_DRAFT, \
_DEPENDS_WRITE_DRAFT, _STATES_VALIDATED, _DEPENDS_VALIDATED
__all__ = ['PregnancyDiagnosisEvent']
class PregnancyDiagnosisEvent(AbstractEvent):
'''Farm Pregnancy Diagnosis Event'''
__name__ = 'farm.pregnancy_diagnosis.event'
_table = 'farm__ency_diagnosis_event'
result = fields.Selection([
('negative', 'Negative'),
('positive', 'Positive'),
('nonconclusive', 'Non conclusive'),
('not-pregnant', 'Observed not Pregnant'),
], 'Result', required=True,
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT)
female_cycle = fields.Many2One('farm.animal.female_cycle', 'Female Cycle',
readonly=True, domain=[('animal', '=', Eval('animal'))],
states=_STATES_VALIDATED, depends=_DEPENDS_VALIDATED + ['animal'])
@classmethod
def __setup__(cls):
super(PregnancyDiagnosisEvent, cls).__setup__()
cls.animal.domain += [
('type', '=', 'female'),
('current_cycle', '!=', False),
If(Equal(Eval('state'), 'draft'),
('current_cycle.state', 'in', ('mated', 'pregnant')),
()),
]
@staticmethod
def default_animal_type():
return 'female'
@staticmethod
def default_result():
return 'positive'
@staticmethod
def valid_animal_types():
return ['female']
def get_rec_name(self, name):
cycle = (self.female_cycle and self.female_cycle.sequence or
self.animal.current_cycle and self.animal.current_cycle.sequence
or None)
if cycle:
return "%s on cycle %s %s" % (self.animal.rec_name, cycle,
self.timestamp)
return super(PregnancyDiagnosisEvent, self).get_rec_name(name)
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
"""
Set the 'female_cycle' of events and update the 'state' FemaleCycle and
Female.
"""
for diagnosis_event in events:
diagnosis_event.female_cycle = diagnosis_event.animal.current_cycle
diagnosis_event.save()
diagnosis_event.female_cycle.update_state(diagnosis_event)
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.update({
'female_cycle': None,
})
return super(PregnancyDiagnosisEvent, cls).copy(records,
default=default)

View File

@ -0,0 +1,65 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- farm.pregnancy_diagnosis.event -->
<record model="ir.ui.view"
id="farm_pregnancy_diagnosis_event_form_view">
<field name="model">farm.pregnancy_diagnosis.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_pregnancy_diagnosis_event_form</field>
</record>
<record model="ir.ui.view"
id="farm_pregnancy_diagnosis_event_list_view">
<field name="model">farm.pregnancy_diagnosis.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_pregnancy_diagnosis_event_list</field>
</record>
<record model="ir.model.button"
id="validate_pregnancy_diagnosis_event_button">
<field name="name">validate_event</field>
<field name="model"
search="[('model', '=', 'farm.pregnancy_diagnosis.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_pregnancy_diagnosis_event_button_group_farm_admin">
<field name="button"
ref="validate_pregnancy_diagnosis_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_pregnancy_diagnosis_event_button_group_farm_females">
<field name="button"
ref="validate_pregnancy_diagnosis_event_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.action.act_window"
id="act_farm_pregnancy_diagnosis_event">
<field name="name">Pregnancy Diagnosis Events</field>
<field name="res_model">farm.pregnancy_diagnosis.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context">{'animal_type': 'female'}</field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_pregnancy_diagnosis_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_pregnancy_diagnosis_event_list_view"/>
<field name="act_window" ref="act_farm_pregnancy_diagnosis_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_pregnancy_diagnosis_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_pregnancy_diagnosis_event_form_view"/>
<field name="act_window" ref="act_farm_pregnancy_diagnosis_event"/>
</record>
</data>
</tryton>

268
events/removal_event.py Normal file
View File

@ -0,0 +1,268 @@
#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, Workflow
from trytond.pyson import Bool, Equal, Eval, If, Not, Or
from trytond.pool import Pool
from trytond.transaction import Transaction
from .abstract_event import AbstractEvent, _STATES_WRITE_DRAFT, \
_DEPENDS_WRITE_DRAFT, _STATES_VALIDATED_ADMIN, _DEPENDS_VALIDATED_ADMIN
__all__ = ['RemovalType', 'RemovalReason', 'RemovalEvent']
class RemovalType(ModelSQL, ModelView):
'''Removal Event Type'''
__name__ = 'farm.removal.type'
_order_name = 'name'
name = fields.Char('Name', required=True, translate=True)
class RemovalReason(ModelSQL, ModelView):
'''Removal Event Reason'''
__name__ = 'farm.removal.reason'
_order_name = 'name'
name = fields.Char('Name', required=True, translate=True)
class RemovalEvent(AbstractEvent):
'''Farm Removal Event'''
__name__ = 'farm.removal.event'
_table = 'farm_removal_event'
from_location = fields.Many2One('stock.location', 'Origin',
required=True, domain=[
('warehouse', '=', Eval('farm')),
('type', '=', 'storage'),
],
states={
'readonly': Or(
Not(Bool(Eval('farm', 0))),
Not(Equal(Eval('state'), 'draft')),
),
}, depends=['farm', 'state'],
context={'restrict_by_specie_animal_type': True})
quantity = fields.Integer('Quantity', required=True,
states={
'invisible': Not(Equal(Eval('animal_type'), 'group')),
'readonly': Not(Equal(Eval('state'), 'draft')),
},
on_change_with=['animal_type', 'animal_group', 'timestamp',
'from_location'],
depends=['animal_type', 'animal_group', 'timestamp', 'from_location',
'state'])
removal_type = fields.Many2One('farm.removal.type', 'Type',
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT)
reason = fields.Many2One('farm.removal.reason', 'Reason',
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT)
move = fields.Many2One('stock.move', 'Stock Move', readonly=True, domain=[
('lot', '=', Eval('lot')),
],
states=_STATES_VALIDATED_ADMIN,
depends=_DEPENDS_VALIDATED_ADMIN + ['lot'])
# # TODO: Extra
# customer = fields.Many2One('party.party', 'Customer',
# help='Company that is purchasing the animal (if any).')
# weight = fields.Float('Weight'),
@classmethod
def __setup__(cls):
super(RemovalEvent, cls).__setup__()
cls.animal.domain += [
If(Equal(Eval('state'), 'draft'),
If(Bool(Eval('from_location', 0)),
('location', '=', Eval('from_location')),
('location.type', '=', 'storage')),
('location.type', '=', 'lost_found')),
['OR',
('current_cycle', '=', None),
('current_cycle.state', '!=', 'lactating'),
],
]
if 'from_location' not in cls.animal.depends:
cls.animal.depends.append('from_location')
if 'farm' not in cls.animal.depends:
cls.animal.depends.append('farm')
if 'from_location' not in cls.animal.on_change:
cls.animal.on_change.append('from_location')
cls._error_messages.update({
'female_is_lactating': ('The female of removal event '
'"%s" is lactating. To remove it you have to create a '
'weaning event or remove its produced group.'),
'animal_not_in_location': ('The removal event of animal '
'"%(animal)s" is trying to remove it from location '
'"%(from_location)s" but it isn\'t there at '
'"%(timestamp)s".'),
'group_not_in_location': ('The removal event of group '
'"%(group)s" is trying to remove %(quantity)s animals '
'from location "%(from_location)s" but there isn\'t '
'enough there at "%(timestamp)s".'),
'already_exist_validated_removal_event': ('There are other '
'removal validated events for the animal "%s".'),
})
cls._sql_constraints += [
('quantity_positive', 'check ( quantity != 0 )',
'In Removal Events, the quantity must be positive (greater or '
'equal to 1)'),
('quantity_1_for_animals',
("check ( animal_type = 'group' or "
"(quantity = 1 or quantity = -1))"),
'In Removal Events, the quantity must be 1 for Animals (not '
'Groups).'),
]
@staticmethod
def default_quantity():
return 1
@staticmethod
def valid_animal_types():
return ['male', 'female', 'individual', 'group']
def on_change_animal(self):
res = super(RemovalEvent, self).on_change_animal()
res['from_location'] = (self.animal and self.animal.location.id or
None)
return res
def on_change_with_quantity(self):
if self.animal_type != 'group':
return 1
if not self.animal_group or not self.from_location:
return None
with Transaction().set_context(
locations=[self.from_location.id],
stock_date_end=self.timestamp.date()):
return self.animal_group.lot.quantity or None
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
'''
Create stock move:
What: production_lot_id,
From: Current Location, To: removed_location_id from configuration,
Quantity: 'quantity'
Fill in field 'move_id' with the created stock move id.
Fill in 'removal' and 'removal_date' of the animal.
'''
pool = Pool()
Location = pool.get('stock.location')
Move = pool.get('stock.move')
todo_moves = []
for removal_event in events:
assert not removal_event.move, ('Removal Event "%s" already has a '
'related stock move: "%s"' % (removal_event.id,
removal_event.move.id))
if removal_event.animal_type != 'group':
if not removal_event.animal.check_in_location(
removal_event.from_location,
removal_event.timestamp):
cls.raise_user_error('animal_not_in_location', {
'animal': removal_event.animal.rec_name,
'from_location':
removal_event.from_location.rec_name,
'timestamp': removal_event.timestamp,
})
if (removal_event.animal_type == 'female' and
removal_event.animal.is_lactating()):
cls.raise_user_error('female_is_lactating',
removal_event.rec_name)
else:
if not removal_event.animal_group.check_in_location(
removal_event.from_location,
removal_event.timestamp,
removal_event.quantity):
cls.raise_user_error('group_not_in_location', {
'group': removal_event.animal_group.rec_name,
'from_location':
removal_event.from_location.rec_name,
'quantity': removal_event.quantity,
'timestamp': removal_event.timestamp,
})
removal_event._check_existing_validated_removal_events()
new_move = removal_event._get_event_move()
new_move.save()
todo_moves.append(new_move)
removal_event.move = new_move
removal_event.save()
Move.assign(todo_moves)
Move.do(todo_moves)
for removal_event in events:
if removal_event.animal_type != 'group':
animal = removal_event.animal
animal.removal_date = removal_event.timestamp.date()
if removal_event.reason:
animal.removal_reason = removal_event.reason
# TODO: dactivated animal fails
#animal.active = False
animal.save()
else:
animal_group = removal_event.animal_group
to_remove = False
storage_locations = Location.search([
('type', '=', 'storage'),
])
with Transaction().set_context(
locations=[l.id for l in storage_locations],
stock_date_end=removal_event.timestamp.date()):
if animal_group.lot.quantity == 0:
to_remove = True
if to_remove:
animal_group.removal_date = removal_event.timestamp.date()
# TODO: if it deactivates the group the domain fails
#animal_group.active = False
animal_group.save()
def _check_existing_validated_removal_events(self):
assert self.animal_type != 'group' and self.animal, (
"_check_existing_validated_removal_events() must to be called for "
"Removal Events of animals")
n_validated_events = self.search([
('animal', '=', self.animal.id),
('state', '=', 'validated'),
], count=True)
if n_validated_events:
self.raise_user_error('already_exist_validated_removal_event',
(self.animal.rec_name,))
return True
def _get_event_move(self):
pool = Pool()
Move = pool.get('stock.move')
context = Transaction().context
lot = (self.animal_type != 'group' and self.animal.lot or
self.animal_group.lot)
return Move(
product=lot.product.id,
uom=lot.product.default_uom.id,
quantity=self.quantity,
from_location=self.from_location.id,
to_location=self.specie.removed_location.id,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=lot.id,
origin=self,
)
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default['move'] = None
return super(RemovalEvent, cls).copy(records, default=default)

198
events/removal_event.xml Normal file
View File

@ -0,0 +1,198 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- Removal Types -->
<record id="removal_type_death_on_farm" model="farm.removal.type">
<field name="name">Death on Farm</field>
</record>
<record id="removal_type_slaughtered" model="farm.removal.type">
<field name="name">Slaughtered</field>
</record>
<record id="removal_type_transferred" model="farm.removal.type">
<field name="name">Transferred</field>
</record>
<record id="removal_type_export" model="farm.removal.type">
<field name="name">Export</field>
</record>
<record id="removal_type_missing" model="farm.removal.type">
<field name="name">Missing</field>
</record>
<!-- Removal Reasons -->
<record id="removal_reason_other_unknown" model="farm.removal.reason">
<field name="name">Other/Unknown</field>
</record>
<record id="removal_reason_low_viability" model="farm.removal.reason">
<field name="name">Low Viability</field>
</record>
<record id="removal_reason_starvation" model="farm.removal.reason">
<field name="name">Starvation</field>
</record>
<record id="removal_reason_injury" model="farm.removal.reason">
<field name="name">Injury</field>
</record>
<record id="removal_reason_digestive_process"
model="farm.removal.reason">
<field name="name">Digestive Process</field>
</record>
<record id="removal_reason_respiratory_process"
model="farm.removal.reason">
<field name="name">Respiratory Process</field>
</record>
<!-- farm.removal.type -->
<record model="ir.ui.view" id="farm_removal_type_form_view">
<field name="model">farm.removal.type</field>
<field name="type">form</field>
<field name="name">farm_removal_type_form</field>
</record>
<record model="ir.ui.view" id="farm_removal_type_list_view">
<field name="model">farm.removal.type</field>
<field name="type">tree</field>
<field name="name">farm_removal_type_list</field>
</record>
<record model="ir.action.act_window" id="act_farm_removal_type">
<field name="name">Removal Types</field>
<field name="res_model">farm.removal.type</field>
<field name="search_value"></field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_removal_type_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_removal_type_list_view"/>
<field name="act_window" ref="act_farm_removal_type"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_removal_type_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_removal_type_form_view"/>
<field name="act_window" ref="act_farm_removal_type"/>
</record>
<!-- farm.removal.reason -->
<record model="ir.ui.view" id="farm_removal_reason_form_view">
<field name="model">farm.removal.reason</field>
<field name="type">form</field>
<field name="name">farm_removal_reason_form</field>
</record>
<record model="ir.ui.view" id="farm_removal_reason_list_view">
<field name="model">farm.removal.reason</field>
<field name="type">tree</field>
<field name="name">farm_removal_reason_list</field>
</record>
<record model="ir.action.act_window" id="act_farm_removal_reason">
<field name="name">Removal Reasons</field>
<field name="res_model">farm.removal.reason</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context"></field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_removal_reason_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_removal_reason_list_view"/>
<field name="act_window" ref="act_farm_removal_reason"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_removal_reason_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_removal_reason_form_view"/>
<field name="act_window" ref="act_farm_removal_reason"/>
</record>
<!-- farm.removal.event -->
<record model="ir.ui.view" id="farm_removal_event_form_view">
<field name="model">farm.removal.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_removal_event_form</field>
</record>
<record model="ir.ui.view" id="farm_removal_event_list_view">
<field name="model">farm.removal.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_removal_event_list</field>
</record>
<record model="ir.model.button" id="validate_removal_event_button">
<field name="name">validate_event</field>
<field name="model"
search="[('model', '=', 'farm.removal.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_removal_event_button_group_farm_admin">
<field name="button" ref="validate_removal_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_removal_event_button_group_farm_males">
<field name="button" ref="validate_removal_event_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="validate_removal_event_button_group_farm_females">
<field name="button" ref="validate_removal_event_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="validate_removal_event_button_group_farm_individuals">
<field name="button" ref="validate_removal_event_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="validate_removal_event_button_group_farm_groups">
<field name="button" ref="validate_removal_event_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.action.act_window" id="act_farm_removal_event">
<field name="name">Removal Events</field>
<field name="res_model">farm.removal.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context"></field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_removal_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_removal_event_list_view"/>
<field name="act_window" ref="act_farm_removal_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_removal_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_removal_event_form_view"/>
<field name="act_window" ref="act_farm_removal_event"/>
</record>
<!-- Menus -->
<menuitem action="act_farm_removal_type"
id="menu_farm_removal_type"
parent="menu_configuration" sequence="10"/>
<menuitem action="act_farm_removal_reason"
id="menu_farm_removal_reason"
parent="menu_configuration" sequence="11"/>
</data>
</tryton>

View File

@ -0,0 +1,883 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
import math
from datetime import datetime
from itertools import groupby
from trytond.model import fields, ModelView, ModelSQL, Workflow
from trytond.pyson import Bool, Equal, Eval, Greater, Id
from trytond.pool import Pool
from trytond.transaction import Transaction
from .abstract_event import AbstractEvent, _EVENT_STATES, _STATES_WRITE_DRAFT,\
_DEPENDS_WRITE_DRAFT, _STATES_VALIDATED, _DEPENDS_VALIDATED, \
_STATES_VALIDATED_ADMIN, _DEPENDS_VALIDATED_ADMIN
__all__ = ['SemenExtractionEvent', 'SemenExtractionEventQualityTest',
'SemenExtractionDose', 'SemenExtractionDelivery']
class SemenExtractionEvent(AbstractEvent):
'''Farm Semen Extraction Event'''
__name__ = 'farm.semen_extraction.event'
_table = 'farm_semen_extraction_event'
semen_product = fields.Function(fields.Many2One('product.product',
"Semen's Product", on_change_with=['specie']),
'on_change_with_semen_product')
untreated_semen_uom = fields.Many2One('product.uom', 'Semen Extracted UOM',
domain=[('category', '=', Id('product', 'uom_cat_volume'))],
required=True,
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT)
untreated_semen_unit_digits = fields.Function(
fields.Integer('Semen Extracted Unit Digits',
on_change_with=['untreated_semen_uom']),
'on_change_with_untreated_semen_unit_digits')
untreated_semen_qty = fields.Float('Semen Extracted Qty.',
digits=(16, Eval('untreated_semen_unit_digits', 3)), required=True,
states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT + ['untreated_semen_unit_digits'],
help='The amount of untreated semen taken from male.')
test = fields.One2One('farm.semen_extraction.event-quality.test', 'event',
'test', string='Quality Test', readonly=False, domain=[
# TODO: Falla el test al cridar el super().create()
#('document', '=', 'product.product', Eval('semen_product')),
#('unit.category', '=', Id('product', 'uom_cat_volume')),
#('unit', '=', Id('product', 'uom_cubic_centimeter')),
],
states={
'required': Greater(Eval('id', 0), 0),
}, depends=['semen_product', 'id'])
formula_uom = fields.Function(fields.Many2One('product.uom',
'Formula UOM'),
'get_formula_uom')
formula_unit_digits = fields.Function(
fields.Integer('Formula Unit Digits'), 'get_formula_unit_digits')
formula_result = fields.Function(fields.Float('Formula Result',
digits=(16, Eval('formula_unit_digits', 2)),
depends=['formula_unit_digits'],
help='Value calculated by the formula of Quality Test.'),
'get_formula_result')
solvent_calculated_qty = fields.Function(
fields.Float('Calc. Semen Solvent Qty.',
digits=(16, Eval('formula_unit_digits', 2)),
depends=['formula_unit_digits'],
help='The theorical amount of solvent to add to the extracted '
'semen (in UoM of the formula), calculated from the amount '
'extracted and the value of the formula.'),
'get_semen_quantities')
semen_calculated_qty = fields.Function(
fields.Float('Calc. Semen Produced Qty.',
digits=(16, Eval('formula_unit_digits', 2)),
depends=['formula_unit_digits'],
help='The theorical amount of mixture of semen produced (in UoM '
'of the formula), calculated from the amount extracted and the '
'value of the formula.'),
'get_semen_quantities')
semen_qty = fields.Float('Semen Produced Qty.',
digits=(16, Eval('formula_unit_digits', 3)),
depends=['formula_unit_digits'],
help='The amount of mixture of semen produced (in the UoM of formula)')
semen_lot = fields.Many2One('stock.lot', 'Semen Lot', readonly=True,
domain=[
('product', '=', Eval('semen_product')),
], states=_STATES_VALIDATED,
depends=_DEPENDS_VALIDATED + ['semen_product'])
semen_move = fields.Many2One('stock.move', 'Semen Move', readonly=True,
domain=[
('lot', '=', Eval('semen_lot')),
('uom', '=', Eval('formula_uom')),
], states=_STATES_VALIDATED_ADMIN,
depends=_DEPENDS_VALIDATED_ADMIN + ['semen_lot', 'formula_uom'])
dose_location = fields.Many2One('stock.location', 'Doses Location',
domain=[
('warehouse', '=', Eval('farm')),
('type', '=', 'storage'),
], required=True, states=_STATES_WRITE_DRAFT,
on_change_with=['farm'], depends=_DEPENDS_WRITE_DRAFT + ['farm'],
help="Destination location of semen doses")
dose_bom = fields.Many2One('production.bom', 'Dose Container', domain=[
('semen_dose', '=', True),
('specie', '=', Eval('specie')),
], states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT + ['specie'])
dose_calculated_units = fields.Function(fields.Float('Calculated Doses',
digits=(16, 2), on_change_with=['specie', 'formula_uom',
'semen_qty', 'dose_bom'],
help='Calculates the number of doses based on Container (BoM) and '
'Semen Produced Qty. The quantity is expressed in the UoM of the '
'Container.\n'
'You have to save the event to see this calculated value.'),
'on_change_with_dose_calculated_units')
doses = fields.One2Many('farm.semen_extraction.dose', 'event', 'Doses',
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT)
doses_semen_qty = fields.Function(fields.Float('Dose Semen Qty.',
digits=(16, Eval('formula_unit_digits', 2)),
on_change_with=['semen_qty', 'doses'],
states={
'invisible': Equal(Eval('state'), 'validated'),
}, depends=['formula_unit_digits', 'state'],
help='Total quantity of semen in the doses of list (expressed in '
'the UoM of the formula).'),
'on_change_with_doses_semen_qty')
semen_remaining_qty = fields.Function(fields.Float('Remaining Semen',
digits=(16, Eval('formula_unit_digits', 2)),
on_change_with=['semen_qty', 'doses'],
states={
'invisible': Equal(Eval('state'), 'validated'),
}, depends=['formula_unit_digits', 'state'],
help='The remaining quantity of semen of the specified doses '
'(expressed in the UoM of the formula).'),
'on_change_with_semen_remaining_qty')
deliveries = fields.One2Many('farm.semen_extraction.delivery',
'event', 'Deliveries', states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT)
dose_remaining_units = fields.Function(fields.Integer('Remaining Doses',
on_change_with=['doses', 'deliveries'], states={
'invisible': Equal(Eval('state'), 'validated'),
}, depends=['state'],
help='The remaining quantity of Doses not assigned to any '
'Delivery.'),
'on_change_with_dose_remaining_units')
@classmethod
def __setup__(cls):
super(SemenExtractionEvent, cls).__setup__()
cls.animal.domain += [
('type', '=', 'male'),
]
cls._sql_constraints += [
('untreated_semen_qty_positive',
'CHECK(untreated_semen_qty > 0.0)',
'In Semen Extraction Events, the quantity must be positive '
'(greater or equals 1)'),
]
cls._error_messages.update({
'more_semen_in_doses_than_produced': ('The sum of semen quantity '
'in the doses of semen extraction event "%s" is greater than '
'the semen produced quantity'),
'more_doses_in_deliveries_than_produced': ('The semen extraction '
'event "%(event)s" has defined more deliveries of dose '
'"%(dose)s" than produced.'),
'dose_already_defined': ('The semen extraction event "%s" already '
'has defined doses.\nPlease empty the list before click to '
'calculate them by the system.'),
'no_doses_on_validate': ('The semen extraction event "%s" doesn\t '
'have any produced dose defined.\n'
'It can\'t be validated.'),
'no_deliveries_on_validate': ('The semen extraction event "%s" '
'doesn\t have any dose delivery defined.\n'
'It can\'t be validated.'),
'quality_test_not_succeeded': ('The Quality Test "%(test)s" of '
'semen extraction event "%(event)s" did not succeed.\n'
'Please, check its values and try to it again.'),
})
cls._buttons.update({
'calculate_doses': {
'invisible': Eval('state') != 'draft',
'readonly': Bool(Eval('doses')),
},
})
@staticmethod
def default_animal_type():
return 'male'
@staticmethod
def valid_animal_types():
return ['male']
def on_change_with_semen_product(self, name=None):
return self.specie and self.specie.semen_product.id
def on_change_with_untreated_semen_unit_digits(self, name=None):
return (self.untreated_semen_uom and self.untreated_semen_uom.digits
or 3)
def get_formula_uom(self, name):
return self.test and self.test.unit.id
def get_formula_unit_digits(self, name):
return self.test and self.test.unit_digits or 2
def get_formula_result(self, name):
return self.test and self.test.formula_result
@classmethod
def get_semen_quantities(cls, extraction_events, names):
Uom = Pool().get('product.uom')
res = {
'semen_calculated_qty': {},
'solvent_calculated_qty': {},
}
for extraction_event in extraction_events:
res['semen_calculated_qty'][extraction_event.id] = 0.0
res['solvent_calculated_qty'][extraction_event.id] = 0.0
untreated_semen_qty = Uom.compute_qty(
extraction_event.untreated_semen_uom,
extraction_event.untreated_semen_qty,
extraction_event.formula_uom)
semen_calculated_qty = (extraction_event.formula_result *
untreated_semen_qty)
res['semen_calculated_qty'][extraction_event.id] = (
semen_calculated_qty)
res['solvent_calculated_qty'][extraction_event.id] = (
semen_calculated_qty - untreated_semen_qty)
if 'semen_calculated_qty' not in names:
del res['semen_calculated_qty']
if 'solvent_calculated_qty' not in names:
del res['solvent_calculated_qty']
return res
def on_change_with_dose_calculated_units(self, name=None):
Uom = Pool().get('product.uom')
if not self.dose_bom or not self.semen_qty:
return 0.0
semen_product = self.specie.semen_product
dose_product = self.dose_bom.output_products[0]
dose_uom = self.dose_bom.outputs[0].uom
factor = self.dose_bom.compute_factor(dose_product, 1.0, dose_uom)
consumed_semen_qty = 0.0 # by 1 unit of dose
for input_ in self.dose_bom.inputs:
if input_.product == semen_product:
consumed_semen_qty = input_.compute_quantity(factor)
consumed_semen_qty = Uom.compute_qty(self.formula_uom,
consumed_semen_qty, input_.uom)
break
assert consumed_semen_qty > 0.0, ('BOM of semen extraction event "%s" '
'generetes 0.0 consumed semen qty' % self.id)
n_doses = float(self.semen_qty) / consumed_semen_qty
return n_doses
def on_change_with_doses_semen_qty(self, name=None):
return self._doses_semen_quantities()['doses_semen_qty']
def on_change_with_semen_remaining_qty(self, name=None):
return self._doses_semen_quantities()['semen_remaining_qty']
def _doses_semen_quantities(self):
res = {
'doses_semen_qty': 0.0,
'semen_remaining_qty': self.semen_qty or 0.0,
}
if self.semen_qty is None or not self.doses:
return res
for dose in self.doses:
res['doses_semen_qty'] += dose.semen_qty or 0.0
res['semen_remaining_qty'] = self.semen_qty - res['doses_semen_qty']
return res
def on_change_with_dose_location(self, name=None):
return self.farm and self.farm.storage_location.id or None
def on_change_with_dose_remaining_units(self, name=None):
if not self.doses:
return 0
n_generated_doses = 0
for dose in self.doses:
n_generated_doses += dose.quantity or 0
n_delivered_doses = 0
for delivery in self.deliveries:
n_delivered_doses += delivery.quantity or 0
return n_generated_doses - n_delivered_doses
@classmethod
def validate(cls, extraction_events):
super(SemenExtractionEvent, cls).validate(extraction_events)
for extraction_event in extraction_events:
extraction_event.check_doses_semen_quantity()
extraction_event.check_deliveries_dose_quantity()
def check_doses_semen_quantity(self):
# It doesn't check 'Validated' events because it was checked before
# validating and if it changes is because BoM definition has changed
# and it can't modify past events
if self.state == 'valid':
return
if self.semen_remaining_qty < 0.0:
self.raise_user_error('more_semen_in_doses_than_produced',
self.rec_name)
def check_deliveries_dose_quantity(self):
dose_qty = dict((d.id, d.quantity) for d in self.doses)
for delivery in self.deliveries:
dose_qty[delivery.dose.id] -= delivery.quantity
if dose_qty[delivery.dose.id] < 0:
self.raise_user_error('more_doses_in_deliveries_than_produced',
{
'event': self.rec_name,
'dose': delivery.dose.rec_name,
})
@classmethod
@ModelView.button
def calculate_doses(cls, events):
Dose = Pool().get('farm.semen_extraction.dose')
for extraction_event in events:
if extraction_event.doses:
cls.raise_user_error('dose_already_defined',
extraction_event.rec_name)
if not extraction_event.dose_bom:
continue
n_doses = math.floor(extraction_event.dose_calculated_units)
new_dose = Dose(
event=extraction_event.id,
sequence=1,
bom=extraction_event.dose_bom.id,
quantity=n_doses)
new_dose.save()
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
"""
Create and validate Production Move for semen extraction, creating a
Production Lot for semen extracted (see __doc__ of
_create_semen_production function for more details).
Create and validate Production Move for Doses creation from semen
extracted, creating a Production Lot for each dose (see __doc__ of
_create_doses_production function for more details).
Create an Stock Move associated to Picking for each dose delivery (see
__doc of _create_deliveries_moves_and_pickings function for more
details)
"""
pool = Pool()
Move = pool.get('stock.move')
Production = pool.get('production')
QualityTest = pool.get('quality.test')
todo_moves = []
todo_productions = []
for extraction_event in events:
assert (not extraction_event.semen_move and
not extraction_event.semen_lot), ('Semen move and lot must '
'to be empty to validate the Extraction Event %s'
% extraction_event.id)
if not extraction_event.doses:
cls.raise_user_error('no_doses_on_validate',
extraction_event.rec_name)
if not extraction_event.deliveries:
cls.raise_user_error('no_deliveries_on_validate',
extraction_event.rec_name)
to_aprove = (extraction_event.test.state == 'confirmed')
if extraction_event.test.state == 'draft':
QualityTest.confirmed([extraction_event.test])
to_aprove = True
if to_aprove:
QualityTest.successful([extraction_event.test])
quality_test = QualityTest(extraction_event.test)
if not quality_test.success:
cls.raise_user_error('quality_test_not_succeeded', {
'test': quality_test.rec_name,
'event': extraction_event.rec_name,
})
semen_move = extraction_event._get_semen_move()
semen_move.save()
todo_moves.append(semen_move)
extraction_event.semen_move = semen_move
extraction_event.semen_lot = semen_move.lot
extraction_event.test.document = semen_move.lot
extraction_event.test.save()
for dose in extraction_event.doses:
assert not dose.production, (
'Production must to be empty for all doses to validate '
'the Extraction Event "%s"' % extraction_event.rec_name)
dose_production = dose._get_production(semen_move.lot)
dose_production.save()
todo_productions.append(dose_production)
dose.production = dose_production
dose.save()
delivery_moves = {}
for delivery in extraction_event.deliveries:
assert not delivery.move, ('Stock Move must to be empty for '
'all deliveries to validate the Extracton Event "%s"'
% extraction_event.rec_name)
delivery_move = delivery._get_move()
delivery_move.save()
delivery_moves[delivery] = delivery_move
extraction_event._set_delivery_moves_in_shipments(
delivery_moves.values())
for delivery, delivery_move in delivery_moves.items():
delivery.move = delivery_move
delivery.save()
extraction_event.save()
extraction_event.animal.update_last_extraction(extraction_event)
Move.assign(todo_moves)
Move.do(todo_moves)
Production.wait(todo_productions)
Production.assign_try(todo_productions)
Production.run(todo_productions)
Production.done(todo_productions)
def _get_semen_move(self):
Move = Pool().get('stock.move')
context = Transaction().context
semen_lot = self._get_semen_lot()
semen_lot.save()
return Move(
product=self.specie.semen_product,
uom=self.formula_uom,
quantity=self.semen_qty,
from_location=self.farm.production_location,
to_location=self.dose_location,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=semen_lot,
unit_price=self.specie.semen_product.cost_price,
origin=self)
def _get_semen_lot(self):
pool = Pool()
FarmLine = pool.get('farm.specie.farm_line')
Lot = pool.get('stock.lot')
Sequence = pool.get('ir.sequence')
farm_line, = FarmLine.search([
('specie', '=', self.specie.id),
('farm', '=', self.farm.id),
('has_male', '=', True),
])
return Lot(
number=Sequence.get_id(farm_line.semen_lot_sequence.id),
product=self.specie.semen_product.id)
def _set_delivery_moves_in_shipments(self, delivery_moves):
Shipment = Pool().get('stock.shipment.internal')
def keyfunc(move):
return move.to_location
shipments = []
delivery_moves = sorted(delivery_moves, key=keyfunc)
for to_location, moves in groupby(delivery_moves, key=keyfunc):
existing_shipments = Shipment.search([
('state', '=', 'draft'),
('from_location', '=', self.dose_location.id),
('to_location', '=', to_location.id),
])
if existing_shipments:
shipment = existing_shipments[0]
if shipment.planned_date < self.timestamp.date():
shipment.planned_date = self.timestamp.date()
shipment.moves += list(moves)
else:
shipment = Shipment(
from_location=self.dose_location,
to_location=to_location,
planned_date=self.timestamp.date(),
moves=list(moves),
)
shipment.save()
shipments.append(shipment)
return shipments
@classmethod
def create(cls, vlist):
pool = Pool()
QualityTemplate = pool.get('quality.template')
QualityTest = pool.get('quality.test')
Specie = pool.get('farm.specie')
todo_tests = []
for values in vlist:
if values.get('test'):
continue
specie_id = values.get('specie') or cls.default_specie()
if specie_id:
specie = Specie(specie_id)
semen_prod_ref = 'product.product,%d' % specie.semen_product.id
# Configure the test in specie?
templates = QualityTemplate.search([
('document', '=', semen_prod_ref),
])
if not templates:
cls.raise_user_error('missing_quality_template_for_semen',
specie.semen_product.rec_name)
test = QualityTest(
test_date=values.get('timestamp') or datetime.today(),
template=templates[0],
document=semen_prod_ref,
)
test.save()
values['test'] = test.id
todo_tests.append(test)
if todo_tests:
QualityTest.set_template(todo_tests)
return super(SemenExtractionEvent, cls).create(vlist)
@classmethod
def copy(cls, extraction_events, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.update({
'test': None,
'semen_lot': None,
'semen_move': None,
})
return super(SemenExtractionEvent, cls).copy(extraction_events,
default=default)
@classmethod
def delete(cls, events):
QualityTest = Pool().get('quality.test')
males = []
tests = []
for event in events:
males.append(event.animal)
if event.test:
tests.append(event.test)
if tests:
QualityTest.write(tests, {
'semen_extraction': False,
})
res = super(SemenExtractionEvent, cls).delete(events)
if tests:
QualityTest.delete(tests)
for male in males:
male.update_last_extraction()
return res
class SemenExtractionEventQualityTest(ModelSQL):
"Semen Extraction Event - Quality Test"
__name__ = 'farm.semen_extraction.event-quality.test'
event = fields.Many2One('farm.semen_extraction.event',
'Semen Extraction Event', required=True, ondelete='RESTRICT')
test = fields.Many2One('quality.test', 'Quality Test', required=True,
ondelete='RESTRICT')
@classmethod
def __setup__(cls):
super(SemenExtractionEventQualityTest, cls).__setup__()
cls._sql_constraints += [
('event_unique', 'UNIQUE(event)',
'The Semen Extraction Event must be unique.'),
('test_unique', 'UNIQUE(test)',
'The Quality Test must be unique.'),
]
class SemenExtractionDose(ModelSQL, ModelView):
'Farm Semen Extraction Dose'
__name__ = 'farm.semen_extraction.dose'
_order = [
('event', 'ASC'),
('sequence', 'ASC'),
]
event = fields.Many2One('farm.semen_extraction.event', 'Event',
required=True, ondelete='CASCADE')
specie = fields.Function(fields.Many2One('farm.specie',
"Specie", on_change_with=['event']),
'on_change_with_specie')
semen_unit_digits = fields.Function(fields.Integer('Semen Unit Digits'),
'get_semen_unit_digits')
state = fields.Function(fields.Selection(_EVENT_STATES, 'Event State',
on_change_with=['event']),
'on_change_with_state')
sequence = fields.Integer('Line Num.', required=True)
bom = fields.Many2One('production.bom', 'Container', required=True,
domain=[
('semen_dose', '=', True),
('specie', '=', Eval('specie')),
], states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT + ['specie'])
quantity = fields.Integer('Quantity (Units)', required=True,
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT)
semen_qty = fields.Function(fields.Float('Semen Qty.',
digits=(16, Eval('semen_unit_digits', 2)),
on_change_with=['event', 'bom', 'quantity'], states={
'invisible': Equal(Eval('state'), 'validated'),
}, depends=['semen_unit_digits', 'state'],
help='Total quantity of semen in the dose (expressed in Formula '
'UoM).'),
'on_change_with_semen_qty')
dose_product = fields.Function(fields.Many2One('product.product',
"dose_product", on_change_with=['bom']),
'on_change_with_dose_product')
production = fields.Many2One('production', 'Dose Production',
readonly=True, states=_STATES_VALIDATED_ADMIN,
depends=_DEPENDS_VALIDATED_ADMIN)
lot = fields.Many2One('stock.lot', 'Lot', readonly=True, domain=[
('product', '=', Eval('dose_product')),
], states=_STATES_VALIDATED,
depends=_DEPENDS_VALIDATED + ['dose_product'])
@classmethod
def __setup__(cls):
super(SemenExtractionDose, cls).__setup__()
cls._error_messages.update({
'invalid_state_to_delete': ('The semen extraction dose "%s" '
'can\'t be deleted because is not in "Draft" state.'),
})
cls._sql_constraints += [
('event_sequence_uniq', 'unique (event, sequence)',
'In Semen Extraction Doses, the Line Num. must be unique in a '
'event.'),
('event_bom_uniq', 'unique (event, bom)',
'In Semen Extraction Doses, the Container must be unique in a '
'event.'),
('quantity_positive', 'check (quantity > 0)',
'In Semen Extraction Doses, the Quantity must be positive '
'(greater than 0).'),
]
# TODO: these defaults should not be necessary, but...
@staticmethod
def default_specie():
return Pool().get('farm.semen_extraction.event').default_specie()
@staticmethod
def default_state():
return Pool().get('farm.semen_extraction.event').default_state()
@staticmethod
def default_sequence():
return 0
def get_rec_name(self, name):
return "#%d (%s)" % (self.sequence,
self.lot and self.lot.rec_name or self.bom.rec_name)
def on_change_with_specie(self, name=None):
return self.event and self.event.specie.id or None
def get_semen_unit_digits(self, name):
return self.event and self.event.formula_unit_digits
def on_change_with_state(self, name=None):
return self.event and self.event.state or 'draft'
def on_change_with_semen_qty(self, name=None):
Uom = Pool().get('product.uom')
if not self.event or not self.bom or not self.quantity:
return
semen_product = self.event.specie.semen_product
dose_product = self.bom.output_products[0]
dose_uom = self.bom.outputs[0].uom
factor = self.bom.compute_factor(dose_product, self.quantity, dose_uom)
semen_qty = 0.0 # by 1 unit of dose
for input_ in self.bom.inputs:
if input_.product == semen_product:
semen_qty = input_.compute_quantity(factor)
semen_qty = Uom.compute_qty(self.event.formula_uom, semen_qty,
input_.uom)
break
return semen_qty
def on_change_with_dose_product(self, name=None):
return self.bom and self.bom.output_products[0].id
@classmethod
def validate(cls, extraction_doses):
ExtractionEvent = Pool().get('farm.semen_extraction.event')
super(SemenExtractionDose, cls).validate(extraction_doses)
events = list(set(ed.event for ed in extraction_doses))
ExtractionEvent.validate(events)
def _get_production(self, semen_lot):
Production = Pool().get('production')
context = Transaction().context
production = Production(
reference=self.rec_name,
planned_date=self.event.timestamp.date(),
effective_date=self.event.timestamp.date(),
company=context.get('company'),
warehouse=self.event.farm,
location=self.event.farm.production_location,
product=self.bom.output_products[0],
bom=self.bom,
uom=self.bom.outputs[0].uom,
quantity=self.quantity,
state='draft')
production.set_moves()
for input_move in production.inputs:
if input_move.product.id == self.specie.semen_product.id:
input_move.lot = semen_lot
input_move.from_location = self.event.dose_location
input_move.save()
dose_lot = self._get_lot()
dose_lot.save()
production.outputs[0].lot = dose_lot
production.outputs[0].to_location = self.event.dose_location
production.outputs[0].save()
self.lot = dose_lot
return production
def _get_lot(self):
pool = Pool()
FarmLine = pool.get('farm.specie.farm_line')
Lot = pool.get('stock.lot')
Sequence = pool.get('ir.sequence')
farm_line, = FarmLine.search([
('specie', '=', self.event.specie.id),
('farm', '=', self.event.farm.id),
('has_male', '=', True),
])
return Lot(
number=Sequence.get_id(farm_line.dose_lot_sequence.id),
product=self.dose_product.id)
@classmethod
def copy(cls, doses, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.update({
'production': None,
'lot': None,
})
return super(SemenExtractionDose, cls).copy(doses, default=default)
@classmethod
def delete(cls, doses):
for dose in doses:
if dose.state != 'draft':
cls.raise_user_error('invalid_state_to_delete',
dose.rec_name)
return super(SemenExtractionDose, cls).delete(doses)
class SemenExtractionDelivery(ModelSQL, ModelView):
'Farm Semen Extraction Delivery'
__name__ = 'farm.semen_extraction.delivery'
event = fields.Many2One('farm.semen_extraction.event', 'Event',
required=True, ondelete='CASCADE', readonly=True)
dose_location = fields.Function(fields.Many2One('stock.location',
"Doses Location", on_change_with=['event']),
'on_change_with_dose_location')
state = fields.Function(fields.Selection(_EVENT_STATES, 'Event State',
readonly=True, on_change_with=['event']),
'on_change_with_state')
dose = fields.Many2One('farm.semen_extraction.dose', 'Dose',
required=True, domain=[
('event', '=', Eval('event')),
],
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT + ['event'])
quantity = fields.Integer('Quantity (Units)', required=True,
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT)
to_location = fields.Many2One('stock.location', 'Destination', domain=[
('type', '=', 'storage'),
], required=True, states=_STATES_WRITE_DRAFT,
depends=_DEPENDS_WRITE_DRAFT)
dose_lot = fields.Function(fields.Many2One('stock.lot', "Doses Lot"),
'get_dose_lot')
move = fields.Many2One('stock.move', 'Stock Move', readonly=True, domain=[
('from_location', '=', Eval('dose_location')),
('to_location', '=', Eval('to_location')),
('quantity', '=', Eval('quantity')),
('lot', '=', Eval('dose_lot')),
], states=_STATES_VALIDATED_ADMIN,
depends=_DEPENDS_VALIDATED_ADMIN + ['dose_location', 'to_location',
'quantity', 'dose_lot'])
@classmethod
def __setup__(cls):
super(SemenExtractionDelivery, cls).__setup__()
cls._error_messages.update({
'more_quantity_than_produced': ('The quantity in semen '
'extraction delivery "%s" is greater than produced units '
'of its dose.'),
'invalid_state_to_delete': ('The semen extraction delivery '
'"%s" can\'t be deleted because is not in "Draft" state.'),
})
cls._sql_constraints += [
('dose_to_location_uniq', 'unique (dose, to_location)',
'In Semen Extraction Deliveries, the tuple Dose and '
'must be unique!'),
('quantity_positive', 'check (quantity > 0)',
'In Semen Extraction Deliveries, the Quantity must be '
'positive (greater than 0).'),
]
# TODO: these defaults should not be necessary, but...
@staticmethod
def default_state():
return Pool().get('farm.semen_extraction.event').default_state()
def get_name_rec(self, name):
return "%s -> %s" % (self.dose.rec_name, self.to_location.rec_name)
def on_change_with_dose_location(self, name=None):
return self.event and self.event.dose_location.id or None
def on_change_with_state(self, name=None):
return self.event and self.event.state or 'draft'
def get_dose_lot(self, name):
return self.dose.lot.id
@classmethod
def validate(cls, extraction_deliveries):
super(SemenExtractionDelivery, cls).validate(extraction_deliveries)
for delivery in extraction_deliveries:
delivery.check_dose_quantity()
def check_dose_quantity(self):
if self.quantity > self.dose.quantity:
self.raise_user_error('more_quantity_than_produced', self.rec_name)
def _get_move(self):
Move = Pool().get('stock.move')
context = Transaction().context
dose_product = self.dose_lot.product
return Move(
product=dose_product,
uom=dose_product.default_uom,
quantity=self.quantity,
from_location=self.event.dose_location,
to_location=self.to_location,
planned_date=self.event.timestamp.date(),
effective_date=self.event.timestamp.date(),
company=context.get('company'),
lot=self.dose_lot,
origin=self)
@classmethod
def copy(cls, deliveries, default=None):
if default is None:
default = {}
else:
default = default.copy()
default['move'] = None
return super(SemenExtractionDelivery, cls).copy(deliveries,
default=default)
@classmethod
def delete(cls, extraction_deliveries):
for delivery in extraction_deliveries:
if delivery.state != 'draft':
cls.raise_user_error('invalid_state_to_delete',
delivery.rec_name)
return super(SemenExtractionDelivery, cls).delete(
extraction_deliveries)

View File

@ -0,0 +1,88 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- TODO permissions -->
<!-- farm.semen_extraction.event -->
<record model="ir.ui.view" id="farm_semen_extraction_event_form_view">
<field name="model">farm.semen_extraction.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_semen_extraction_event_form</field>
</record>
<record model="ir.ui.view" id="farm_semen_extraction_event_list_view">
<field name="model">farm.semen_extraction.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_semen_extraction_event_list</field>
</record>
<record model="ir.model.button"
id="validate_semen_extraction_event_button">
<field name="name">validate_event</field>
<field name="model"
search="[('model', '=', 'farm.semen_extraction.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_semen_extraction_event_button_group_farm_admin">
<field name="button" ref="validate_semen_extraction_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_semen_extraction_event_button_group_farm_females">
<field name="button" ref="validate_semen_extraction_event_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.action.act_window"
id="act_farm_semen_extraction_event">
<field name="name">Semen Extraction Events</field>
<field name="res_model">farm.semen_extraction.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context">{'animal_type': 'male'}</field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_semen_extraction_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_semen_extraction_event_list_view"/>
<field name="act_window" ref="act_farm_semen_extraction_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_semen_extraction_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_semen_extraction_event_form_view"/>
<field name="act_window" ref="act_farm_semen_extraction_event"/>
</record>
<!-- farm.semen_extraction.dose -->
<record model="ir.ui.view" id="farm_semen_extraction_dose_form_view">
<field name="model">farm.semen_extraction.dose</field>
<field name="type">form</field>
<field name="name">farm_semen_extraction_dose_form</field>
</record>
<record model="ir.ui.view" id="farm_semen_extraction_dose_list_view">
<field name="model">farm.semen_extraction.dose</field>
<field name="type">tree</field>
<field name="name">farm_semen_extraction_dose_list</field>
</record>
<!-- farm.semen_extraction.delivery -->
<record model="ir.ui.view" id="farm_semen_extraction_delivery_form_view">
<field name="model">farm.semen_extraction.delivery</field>
<field name="type">form</field>
<field name="name">farm_semen_extraction_delivery_form</field>
</record>
<record model="ir.ui.view" id="farm_semen_extraction_delivery_list_view">
<field name="model">farm.semen_extraction.delivery</field>
<field name="type">tree</field>
<field name="name">farm_semen_extraction_delivery_list</field>
</record>
</data>
</tryton>

View File

@ -0,0 +1,364 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import fields, ModelView, Workflow
from trytond.pyson import Bool, Equal, Eval, If, Not, Or
from trytond.pool import Pool
from trytond.rpc import RPC
from trytond.transaction import Transaction
from .abstract_event import AbstractEvent, _STATES_VALIDATED_ADMIN, \
_DEPENDS_VALIDATED_ADMIN
__all__ = ['TransformationEvent']
class TransformationEvent(AbstractEvent):
'''Farm Transformation Event'''
__name__ = 'farm.transformation.event'
_table = 'farm_transformation_event'
from_location = fields.Many2One('stock.location', 'Origin',
required=True, domain=[
('warehouse', '=', Eval('farm')),
('type', '=', 'storage'),
],
states={
'readonly': Or(
Not(Bool(Eval('farm', 0))),
Not(Equal(Eval('state'), 'draft')),
),
}, depends=['farm', 'state'],
context={'restrict_by_specie_animal_type': True})
to_animal_type = fields.Selection('get_to_animal_types',
"Animal Type to Transform", selection_change_with=['animal_type'],
required=True, states={
'readonly': Or(Not(Equal(Eval('state'), 'draft')),
Bool(Eval('to_location'))),
}, depends=['animal_type', 'state'])
to_location = fields.Many2One('stock.location', 'Destination',
required=True, domain=[
('type', '=', 'storage'),
],
states={
'readonly': Or(
Not(Bool(Eval('from_location', 0))),
Not(Bool(Eval('to_animal_type', ''))),
Not(Equal(Eval('state'), 'draft')),
),
},
context={
'restrict_by_specie_animal_type': True,
'animal_type': Eval('to_animal_type'),
},
depends=['from_location', 'state', 'to_animal_type'])
quantity = fields.Integer('Quantity', required=True,
states={
'invisible': Or(Not(Equal(Eval('animal_type'), 'group')),
Not(Equal(Eval('to_animal_type'), 'group'))),
'readonly': Not(Equal(Eval('state'), 'draft')),
},
on_change_with=['animal_type', 'to_animal_type', 'animal_group',
'from_location', 'timestamp'],
depends=['animal_type', 'to_animal_type', 'state'])
to_animal = fields.Many2One('farm.animal', 'Destination Animal',
select=True, readonly=True, states={
'invisible': Equal(Eval('to_animal_type'), 'group'),
},
depends=['specie', 'animal_type', 'to_animal_type'])
to_animal_group = fields.Many2One('farm.animal.group', 'Destination Group',
domain=[
('specie', '=', Eval('specie')),
('farms', 'in', [Eval('farm')]),
],
select=True, states={
'invisible': Not(Equal(Eval('to_animal_type'), 'group')),
'readonly': Not(Equal(Eval('state'), 'draft')),
},
depends=['specie', 'farm', 'to_animal_type', 'state'],
help='Select a Destination Group if you want to add the transformed '
'animals to this group. To create a new group leave it empty.')
in_move = fields.Many2One('stock.move', 'Input Stock Move', readonly=True,
states=_STATES_VALIDATED_ADMIN, depends=_DEPENDS_VALIDATED_ADMIN)
out_move = fields.Many2One('stock.move', 'Output Stock Move',
readonly=True, states=_STATES_VALIDATED_ADMIN,
depends=_DEPENDS_VALIDATED_ADMIN)
@classmethod
def __setup__(cls):
super(TransformationEvent, cls).__setup__()
cls.animal.domain += [
If(Equal(Eval('state'), 'draft'),
If(Bool(Eval('from_location', 0)),
('location', '=', Eval('from_location')),
('location.type', '=', 'storage')),
('location.type', '=', 'production')),
]
if 'state' not in cls.animal.depends:
cls.animal.depends.append('state')
if 'from_location' not in cls.animal.depends:
cls.animal.depends.append('from_location')
if 'to_location' not in cls.animal.depends:
cls.animal.depends.append('to_location')
cls.__rpc__['get_to_animal_types'] = RPC(instantiate=0)
cls._error_messages.update({
'animal_not_in_location': ('The move event of animal '
'"%(animal)s" is trying to move it from location '
'"%(from_location)s" but it isn\'t there at '
'"%(timestamp)s".'),
'group_not_in_location': ('The move event of group '
'"%(group)s" is trying to move %(quantity)s animals '
'from location "%(from_location)s" but there isn\'t '
'enough there at "%(timestamp)s".'),
})
cls._sql_constraints += [
('quantity_positive', 'check ( quantity != 0 )',
'In Transformation Events, the quantity must be positive '
'(greater or equal to 1)'),
('quantity_1_for_animals',
("check ( animal_type = 'group' and to_animal_type = 'group' "
"or (quantity = 1 or quantity = -1) )"),
'In Transformation Events, the quantity must be 1 for Animals '
'(not Groups).'),
]
@staticmethod
def default_quantity():
return 1
@staticmethod
def valid_animal_types():
return ['male', 'female', 'individual', 'group']
def on_change_animal(self):
res = super(TransformationEvent, self).on_change_animal()
res['from_location'] = (self.animal and self.animal.location.id or
None)
return res
def on_change_with_quantity(self):
if self.animal_type != 'group' or self.to_animal_type != 'group':
return 1
if not self.animal_group or not self.from_location:
return None
with Transaction().set_context(
locations=[self.from_location.id],
stock_date_end=self.timestamp.date()):
return self.animal_group.lot.quantity
def get_to_animal_types(self):
"""
Returns the list of allowed destination types for the suplied 'type'
* group -> male, female, individual, group
* male -> individual, group
* female -> individual, group
* individual -> male, female, group
"""
if self.animal_type == 'group':
return [
('male', 'Male'),
('female', 'Female'),
('individual', 'Individual'),
('group', 'Group'),
]
elif self.animal_type in ('male', 'female'):
return [
('individual', 'Individual'),
('group', 'Group'),
]
elif self.animal_type == 'individual':
return [
('male', 'Male'),
('female', 'Female'),
('group', 'Group'),
]
return []
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
"""
Create the input and output stock moves.
"""
pool = Pool()
Location = pool.get('stock.location')
Move = pool.get('stock.move')
todo_moves = []
for transf_event in events:
assert (not transf_event.in_move and
not transf_event.out_move), ('Transformation Event '
'"%s" already has the related stock moves: IN:"%s", OUT:"%s"'
% (transf_event.id,
transf_event.in_move.id,
transf_event.out_move.id))
if transf_event.animal_type == 'group':
if not transf_event.animal_group.check_in_location(
transf_event.from_location,
transf_event.timestamp,
transf_event.quantity):
cls.raise_user_error('group_not_in_location', {
'group': transf_event.animal_group.rec_name,
'from_location':
transf_event.from_location.rec_name,
'quantity': transf_event.quantity,
'timestamp': transf_event.timestamp,
})
else:
if not transf_event.animal.check_in_location(
transf_event.from_location,
transf_event.timestamp):
cls.raise_user_error('animal_not_in_location', {
'animal': transf_event.animal.rec_name,
'from_location':
transf_event.from_location.rec_name,
'timestamp': transf_event.timestamp,
})
if transf_event.to_animal_type == 'group':
if transf_event.to_animal_group:
transf_event.to_animal_group.check_allowed_location(
transf_event.to_location, transf_event.rec_name)
else:
with Transaction().set_context(no_create_stock_move=True):
new_group = transf_event._get_to_animal_group()
new_group.save()
transf_event.to_animal_group = new_group
else:
with Transaction().set_context(no_create_stock_move=True):
new_animal = transf_event._get_to_animal()
new_animal.save()
transf_event.to_animal = new_animal
new_in_move = transf_event._get_event_input_move()
new_in_move.save()
new_out_move = transf_event._get_event_output_move()
new_out_move.save()
todo_moves += [new_in_move, new_out_move]
transf_event.in_move = new_in_move
transf_event.out_move = new_out_move
transf_event.save()
Move.assign(todo_moves)
Move.do(todo_moves)
for transf_event in events:
if transf_event.animal_type != 'group':
animal = transf_event.animal
animal.removal_date = transf_event.timestamp.date()
# TODO: if it deactivates the animal the domain fails
# TODO: set removal reason?
#animal.active = False
animal.save()
else:
animal_group = transf_event.animal_group
to_remove = False
storage_locations = Location.search([
('type', '=', 'storage'),
])
with Transaction().set_context(
locations=[l.id for l in storage_locations],
stock_date_end=transf_event.timestamp.date()):
if animal_group.lot.quantity == 0:
to_remove = True
if to_remove:
animal_group.removal_date = transf_event.timestamp.date()
# TODO: if it deactivates the group the domain fails
#animal_group.active = False
animal_group.save()
def _get_to_animal_group(self):
AnimalGroup = Pool().get('farm.animal.group')
from_animal_or_group = self.animal or self.animal_group
return AnimalGroup(
specie=self.specie,
breed=from_animal_or_group.breed,
origin=from_animal_or_group.origin,
arrival_date=from_animal_or_group.arrival_date,
initial_location=self.to_location,
initial_quantity=self.quantity,
)
def _get_to_animal(self):
Animal = Pool().get('farm.animal')
from_animal_or_group = self.animal or self.animal_group
purpose = None
if self.animal and self.to_animal_type == 'individual':
purpose = self.animal.purpose
return Animal(
type=self.to_animal_type,
specie=self.specie,
breed=from_animal_or_group.breed,
origin=from_animal_or_group.origin,
arrival_date=from_animal_or_group.arrival_date,
initial_location=self.to_location,
birthdate=(self.animal and self.animal.birthdate),
sex=(self.animal and self.animal.sex),
purpose=purpose,
)
def _get_event_input_move(self):
pool = Pool()
Move = pool.get('stock.move')
context = Transaction().context
if self.animal_type == 'group':
lot = self.animal_group.lot
else:
lot = self.animal.lot
production_location = self.farm.production_location
return Move(
product=lot.product.id,
uom=lot.product.default_uom.id,
quantity=self.quantity,
from_location=self.from_location.id,
to_location=production_location.id,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=lot.id,
origin=self,
)
def _get_event_output_move(self):
pool = Pool()
Move = pool.get('stock.move')
context = Transaction().context
if self.to_animal_type == 'group':
lot = self.to_animal_group.lot
else:
lot = self.to_animal.lot
production_location = self.farm.production_location
return Move(
product=lot.product.id,
uom=lot.product.default_uom.id,
quantity=self.quantity,
from_location=production_location.id,
to_location=self.to_location.id,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=lot.id,
unit_price=lot.product.cost_price,
origin=self,
)
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.update({
'to_animal': None,
'to_animal_group': None,
'in_move': None,
'out_move': None,
})
return super(TransformationEvent, cls).copy(records, default=default)

View File

@ -0,0 +1,76 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- farm.transformation.event -->
<record model="ir.ui.view" id="farm_transformation_event_form_view">
<field name="model">farm.transformation.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_transformation_event_form</field>
</record>
<record model="ir.ui.view" id="farm_transformation_event_list_view">
<field name="model">farm.transformation.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_transformation_event_list</field>
</record>
<record model="ir.model.button"
id="validate_transformation_event_button">
<field name="name">validate_event</field>
<field name="model"
search="[('model', '=', 'farm.transformation.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_transformation_event_button_group_farm_admin">
<field name="button" ref="validate_transformation_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_transformation_event_button_group_farm_males">
<field name="button" ref="validate_transformation_event_button"/>
<field name="group" ref="group_farm_males"/>
</record>
<record model="ir.model.button-res.group"
id="validate_transformation_event_button_group_farm_females">
<field name="button" ref="validate_transformation_event_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.model.button-res.group"
id="validate_transformation_event_button_group_farm_individuals">
<field name="button" ref="validate_transformation_event_button"/>
<field name="group" ref="group_farm_individuals"/>
</record>
<record model="ir.model.button-res.group"
id="validate_transformation_event_button_group_farm_groups">
<field name="button" ref="validate_transformation_event_button"/>
<field name="group" ref="group_farm_groups"/>
</record>
<record model="ir.action.act_window"
id="act_farm_transformation_event">
<field name="name">Transformation Events</field>
<field name="res_model">farm.transformation.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context"></field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_transformation_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_transformation_event_list_view"/>
<field name="act_window" ref="act_farm_transformation_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_transformation_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_transformation_event_form_view"/>
<field name="act_window" ref="act_farm_transformation_event"/>
</record>
</data>
</tryton>

342
events/weaning_event.py Normal file
View File

@ -0,0 +1,342 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
"""
By now, the Weaning Event will only allow weaning the group of animals
associated with the female.
No possibility of creating individuals from here. Maybe in the future we
could consider several options:
- Farrowing creates individuals and weaning is for individuals
- Farrowing creates groups and weaning is for groups (current implementation)
- Farrowing creates groups and weaning is for individuals
"""
from trytond.model import fields, ModelView, ModelSQL, Workflow
from trytond.pyson import Equal, Eval, Id, If, Not
from trytond.pool import Pool
from trytond.transaction import Transaction
from .abstract_event import AbstractEvent, _STATES_WRITE_DRAFT, \
_DEPENDS_WRITE_DRAFT, _STATES_VALIDATED, _DEPENDS_VALIDATED
__all__ = ['WeaningEvent', 'WeaningEventFemaleCycle']
class WeaningEvent(AbstractEvent):
'''Farm Weaning Event'''
__name__ = 'farm.weaning.event'
_table = 'farm_weaning_event'
farrowing_group = fields.Function(fields.Many2One('farm.animal.group',
'Farrowing Group'),
'get_farrowing_group')
quantity = fields.Integer('Quantity', required=True,
states=_STATES_WRITE_DRAFT,
on_change_with=['farrowing_group', 'animal', 'timestamp'],
depends=_DEPENDS_WRITE_DRAFT)
female_to_location = fields.Many2One('stock.location',
'Female Destination', required=True, domain=[
('type', '=', 'storage'),
], states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT,
context={'restrict_by_specie_animal_type': True})
weaned_to_location = fields.Many2One('stock.location',
'Weaned Destination', required=True, domain=[
('type', '=', 'storage'),
], states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT,
context={
'restrict_by_specie_animal_type': True,
'animal_type': 'group',
})
weaned_group = fields.Many2One('farm.animal.group', 'Weaned Group',
domain=[
('farms', 'in', [Eval('farm')]),
],
states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT + ['farm'],
help='Group in which weaned animals should be added to. If left blank '
'they will keep the same group.')
female_cycle = fields.One2One(
'farm.weaning.event-farm.animal.female_cycle', 'event', 'cycle',
string='Female Cycle', readonly=True, domain=[
('animal', '=', Eval('animal')),
],
states=_STATES_VALIDATED, depends=_DEPENDS_VALIDATED + ['animal'])
female_move = fields.Many2One('stock.move', 'Female Stock Move',
readonly=True, domain=[
('lot', '=', Eval('lot')),
],
states={
'invisible': Not(Eval('groups', []).contains(
Id('farm', 'group_farm_admin'))),
},
depends=['lot'])
lost_move = fields.Many2One('stock.move', 'Lost Stock Move',
readonly=True, domain=[
('lot.animal_group', '=', Eval('farrowing_group', 0)),
],
states={
'invisible': Not(Eval('groups', []).contains(
Id('farm', 'group_farm_admin'))),
},
depends=['farrowing_group'])
weaned_move = fields.Many2One('stock.move', 'Weaned Stock Move',
readonly=True, domain=[
('lot.animal_group', '=', Eval('farrowing_group', 0)),
],
states={
'invisible': Not(Eval('groups', []).contains(
Id('farm', 'group_farm_admin'))),
},
depends=['farrowing_group'])
transformation_event = fields.Many2One('farm.transformation.event',
'Transformation Event', readonly=True, states={
'invisible': Not(Eval('groups', []).contains(
Id('farm', 'group_farm_admin'))),
})
# TODO: Extra 'weight': fields.float('Weight'),
@classmethod
def __setup__(cls):
super(WeaningEvent, cls).__setup__()
cls.animal.domain += [
('farm', '=', Eval('farm')),
('location.type', '=', 'storage'),
('type', '=', 'female'),
('current_cycle', '!=', False),
If(Equal(Eval('state'), 'draft'),
('current_cycle.state', '=', 'lactating'),
()),
]
if 'farm' not in cls.animal.depends:
cls.animal.depends.append('farm')
# TODO: not added constraint for non negative quantity but negative
# quantities are not suported
@staticmethod
def default_animal_type():
return 'female'
@staticmethod
def valid_animal_types():
return ['female']
def get_rec_name(self, name):
cycle = (self.female_cycle and self.female_cycle.sequence or
self.animal.current_cycle and self.animal.current_cycle.sequence
or None)
if cycle:
return "%s on cycle %s %s" % (self.animal.rec_name, cycle,
self.timestamp)
return super(WeaningEvent, self).get_rec_name(name)
def get_farrowing_group(self, name):
'''
Return the farm.animal.group produced on Farrowing Event of this event
cycle
'''
farrowing_event = (self.female_cycle and
self.female_cycle.farrowing_event or
self.animal.current_cycle.farrowing_event)
if (farrowing_event and farrowing_event.state == 'validated' and
farrowing_event.produced_group):
return farrowing_event.produced_group.id
return None
def on_change_with_quantity(self):
if not self.animal or not self.farrowing_group:
return None
with Transaction().set_context(
locations=[self.animal.location.id],
stock_date_end=self.timestamp.date()):
quantity = self.farrowing_group.lot.quantity
return quantity or None
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_event(cls, events):
"""
Allow the event only if the female has an open cycle and it's in the
state of 'lactating'.
Create stock move for the female:
What: animal_id, From: animal's location, To: female_location_dest
Create a production move with:
What: animal_id.last_produced_group_id,
From: animal's location, To: production location
What: weaned_group_id.production_lot_id, Qty: 'quantity' value,
From: production location, To: weaned_location_dest
What: animal_id.last_produced_group_id, Qty: difference between
the stock of produced group and number of weaned ('quantity')
FROM: production location, To: specie_id.lost_found_location_id
"""
pool = Pool()
Move = pool.get('stock.move')
TransformationEvent = pool.get('farm.transformation.event')
todo_moves = []
todo_trans_events = []
for weaning_event in events:
assert (not weaning_event.female_move and
not weaning_event.weaned_move), ('Weaning Event %s already '
'has related stock moves when it is to validate.'
% weaning_event.id)
current_cycle = weaning_event.animal.current_cycle
weaning_event.female_cycle = current_cycle
if (weaning_event.female_to_location and
weaning_event.female_to_location !=
weaning_event.animal.location):
weaning_event.animal.check_allowed_location(
weaning_event.female_to_location, weaning_event.rec_name)
female_move = weaning_event._get_female_move()
female_move.save()
weaning_event.female_move = female_move
todo_moves.append(female_move)
farrowing_group_lot = weaning_event.farrowing_group.lot
with Transaction().set_context(
product=farrowing_group_lot.product.id,
locations=[weaning_event.animal.location.id],
stock_date_end=weaning_event.timestamp.date()):
farrowing_group_qty = farrowing_group_lot.quantity
lost_qty = farrowing_group_qty - weaning_event.quantity
if lost_qty != 0:
# put in 'animal.location' exactly 'quantity' units of
# farrowing_group
lost_move = weaning_event._get_lost_move(lost_qty)
lost_move.save()
weaning_event.lost_move = lost_move
todo_moves.append(lost_move)
if (weaning_event.quantity and weaning_event.weaned_group and
weaning_event.weaned_group !=
weaning_event.farrowing_group):
transformation_event = (
weaning_event._get_transformation_event())
transformation_event.save()
weaning_event.transformation_event = transformation_event
todo_trans_events.append(transformation_event)
elif (weaning_event.quantity and weaning_event.animal.location !=
weaning_event.weaned_to_location):
# same group but different locations
weaning_event.farrowing_group.check_allowed_location(
weaning_event.weaned_to_location, weaning_event.rec_name)
weaned_move = weaning_event._get_weaned_move()
weaned_move.save()
weaning_event.weaned_move = weaned_move
todo_moves.append(weaned_move)
weaning_event.save()
current_cycle.update_state(weaning_event)
if todo_moves:
Move.assign(todo_moves)
Move.do(todo_moves)
if todo_trans_events:
TransformationEvent.validate_event(todo_trans_events)
def _get_female_move(self):
pool = Pool()
Move = pool.get('stock.move')
context = Transaction().context
return Move(
product=self.lot.product,
uom=self.lot.product.default_uom,
quantity=1.0,
from_location=self.animal.location,
to_location=self.female_to_location,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=self.lot,
origin=self)
def _get_lost_move(self, lost_qty):
pool = Pool()
Move = pool.get('stock.move')
context = Transaction().context
if lost_qty > 0:
from_location = self.animal.location
to_location = self.specie.lost_found_location
else:
# recover lost units
from_location = self.specie.lost_found_location
to_location = self.animal.location
return Move(
product=self.farrowing_group.lot.product,
uom=self.farrowing_group.lot.product.default_uom,
quantity=abs(lost_qty),
from_location=from_location,
to_location=to_location,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=self.farrowing_group.lot,
origin=self)
def _get_weaned_move(self):
pool = Pool()
Move = pool.get('stock.move')
context = Transaction().context
return Move(
product=self.farrowing_group.lot.product,
uom=self.farrowing_group.lot.product.default_uom,
quantity=self.quantity,
from_location=self.animal.location,
to_location=self.weaned_to_location,
planned_date=self.timestamp.date(),
effective_date=self.timestamp.date(),
company=context.get('company'),
lot=self.farrowing_group.lot,
origin=self)
def _get_transformation_event(self):
TransformationEvent = Pool().get('farm.transformation.event')
return TransformationEvent(
animal_type='group',
specie=self.specie,
farm=self.farm,
timestamp=self.timestamp,
animal_group=self.farrowing_group,
from_location=self.animal.location,
to_animal_type='group',
to_location=self.weaned_to_location,
quantity=self.quantity,
to_animal_group=self.weaned_group)
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.update({
'weaned_group': None,
'female_cycle': None,
'female_move': None,
'lost_move': None,
'weaned_move': None,
'transformation_event': None,
})
return super(WeaningEvent, cls).copy(records, default=default)
class WeaningEventFemaleCycle(ModelSQL):
"Weaning Event - Female Cycle"
__name__ = 'farm.weaning.event-farm.animal.female_cycle'
event = fields.Many2One('farm.weaning.event', 'Weaning Event',
required=True, ondelete='RESTRICT')
cycle = fields.Many2One('farm.animal.female_cycle', 'Female Cycle',
required=True, ondelete='RESTRICT')
@classmethod
def __setup__(cls):
super(WeaningEventFemaleCycle, cls).__setup__()
cls._sql_constraints += [
('event_unique', 'UNIQUE(event)',
'The Weaning Event must be unique.'),
('cycle_unique', 'UNIQUE(cycle)',
'The Female Cycle must be unique.'),
]

62
events/weaning_event.xml Normal file
View File

@ -0,0 +1,62 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- farm.weaning.event -->
<record model="ir.ui.view"
id="farm_weaning_event_form_view">
<field name="model">farm.weaning.event</field>
<field name="type">form</field>
<field name="inherit" ref="farm.farm_abstract_event_form_view"/>
<field name="name">farm_weaning_event_form</field>
</record>
<record model="ir.ui.view"
id="farm_weaning_event_list_view">
<field name="model">farm.weaning.event</field>
<field name="type">tree</field>
<field name="inherit" ref="farm.farm_abstract_event_list_view"/>
<field name="name">farm_weaning_event_list</field>
</record>
<record model="ir.model.button"
id="validate_weaning_event_button">
<field name="name">validate_event</field>
<field name="model"
search="[('model', '=', 'farm.weaning.event')]"/>
</record>
<record model="ir.model.button-res.group"
id="validate_weaning_event_button_group_farm_admin">
<field name="button" ref="validate_weaning_event_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="ir.model.button-res.group"
id="validate_weaning_event_button_group_farm_females">
<field name="button" ref="validate_weaning_event_button"/>
<field name="group" ref="group_farm_females"/>
</record>
<record model="ir.action.act_window" id="act_farm_weaning_event">
<field name="name">Weaning Events</field>
<field name="res_model">farm.weaning.event</field>
<field name="search_value"></field>
<field name="domain"></field>
<field name="context">{'animal_type': 'female'}</field>
</record>
<record model="ir.action.act_window.view"
id="act_farm_weaning_event_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_weaning_event_list_view"/>
<field name="act_window" ref="act_farm_weaning_event"/>
</record>
<record model="ir.action.act_window.view"
id="act_farm_weaning_event_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_weaning_event_form_view"/>
<field name="act_window" ref="act_farm_weaning_event"/>
</record>
</data>
</tryton>

780
icons/tryton-farm.svg Normal file
View File

@ -0,0 +1,780 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/jimmac/public_html/hideout/wipicons/Tango/48x48/x-office-drawing-template.png"
sodipodi:docname="x-office-drawing-template.svg"
sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/mimetypes"
inkscape:version="0.46"
sodipodi:version="0.32"
id="svg249"
height="48.000000px"
width="48.000000px"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs3">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective121" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient6719"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
inkscape:collect="always"
id="linearGradient5060">
<stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop5062" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5064" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient6717"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
id="linearGradient5048">
<stop
style="stop-color:black;stop-opacity:0;"
offset="0"
id="stop5050" />
<stop
id="stop5056"
offset="0.5"
style="stop-color:black;stop-opacity:1;" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5052" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5048"
id="linearGradient6715"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
x1="302.85715"
y1="366.64789"
x2="302.85715"
y2="609.50507" />
<linearGradient
inkscape:collect="always"
id="linearGradient5224">
<stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop5226" />
<stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop5228" />
</linearGradient>
<linearGradient
id="linearGradient6810">
<stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop6812" />
<stop
style="stop-color:white;stop-opacity:1;"
offset="1"
id="stop6814" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient6490">
<stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop6492" />
<stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop6494" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6490"
id="linearGradient6496"
x1="40.999996"
y1="6.3749962"
x2="43.000004"
y2="62.124996"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
id="linearGradient3078">
<stop
style="stop-color:#c4a000"
offset="0"
id="stop3080" />
<stop
style="stop-color:#fce94f"
offset="1"
id="stop3082" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3078"
id="linearGradient6484"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.939941,0,0,1.036445,-5.437904,-0.137195)"
x1="42.426411"
y1="58.076275"
x2="32.350136"
y2="16.35697" />
<linearGradient
inkscape:collect="always"
id="linearGradient14019">
<stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop14021" />
<stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop14023" />
</linearGradient>
<linearGradient
id="linearGradient7648"
gradientUnits="userSpaceOnUse"
x1="21.9326"
y1="24.6274"
x2="21.9326"
y2="7.1091"
style="stroke-dasharray:none;stroke-miterlimit:4.0000000;stroke-width:1.2166667">
<stop
offset="0"
style="stop-color:#8595bc;stop-opacity:1;"
id="stop7650" />
<stop
offset="1"
style="stop-color:#041a3b;stop-opacity:1;"
id="stop7652" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4542">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4544" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop4546" />
</linearGradient>
<linearGradient
id="linearGradient15662">
<stop
id="stop15664"
offset="0.0000000"
style="stop-color:#ffffff;stop-opacity:1.0000000;" />
<stop
id="stop15666"
offset="1.0000000"
style="stop-color:#f8f8f8;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
id="linearGradient269">
<stop
id="stop270"
offset="0.0000000"
style="stop-color:#a3a3a3;stop-opacity:1.0000000;" />
<stop
id="stop271"
offset="1.0000000"
style="stop-color:#4c4c4c;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
id="linearGradient259">
<stop
id="stop260"
offset="0.0000000"
style="stop-color:#fafafa;stop-opacity:1.0000000;" />
<stop
id="stop261"
offset="1.0000000"
style="stop-color:#bbbbbb;stop-opacity:1.0000000;" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4542"
id="radialGradient14142"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.284916,0,30.08928)"
cx="24.306795"
cy="42.07798"
fx="24.306795"
fy="42.07798"
r="15.821514" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient259"
id="radialGradient14144"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(0.960493,1.041132)"
cx="33.966679"
cy="35.736916"
fx="33.966679"
fy="35.736916"
r="86.708450" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient269"
id="radialGradient14146"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
cx="8.8244190"
cy="3.7561285"
fx="8.8244190"
fy="3.7561285"
r="37.751713" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7648"
id="linearGradient14148"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.098989,0,0,-0.797757,-1.953865,37.324)"
x1="21.9326"
y1="24.627399"
x2="21.9326"
y2="7.1090999" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient15662"
id="radialGradient14150"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
cx="8.1435566"
cy="7.2678967"
fx="8.1435566"
fy="7.2678967"
r="38.158695" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient14019"
id="linearGradient14152"
gradientUnits="userSpaceOnUse"
x1="28.125"
y1="9.5398483"
x2="29.341478"
y2="40.882267" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6810"
id="linearGradient14197"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-48.25,0)"
x1="13"
y1="24.125"
x2="48"
y2="24.125" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5224"
id="linearGradient5230"
x1="37.916054"
y1="22.307138"
x2="12.564167"
y2="22.307138"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
inkscape:window-y="53"
inkscape:window-x="385"
inkscape:window-height="795"
inkscape:window-width="872"
inkscape:document-units="px"
inkscape:grid-bbox="true"
showgrid="true"
inkscape:current-layer="layer5"
inkscape:cy="-17.678009"
inkscape:cx="4.5669788"
inkscape:zoom="1"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="0.25490196"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
inkscape:showpageshadow="false"
fill="#729fcf" />
<metadata
id="metadata4">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Office Document</dc:title>
<dc:subject>
<rdf:Bag>
<rdf:li>rich</rdf:li>
<rdf:li>text</rdf:li>
<rdf:li>document</rdf:li>
<rdf:li>pdf</rdf:li>
<rdf:li>openoffice</rdf:li>
<rdf:li>word</rdf:li>
<rdf:li>rtf</rdf:li>
</rdf:Bag>
</dc:subject>
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
<dc:creator>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:creator>
<dc:source>http://jimmac.musichall.cz</dc:source>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer6"
inkscape:label="Shadow" />
<g
style="display:inline"
inkscape:groupmode="layer"
inkscape:label="Base"
id="layer1" />
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="Text"
style="display:inline">
<g
style="display:inline"
transform="matrix(2.496272e-2,0,0,2.086758e-2,45.27209,41.55739)"
id="g6707">
<rect
style="opacity:0.40206185;color:black;fill:url(#linearGradient6715);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6709"
width="1339.6335"
height="478.35718"
x="-1559.2523"
y="-150.69685" />
<path
style="opacity:0.40206185;color:black;fill:url(#radialGradient6717);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M -219.61876,-150.68038 C -219.61876,-150.68038 -219.61876,327.65041 -219.61876,327.65041 C -76.744594,328.55086 125.78146,220.48075 125.78138,88.454235 C 125.78138,-43.572302 -33.655436,-150.68036 -219.61876,-150.68038 z "
id="path6711"
sodipodi:nodetypes="cccc" />
<path
sodipodi:nodetypes="cccc"
id="path6713"
d="M -1559.2523,-150.68038 C -1559.2523,-150.68038 -1559.2523,327.65041 -1559.2523,327.65041 C -1702.1265,328.55086 -1904.6525,220.48075 -1904.6525,88.454235 C -1904.6525,-43.572302 -1745.2157,-150.68036 -1559.2523,-150.68038 z "
style="opacity:0.40206185;color:black;fill:url(#radialGradient6719);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
</g>
<g
id="g14108"
transform="translate(-6,0)"
mask="none">
<rect
ry="1.1490486"
y="3.6464462"
x="6.6035528"
height="40.920494"
width="34.875"
id="rect15391"
style="color:black;fill:url(#radialGradient14144);fill-opacity:1;fill-rule:nonzero;stroke:url(#radialGradient14146);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:block;overflow:visible"
rx="1.1490486" />
<g
transform="translate(-4.882683,11.18582)"
style="opacity:1;display:inline"
id="g5376">
<g
id="g7654"
transform="matrix(0.608982,0,0,0.606219,12.8233,10.5572)">
<path
id="path7644"
d="M 5.512695,30 L 39.643234,30 L 39.643234,19.627375 L 5.512695,19.627375 L 5.512695,30 z "
style="fill:url(#linearGradient14148);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2166667;stroke-miterlimit:4" />
<path
style="fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2166667;stroke-miterlimit:4"
d="M 5.512695,5.6791358 L 39.643234,5.6791358 L 39.643234,19.627375 L 5.512695,19.627375 L 5.512695,5.6791358 z "
id="path16203" />
<g
id="g16205"
style="fill-rule:nonzero;stroke:black;stroke-width:2.00241709;stroke-miterlimit:4"
transform="matrix(1.189217,0,0,1.189217,-3.525355,-6.535408)">
<g
id="g16207">
<path
style="opacity:0.04999994;fill:#e8f52f;stroke:none"
d="M 18.4,15.4 C 18.4,17.6 16.6,19.5 14.3,19.5 C 12.1,19.5 10.2,17.7 10.2,15.4 C 10.2,13.2 12,11.3 14.3,11.3 C 16.5,11.3 18.4,13.1 18.4,15.4 z "
id="path16209" />
<path
style="opacity:0.20829994;fill:#ecf751;stroke:none"
d="M 18,15.4 C 18,17.4 16.4,19.1 14.3,19.1 C 12.3,19.1 10.6,17.5 10.6,15.4 C 10.6,13.4 12.2,11.7 14.3,11.7 C 16.3,11.7 18,13.3 18,15.4 L 18,15.4 z "
id="path16211" />
<path
style="opacity:0.36669994;fill:#f0f972;stroke:none"
d="M 17.6,15.4 C 17.6,17.2 16.1,18.7 14.3,18.7 C 12.5,18.7 11,17.2 11,15.4 C 11,13.6 12.5,12.1 14.3,12.1 C 16.1,12.1 17.6,13.6 17.6,15.4 L 17.6,15.4 z "
id="path16213" />
<path
style="opacity:0.525;fill:#f4fa95;stroke:none"
d="M 17.2,15.4 C 17.2,17 15.9,18.3 14.3,18.3 C 12.7,18.3 11.4,17 11.4,15.4 C 11.4,13.8 12.7,12.5 14.3,12.5 C 15.9,12.5 17.2,13.8 17.2,15.4 z "
id="path16215" />
<path
style="opacity:0.6833;fill:#f7fcb7;stroke:none"
d="M 16.8,15.4 C 16.8,16.8 15.7,17.9 14.3,17.9 C 12.9,17.9 11.8,16.8 11.8,15.4 C 11.8,14 12.9,12.9 14.3,12.9 C 15.7,12.9 16.8,14 16.8,15.4 L 16.8,15.4 z "
id="path16217" />
<path
style="opacity:0.8417;fill:#fbfddb;stroke:none"
d="M 16.4,15.4 C 16.4,16.6 15.4,17.5 14.3,17.5 C 13.2,17.5 12.2,16.5 12.2,15.4 C 12.2,14.3 13.2,13.3 14.3,13.3 C 15.4,13.3 16.4,14.3 16.4,15.4 z "
id="path16219" />
<path
style="fill:white;stroke:none"
d="M 16,15.4 C 16,16.4 15.2,17.2 14.2,17.2 C 13.2,17.2 12.4,16.4 12.4,15.4 C 12.4,14.4 13.2,13.6 14.2,13.6 C 15.2,13.6 16,14.4 16,15.4 L 16,15.4 z "
id="path16221" />
</g>
</g>
<path
style="opacity:0.3;fill-rule:nonzero;stroke:none;stroke-width:1.2166667;stroke-miterlimit:4"
d="M 25.015859,21.649044 L 33.697148,21.649044 L 35.362052,22.124732 L 32.507931,22.124732 C 32.507931,22.124732 35.362052,22.362574 36.789115,24.146401 C 38.216174,25.811305 35.12421,27.832976 35.12421,27.832976 C 35.12421,27.832976 35.12421,27.832976 35.12421,27.832976 C 35.005288,27.47621 34.291756,24.622087 32.864696,23.43287 C 31.794399,22.481496 30.605182,22.243652 30.605182,22.243652 L 25.015859,22.243652 L 25.015859,21.767966 L 25.015859,21.649044 z "
id="path16223" />
<path
style="opacity:0.3;fill-rule:nonzero;stroke:none;stroke-width:1.2166667;stroke-miterlimit:4"
d="M 30.724106,22.362574 L 25.729391,22.362574 L 35.005288,27.595131 L 30.724106,22.362574 L 30.724106,22.362574 z "
id="path16225" />
<path
style="fill:#515151;fill-rule:nonzero;stroke:none;stroke-width:1.2166667;stroke-miterlimit:4"
d="M 25.015859,21.767966 L 33.697148,21.767966 L 35.005288,20.935513 L 32.151167,20.935513 C 32.151167,20.935513 34.767443,20.459827 35.12421,17.486782 C 35.480973,14.513739 31.080869,11.183931 31.080869,11.183931 C 31.080869,11.183931 31.080869,11.183931 31.080869,11.302853 C 31.19979,12.016383 32.389007,17.011096 31.556557,18.913846 C 31.19979,20.578747 30.129495,20.935513 30.129495,20.935513 L 24.659094,20.935513 L 24.896938,21.767966 L 25.015859,21.767966 z "
id="path16227" />
<path
style="fill:#515151;fill-rule:nonzero;stroke:none;stroke-width:1.2166667;stroke-miterlimit:4"
d="M 30.248418,20.459827 L 25.253704,20.459827 L 31.19979,11.421773 L 30.248418,20.459827 z "
id="path16229" />
</g>
<rect
style="opacity:1;color:black;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#9e9e9e;stroke-width:0.99999863;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect8163"
width="19.995502"
height="13.997463"
x="16.508501"
y="14.485752" />
</g>
<rect
rx="0.14904857"
ry="0.14904857"
y="4.5839462"
x="7.6660538"
height="38.946384"
width="32.775887"
id="rect15660"
style="color:black;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#radialGradient14150);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:block;overflow:visible" />
<g
transform="translate(-1,1)"
style="opacity:0.60112359"
id="g14073">
<rect
style="opacity:1;color:black;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect14027"
width="2"
height="1.9999999"
x="15"
y="7" />
<rect
y="7"
x="37"
height="1.9999999"
width="2"
id="rect14029"
style="opacity:1;color:black;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:1;color:black;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect14031"
width="2"
height="1.9999999"
x="37"
y="29" />
<rect
y="29"
x="15"
height="1.9999999"
width="2"
id="rect14035"
style="opacity:1;color:black;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
y="18"
x="15.116116"
height="1"
width="1"
id="rect14057"
style="opacity:1;color:black;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:1;color:black;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect14059"
width="1"
height="1"
x="38"
y="18" />
<rect
style="opacity:1;color:black;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect14061"
width="1"
height="1"
x="7"
y="-28"
transform="matrix(2.35814e-18,1,-1,2.35814e-18,0,0)" />
<rect
y="-28"
x="29.883884"
height="1"
width="1"
id="rect14063"
style="opacity:1;color:black;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
transform="matrix(2.35814e-18,1,-1,2.35814e-18,0,0)" />
<path
sodipodi:type="arc"
style="opacity:1;color:black;fill:#73d216;fill-opacity:1;fill-rule:evenodd;stroke:#4e9a06;stroke-width:1.02597404;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="path14015"
sodipodi:cx="28.125"
sodipodi:cy="17.125"
sodipodi:rx="9.875"
sodipodi:ry="9.875"
d="M 38 17.125 A 9.875 9.875 0 1 1 18.25,17.125 A 9.875 9.875 0 1 1 38 17.125 z"
transform="matrix(0.974684,0,0,0.974684,-0.412975,2.183544)" />
<path
transform="matrix(0.873417,0,0,0.873417,2.43515,3.917736)"
d="M 38 17.125 A 9.875 9.875 0 1 1 18.25,17.125 A 9.875 9.875 0 1 1 38 17.125 z"
sodipodi:ry="9.875"
sodipodi:rx="9.875"
sodipodi:cy="17.125"
sodipodi:cx="28.125"
id="path14017"
style="opacity:0.28089887;color:black;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14152);stroke-width:1.14492857;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
sodipodi:type="arc" />
</g>
<rect
style="color:black;fill:url(#linearGradient5230);fill-opacity:1.0;fill-rule:nonzero;stroke:url(#radialGradient14150);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:block;overflow:visible"
id="rect4349"
width="32.775887"
height="38.946384"
x="7.6660538"
y="4.5839462"
ry="0.14904857"
rx="0.14904857" />
<rect
rx="1.1490486"
style="color:black;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:white;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0.8;stroke-opacity:1;visibility:visible;display:block;overflow:visible;stroke-dasharray:1,1"
id="rect5232"
width="34.875"
height="40.920494"
x="6.6035528"
y="3.6464462"
ry="1.1490486" />
</g>
<g
style="display:inline"
id="g6716"
transform="translate(-0.99999,-2.920628e-6)"
mask="none">
<rect
ry="0.089702792"
rx="0.081350483"
y="1.5136924"
x="38.513687"
height="43.972618"
width="8.9726143"
id="rect6482"
style="opacity:1;color:black;fill:url(#linearGradient6484);fill-opacity:1;fill-rule:evenodd;stroke:#c4a000;stroke-width:1.02738488px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.70224723;color:black;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient6496);stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6486"
width="6.9730129"
height="41.972607"
x="39.513496"
y="2.5136933" />
<rect
y="3.0554543"
x="39"
height="1"
width="2.9402711"
id="rect6498"
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6500"
width="2.9402711"
height="1"
x="39"
y="15.055454" />
<rect
y="27.055454"
x="39"
height="1"
width="2.9402711"
id="rect6502"
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6504"
width="2.9402711"
height="1"
x="39"
y="39.055496" />
<rect
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6506"
width="2"
height="1"
x="39"
y="5.0554543" />
<rect
y="7.0554543"
x="39"
height="1"
width="2"
id="rect6508"
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6510"
width="2"
height="1"
x="39"
y="9.0554543" />
<rect
y="11.055454"
x="39"
height="1"
width="2"
id="rect6512"
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6514"
width="2"
height="1"
x="39"
y="13.055454" />
<rect
y="17.055454"
x="39"
height="1"
width="2"
id="rect6516"
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6518"
width="2"
height="1"
x="39"
y="19.055454" />
<rect
y="21.055454"
x="39"
height="1"
width="2"
id="rect6520"
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6522"
width="2"
height="1"
x="39"
y="23.055454" />
<rect
y="25.055454"
x="39"
height="1"
width="2"
id="rect6524"
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6526"
width="2"
height="1"
x="39"
y="29.055454" />
<rect
y="31.055475"
x="39"
height="1"
width="2"
id="rect6528"
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6530"
width="2"
height="1"
x="39"
y="33.055496" />
<rect
y="35.055496"
x="39"
height="1"
width="2"
id="rect6532"
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6534"
width="2"
height="1"
x="39"
y="37.055496" />
<rect
y="41.055496"
x="39"
height="1"
width="2"
id="rect6536"
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.70224723;color:black;fill:#c4a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02699995;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6540"
width="2"
height="1"
x="39"
y="43.055496" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 37 KiB

1096
locale/es_ES.po Normal file

File diff suppressed because it is too large Load Diff

52
production.py Normal file
View File

@ -0,0 +1,52 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import fields
from trytond.pyson import Bool, Eval, Id, If, Not, Or
from trytond.pool import PoolMeta
__all__ = ['BOM']
__metaclass__ = PoolMeta
class BOM:
__name__ = 'production.bom'
semen_dose = fields.Boolean('Semen Dose')
specie = fields.Many2One('farm.specie', 'Dose Specie', states={
'required': Bool(Eval('semen_dose', 0)),
'invisible': Not(Bool(Eval('semen_dose', 0))),
}, depends=['semen_dose'])
@classmethod
def __setup__(cls):
super(BOM, cls).__setup__()
for field in (cls.inputs, cls.outputs):
states = field.states or {}
if 'required' in states:
states['required'] = Or(Bool(Eval('semen_dose', 0)),
states['required'])
else:
states['required'] = Bool(Eval('semen_dose', 0))
field.states = states
field.depends.append('semen_dose')
cls.outputs.size = If(Bool(Eval('semen_dose', 0)), 1,
cls.outputs.size or -1)
cls.outputs.domain.append(('uom', '=', Id('product', 'uom_unit')))
cls._error_messages.update({
'missing_semen_input': 'The Semen Dose BOM "%s" doesn\'t have '
'any input for the Specie\'s Semen Product.',
})
@classmethod
def validate(cls, boms):
super(BOM, cls).validate(boms)
for bom in boms:
bom.check_specie_semen_in_inputs()
def check_specie_semen_in_inputs(self):
if self.semen_dose:
semen_product = self.specie.semen_product
semen_input_lines = [i for i in self.inputs
if i.product == semen_product]
if not semen_input_lines:
self.raise_user_error('missing_semen_input', (self.rec_name,))

20
production.xml Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- production.bom -->
<record model="ir.ui.view" id="bom_view_list">
<field name="model">production.bom</field>
<field name="type">tree</field>
<field name="inherit" ref="production.bom_view_list"/>
<field name="name">production_bom_list</field>
</record>
<record model="ir.ui.view" id="bom_view_form">
<field name="model">production.bom</field>
<field name="type">form</field>
<field name="inherit" ref="production.bom_view_form"/>
<field name="name">production_bom_form</field>
</record>
</data>
</tryton>

36
quality.py Normal file
View File

@ -0,0 +1,36 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import fields
from trytond.pyson import Bool, Eval, Not
from trytond.pool import PoolMeta
__all__ = ['QualityTest']
__metaclass__ = PoolMeta
class QualityTest:
__name__ = 'quality.test'
semen_extraction = fields.One2One(
'farm.semen_extraction.event-quality.test', 'test', 'event',
string="Semen Extraction", readonly=True, states={
'invisible': Not(Bool(Eval('semen_extraction', 0))),
})
@classmethod
def __setup__(cls):
super(QualityTest, cls).__setup__()
cls._error_messages.update({
'no_set_draft_semen_extraction_test': ('The quality test "%s" '
'can\'t be set to Draft because it is related to a '
'validated semen extraction event.'),
})
@classmethod
def draft(cls, tests):
for test in tests:
if (test.state != 'draft' and test.semen_extraction and
test.semen_extraction.state == 'validated'):
cls.raise_user_exception('no_set_draft_semen_extraction_test',
test.rec_name)
super(QualityTest, cls).draft(tests)

13
quality.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="quality_test_farm_view_form">
<field name="model">quality.test</field>
<field name="inherit"
ref="quality_control.quality_test_form_view"/>
<field name="name">quality_test_form</field>
</record>
</data>
</tryton>

90
setup.py Normal file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env python
from setuptools import setup
import re
import os
import ConfigParser
MODULE = 'farm'
PREFIX = 'nantic'
MODULE2PREFIX = {
'stock_location_warehouse': 'nantic',
}
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
config = ConfigParser.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()
major_version, minor_version, _ = info.get('version', '0.0.1').split('.', 2)
major_version = int(major_version)
minor_version = int(minor_version)
requires = []
for dep in info.get('depends', []):
if not re.match(r'(ir|res|webdav)(\W|$)', dep):
prefix = MODULE2PREFIX.get(dep, 'trytond')
requires.append('%s_%s >= %s.%s, < %s.%s' %
(prefix, dep, major_version, minor_version,
major_version, minor_version + 1))
requires.append('trytond >= %s.%s, < %s.%s' %
(major_version, minor_version, major_version, minor_version + 1))
tests_require = ['proteus >= %s.%s, < %s.%s' %
(major_version, minor_version, major_version, minor_version + 1)]
setup(name='%s_%s' % (PREFIX, MODULE),
version=info.get('version', '0.0.1'),
description=('Tryton module to manage farms for breeding and fattering '
'animals.'),
long_description=read('README'),
author='NaN·tic',
url='http://www.tryton.org/',
download_url=("http://downloads.tryton.org/" +
info.get('version', '0.0.1').rsplit('.', 1)[0] + '/'),
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', 'tests/*.rst']),
},
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 :: Czech',
'Natural Language :: Dutch',
'Natural Language :: English',
'Natural Language :: French',
'Natural Language :: German',
'Natural Language :: Russian',
'Natural Language :: Spanish',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Topic :: Office/Business',
],
license='GPL-3',
install_requires=requires,
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,
)

655
specie.py Normal file
View File

@ -0,0 +1,655 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
import logging
from trytond.model import ModelView, ModelSQL, fields
from trytond.pyson import Bool, Eval, Id, Not, Or
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.tools import safe_eval
__all__ = ['Specie', 'SpecieModel', 'SpecieFarmLine', 'Breed', 'Menu',
'ActWindow']
MODULE_NAME = 'farm'
def _enabled_STATES(depending_fieldname):
return {
'readonly': Not(Bool(Eval(depending_fieldname))),
'required': Bool(Eval(depending_fieldname)),
}
class Specie(ModelSQL, ModelView):
'''Animal Specie'''
__name__ = 'farm.specie'
name = fields.Char('Name', translate=True, required=True,
help='Name of the specie. ie. "Pig"')
male_product = fields.Many2One('product.product', "Male's Product",
domain=[('default_uom.category', '=', Id('product', 'uom_cat_unit'))],
states=_enabled_STATES('male_enabled'), depends=['male_enabled'])
male_enabled = fields.Boolean('Males Enabled', help="If checked the menus "
"to manage this kind of animal will be generated. If you don't want "
"it, uncheck and put a generic product or other type of animal "
"because it is required.")
female_product = fields.Many2One('product.product', "Female's Product",
domain=[('default_uom.category', '=', Id('product', 'uom_cat_unit'))],
states=_enabled_STATES('female_enabled'), depends=['female_enabled'])
female_enabled = fields.Boolean('Females Enabled', help="If checked the "
"menus to manage this kind of animal will be generated. If you don't "
"want it, uncheck and put a generic product or other type of animal "
"because it is required.")
individual_product = fields.Many2One('product.product',
"Individual's Product",
domain=[('default_uom.category', '=', Id('product', 'uom_cat_unit'))],
states=_enabled_STATES('individual_enabled'),
depends=['individual_enabled'])
individual_enabled = fields.Boolean('Individuals Enabled', help="If "
"checked the menus to manage this kind of animal will be generated. "
"If you don't want it, uncheck and put a generic product or other "
"type of animal because it is required.")
group_product = fields.Many2One('product.product', "Group's Product",
domain=[('default_uom.category', '=', Id('product', 'uom_cat_unit'))],
states=_enabled_STATES('group_enabled'), depends=['group_enabled'])
group_enabled = fields.Boolean('Groups Enabled', help="If checked the "
"menus to manage this kind of animal will be generated. If you don't "
"want it, uncheck and put a generic product or other type of animal "
"because it is required.")
semen_product = fields.Many2One('product.product', "Semen's Product",
# TODO: it doesn't work but it should
#domain=[
# ('default_uom.category', '=', Id('product', 'uom_cat_volume')),
# ],
states={
'readonly': Not(Or(Bool(Eval('male_enabled')),
Bool(Eval('female_enabled')))),
'required': Or(Bool(Eval('male_enabled')),
Bool(Eval('female_enabled'))),
}, depends=['male_enabled', 'female_enabled'],
help="Product for the mixture of semen to raise the expected "
"quality.\nIt is used in the Production lots produced in the "
"Extraction Events and in the BoM containers for doses used in "
"deliveries to farms for inseminations.")
breeds = fields.One2Many('farm.specie.breed', 'specie', 'Breeds')
farm_lines = fields.One2Many('farm.specie.farm_line',
'specie', 'Farms')
removed_location = fields.Many2One('stock.location', 'Removed Location',
domain=[('type', '=', 'lost_found')], required=True,
help='Virtual location where removed animals are moved to.')
foster_location = fields.Many2One('stock.location', 'Foster Location',
domain=[('type', '=', 'lost_found')], required=True,
help='Virtual location where fostered animals are moved to.')
lost_found_location = fields.Many2One('stock.location',
'Lost and Found Location', domain=[('type', '=', 'lost_found')],
required=True,
help='Virtual location where lost or found animals are moved to.')
feed_lost_found_location = fields.Many2One('stock.location',
'Feed Lost Location', domain=[('type', '=', 'lost_found')],
required=True)
events = fields.Many2Many('farm.specie-ir.model', 'specie', 'model',
'Events', domain=[('model', 'like', 'farm.%.event')],
help='Type of events available for this specie')
actions = fields.One2Many('ir.action.act_window', 'specie', 'Actions')
menus = fields.One2Many('ir.ui.menu', 'specie', 'Menus')
@classmethod
def __setup__(cls):
super(Specie, cls).__setup__()
cls._sql_constraints += [
('semen_product_uniq', 'UNIQUE (semen_product)',
'The Semen\'s Product of the Specie must be unique.'),
]
cls._error_messages.update({
'no_animal_type_enabled': ('The action "Create menus and '
'actions" has been launched for the specie "%s" but it '
'does not have any animal type enabled.')
})
cls._buttons.update({
'create_menu_entries': {}
})
@staticmethod
def default_male_enabled():
return True
@staticmethod
def default_female_enabled():
return True
@staticmethod
def default_individual_enabled():
return True
@staticmethod
def default_group_enabled():
return True
@classmethod
@ModelView.button
def create_menu_entries(cls, species):
"""
Create all menu options (and actions) for:
- Males
- Females
- Groups/Lots
- Individuals
- Events related with the specie
Store menus and actions created in order to be able to remove or
recreate them later.
If menu entries (and actions) had already been created, do not remove
them but create only the missing ones, otherwise user shortcuts to
existing menu entries would become invalid or would be removed.
If necessary, replace events many2many by a one2many one with
related menu and actions.
"""
pool = Pool()
Menu = pool.get('ir.ui.menu')
Model = pool.get('ir.model')
ModelData = pool.get('ir.model.data')
ActWindow = pool.get('ir.action.act_window')
EventOrder = pool.get('farm.event.order')
lang_codes = cls._get_lang_codes()
animal_types_data = cls._get_animal_types_data()
animal_types_set = set(animal_types_data.keys())
menu_farm = Menu(ModelData.get_id(MODULE_NAME, 'menu_farm'))
act_window_event_order = ActWindow(ModelData.get_id(MODULE_NAME,
'act_farm_event_order'))
act_window_feed_inventory = ActWindow(ModelData.get_id(MODULE_NAME,
'act_farm_feed_inventory'))
act_window_feed_prov_inventory = ActWindow(
ModelData.get_id(MODULE_NAME,
'act_farm_feed_provisional_inventory'))
with Transaction().set_context(language='en_US'):
event_configuration_data = (
cls._get_act_window_and_animal_types_per_event_model(
animal_types_set))
order_type_labels = dict(EventOrder.fields_get(
['event_type'])['event_type']['selection'])
specie_seq = 1
for specie in species:
enabled_animal_types = []
for animal_type in animal_types_set:
if getattr(specie, '%s_enabled' % animal_type):
enabled_animal_types.append(animal_type)
if not enabled_animal_types:
cls.raise_user_error('no_animal_type_enabled', specie.rec_name)
current_menus = list(specie.menus)[:]
current_actions = list(specie.actions)[:]
logging.getLogger('farm.specie').debug(
"current_menus=%s\ncurrent_actions=%s"
% (current_menus, current_actions))
with Transaction().set_context(lang_codes=lang_codes):
specie_menu = cls._create_submenu(specie, specie.name,
menu_farm, specie_seq, current_menus)
specie_seq += 1
specie_submenu_seq = 1
#### Animal Types and Events menu generation ####
for animal_type in enabled_animal_types:
with Transaction().set_context(lang_codes=lang_codes):
animal_menu = cls._create_menu_w_action(specie, [
('specie', '=', specie.id),
], {
'specie': specie.id,
'animal_type': animal_type,
},
animal_types_data[animal_type]['title'], specie_menu,
specie_submenu_seq, 'STOCK_JUSTIFY_FILL',
animal_types_data[animal_type]['group'],
animal_types_data[animal_type]['action'], True,
current_menus, current_actions)
# The event's models are created in the system in a logic order
# to improve useability
event_ids = [x.id for x in specie.events]
event_ids = sorted(event_ids)
animal_submenu_seq = 1
for event in Model.browse(event_ids):
event_model = event.model
if (animal_type not in event_configuration_data[
event_model]['valid_animal_types']):
continue
event_domain = [
('specie', '=', specie.id),
]
event_context = {
'specie': specie.id,
}
if (len(event_configuration_data[event_model][
'valid_animal_types']) > 1):
event_domain.append(('animal_type', '=', animal_type))
event_context['animal_type'] = animal_type
generic_event = event_configuration_data[event_model][
'is_generic_event']
event_act_window = (
event_configuration_data[event_model]['act_window'])
seq = (generic_event and animal_submenu_seq or
20 + animal_submenu_seq)
icon = (generic_event and 'tryton-list' or
'tryton-preferences-system')
with Transaction().set_context(lang_codes=lang_codes):
cls._create_menu_w_action(specie, event_domain,
event_context, event_act_window.name, animal_menu,
seq, icon, None, event_act_window, False,
current_menus, current_actions)
animal_submenu_seq += 1
specie_submenu_seq += 1
# Orders submenus
for animal_type in enabled_animal_types:
with Transaction().set_context(lang_codes=lang_codes):
animal_orders_menu = cls._create_submenu(specie,
animal_types_data[animal_type]['orders_title'],
specie_menu, specie_submenu_seq, current_menus)
orders_submenu_seq = 1
for order_type in EventOrder.event_types_by_animal_type(
animal_type, True):
order_domain = [
('specie', '=', specie.id),
('animal_type', '=', animal_type),
('event_type', '=', order_type),
]
order_context = {
'specie': specie.id,
'animal_type': animal_type,
'event_type': order_type,
}
with Transaction().set_context(lang_codes=lang_codes):
cls._create_menu_w_action(specie, order_domain,
order_context, order_type_labels[order_type],
animal_orders_menu, orders_submenu_seq,
'tryton-spreadsheet',
animal_types_data[animal_type]['group'],
act_window_event_order, True, current_menus,
current_actions)
orders_submenu_seq += 1
specie_submenu_seq += 1
# Feed Inventories submenu
with Transaction().set_context(lang_codes=lang_codes):
feed_inventories_menu = cls._create_submenu(specie,
'Silo Inventories', specie_menu, specie_submenu_seq,
current_menus)
cls._create_menu_w_action(specie, [
('specie', '=', specie.id),
], {
'specie': specie.id,
},
'Inventory', feed_inventories_menu, 1, 'tryton-list',
None, act_window_feed_inventory, False, current_menus,
current_actions)
cls._create_menu_w_action(specie, [
('specie', '=', specie.id),
], {
'specie': specie.id,
},
'Provisional Inventory', feed_inventories_menu, 2,
'tryton-list', None, act_window_feed_prov_inventory, False,
current_menus, current_actions)
logging.getLogger('farm.specie').debug(
"Current_actions (to be deleted): %s" % current_actions)
if current_actions:
ActWindow.delete(current_actions)
logging.getLogger('farm.specie').debug(
"Current_menus (to be deleted): %s" % current_menus)
if current_menus:
Menu.delete(current_menus)
@classmethod
def _get_lang_codes(cls):
Lang = Pool().get('ir.lang')
langs = Lang.search([
('translatable', '=', True),
])
return [x.code for x in langs]
@staticmethod
def _get_animal_types_data():
'''
Returns a static and hardcoded dictionary of all animal types and
their titles with translations. It is implemented as a function to
allow to extend animal types.
Put translations is, basically, to force system to generate titles
translations.
Returns a dictionary of tuples. the keys are animal types identifiers
and values are tuples of animal type title and translated title.
'''
pool = Pool()
ActWindow = pool.get('ir.action.act_window')
Group = pool.get('res.group')
ModelData = pool.get('ir.model.data')
act_window_male_id = ModelData.get_id(MODULE_NAME,
'act_farm_animal_male')
act_window_female_id = ModelData.get_id(MODULE_NAME,
'act_farm_animal_female')
act_window_group_id = ModelData.get_id(MODULE_NAME,
'act_farm_animal_group')
act_window_individual_id = ModelData.get_id(MODULE_NAME,
'act_farm_animal_individual')
group_males_id = ModelData.get_id(MODULE_NAME, 'group_farm_males')
group_females_id = ModelData.get_id(MODULE_NAME, 'group_farm_females')
group_individuals_id = ModelData.get_id(MODULE_NAME,
'group_farm_individuals')
group_groups_id = ModelData.get_id(MODULE_NAME,
'group_farm_groups')
return {
'male': {
'title': 'Males',
'orders_title': 'Males Orders',
'action': ActWindow(act_window_male_id),
'group': Group(group_males_id),
},
'female': {
'title': 'Females',
'orders_title': 'Females Orders',
'action': ActWindow(act_window_female_id),
'group': Group(group_females_id),
},
'individual': {
'title': 'Individuals',
'orders_title': 'Individuals Orders',
'action': ActWindow(act_window_individual_id),
'group': Group(group_individuals_id),
},
'group': {
'title': 'Groups',
'orders_title': 'Groups Orders',
'action': ActWindow(act_window_group_id),
'group': Group(group_groups_id),
},
}
@staticmethod
def _get_act_window_and_animal_types_per_event_model(animal_types_set):
'''
Returns a dictionary of Events with 'model name' as keys and
'act_window', 'act_window_name' and 'valid_animal_types' as values
'''
pool = Pool()
ModelData = pool.get('ir.model.data')
ActWindow = pool.get('ir.action.act_window')
# It find all 'act_window's with 'res_model' to some farm event
event_act_windows = ActWindow.search([
('res_model', 'like', 'farm.%.event'),
])
# It find 'ir.model.data' of found 'act_window's which name
# (XML identified) is not empty (they has been defined in module,
# not created on previos function execution)
event_act_window_models = ModelData.search([
('model', '=', 'ir.action.act_window'),
('db_id', 'in', [x.id for x in event_act_windows]),
('fs_id', '!=', None),
])
event_act_windows_ids = [md.db_id for md in event_act_window_models]
event_configuration = {}
for act_window in ActWindow.browse(event_act_windows_ids):
valid_animal_types = set(
Pool().get(act_window.res_model).valid_animal_types())
event_configuration[act_window.res_model] = {
'act_window': act_window,
'act_window_name': act_window.name,
'valid_animal_types': valid_animal_types,
# is_generic_event if its valid_animal_types are all
# animal types => difference is empty
'is_generic_event':
not animal_types_set.difference(valid_animal_types)
}
return event_configuration
@classmethod
def _create_submenu(cls, specie, untranslated_title, parent_menu, sequence,
current_menus):
Menu = Pool().get('ir.ui.menu')
menus = Menu.search([
('id', 'in', [x.id for x in current_menus]),
('parent', '=', parent_menu.id),
('name', '=', untranslated_title),
])
values = {
'name': untranslated_title,
'parent': parent_menu.id,
'sequence': sequence,
'specie': specie.id,
}
if menus:
Menu.write(menus, values)
menu = menus[0]
logging.getLogger('farm.specie').debug("Writting menus %s and "
"removing from current_menus" % menus)
current_menus.remove(menu)
else:
menu, = Menu.create([values])
logging.getLogger('farm.specie').debug("Created new menu %s"
% menu)
cls._write_field_in_langs(Menu, menu, 'name', untranslated_title)
return menu
@classmethod
def _create_menu_w_action(cls, specie, new_domain, new_context,
untranslated_title, parent_menu, sequence, menu_icon, group,
original_action, translate_action, current_menus, current_actions):
pool = Pool()
ActWindow = pool.get('ir.action.act_window')
Menu = pool.get('ir.ui.menu')
ModelData = pool.get('ir.model.data')
menus = Menu.search([
('id', 'in', [x.id for x in current_menus]),
('parent', '=', parent_menu.id),
('name', '=', untranslated_title),
])
act_window = None
menu = None
if menus:
menu = menus[0]
act_window = menu.action
if not new_domain:
new_domain = []
original_domain = (original_action.domain and
safe_eval(original_action.domain))
if original_domain and isinstance(original_domain, list):
new_domain.extend(original_domain)
original_context = (original_action.context
and safe_eval(original_action.context))
if original_context and isinstance(original_context, dict):
new_context.update(original_context)
action_vals = {
'specie': specie.id,
'domain': str(new_domain),
'context': str(new_context),
}
if act_window:
ActWindow.write([act_window], action_vals)
logging.getLogger('farm.specie').debug("Writting action %s to be "
"placed in a menu" % act_window)
if act_window in current_actions:
logging.getLogger('farm.specie').debug(
"Removing from current_actions")
current_actions.remove(act_window)
else:
logging.getLogger('farm.specie').debug(
"Creating new action to be placed in menu copying from "
"original action %s"
% original_action)
act_window, = ActWindow.copy([original_action], action_vals)
if translate_action:
cls._write_field_in_langs(ActWindow, act_window, 'name',
untranslated_title)
pass
group_ids = group and [group.id] or []
if group_ids:
group_manager_id = ModelData.get_id(MODULE_NAME,
'group_farm_admin')
group_ids.append(group_manager_id)
menu_vals = {
'name': untranslated_title,
'parent': parent_menu.id,
'sequence': sequence,
'groups': [('set', group_ids)],
'icon': 'tryton-list',
'action': ('ir.action.act_window', act_window.id),
'specie': specie.id,
#'keyword': 'tree_open',
}
if menu:
Menu.write([menu], menu_vals)
logging.getLogger('farm.specie').debug("Writing menu %s with "
"action %s and removing from current_menus"
% (menu, act_window.id))
current_menus.remove(menu)
else:
menu, = Menu.create([menu_vals])
logging.getLogger('farm.specie').debug("Created new menu %s with "
"action %s" % (menu, act_window.id))
cls._write_field_in_langs(Menu, menu, 'name', untranslated_title)
return menu
@classmethod
def _write_field_in_langs(cls, Proxy, obj, fieldname, untranslated_value):
Translation = Pool().get('ir.translation')
context = Transaction().context
lang_codes = context.get('lang_codes') or cls._get_lang_codes()
for lang in lang_codes:
translated_value = Translation.get_source('farm.specie', 'view',
lang, untranslated_value)
logging.getLogger('farm.specie').debug(
"Translated value of name='farm.specie', ttype='view', "
"lang='%s', source='%s': >%s<" % (lang, untranslated_value,
translated_value))
if translated_value:
with Transaction().set_context(language=lang):
Proxy.write([obj], {
fieldname: translated_value,
})
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default['actions'] = False
default['menus'] = False
return super(Specie, cls).copy(records, default=default)
class Breed(ModelSQL, ModelView):
'Breed of Specie'
__name__ = 'farm.specie.breed'
specie = fields.Many2One('farm.specie', 'Specie', required=True,
ondelete='CASCADE')
name = fields.Char('Name', required=True)
@classmethod
def __setup__(cls):
super(Breed, cls).__setup__()
cls._sql_constraints = [
('name_specie_uniq', 'UNIQUE(specie, name)',
'Breed name must be unique per specie.'),
]
class SpecieFarmLine(ModelSQL, ModelView):
'Managed Farm of specie'
__name__ = 'farm.specie.farm_line'
specie = fields.Many2One('farm.specie', 'Specie', required=True,
ondelete='CASCADE')
farm = fields.Many2One('stock.location', 'Farm', required=True,
domain=[('type', '=', 'warehouse')])
event_order_sequence = fields.Many2One('ir.sequence',
"Events Orders' Sequence", required=True, domain=[
('code', '=', 'farm.event.order')
],
help="Sequence used for the Event Orders in this farm.")
has_male = fields.Boolean('Males', help="In this farm there are males.")
male_sequence = fields.Many2One('ir.sequence', "Males' Sequence",
domain=[('code', '=', 'farm.animal')],
states=_enabled_STATES('has_male'), depends=['has_male'],
help='Sequence used for male lots and animals.')
semen_lot_sequence = fields.Many2One('ir.sequence',
"Extracted Semen Lots' Sequence", domain=[
('code', '=', 'stock.lot'),
], states=_enabled_STATES('has_male'), depends=['has_male'])
dose_lot_sequence = fields.Many2One('ir.sequence',
"Semen Dose Lots' Sequence", domain=[
('code', '=', 'stock.lot'),
], states=_enabled_STATES('has_male'), depends=['has_male'])
has_female = fields.Boolean('Females',
help="In this farm there are females.")
female_sequence = fields.Many2One('ir.sequence', "Females' Sequence",
domain=[('code', '=', 'farm.animal')],
states=_enabled_STATES('has_female'), depends=['has_female'],
help='Sequence used for female production lots and animals.')
has_individual = fields.Boolean('Individuals',
help="In this farm there are individuals.")
individual_sequence = fields.Many2One('ir.sequence',
"Individuals' Sequence", domain=[('code', '=', 'farm.animal')],
states=_enabled_STATES('has_individual'), depends=['has_individual'],
help="Sequence used for individual lots and animals.")
has_group = fields.Boolean('Groups',
help="In this farm there are groups.")
group_sequence = fields.Many2One('ir.sequence', "Groups' Sequence",
domain=[('code', '=', 'farm.animal.group')],
states=_enabled_STATES('has_group'), depends=['has_group'],
help='Sequence used for group production lots and animals.')
@classmethod
def __setup__(cls):
super(SpecieFarmLine, cls).__setup__()
cls._sql_constraints += [
('specie_farm_uniq', 'UNIQUE (specie, farm)',
'The Farm of Managed Farms of an specie must be unique.'),
]
class SpecieModel(ModelSQL):
'Specie - Model'
__name__ = 'farm.specie-ir.model'
_table = 'farm_specie_ir_model'
specie = fields.Many2One('farm.specie', 'Specie', required=True)
model = fields.Many2One('ir.model', 'Model', required=True)
class Menu(ModelSQL, ModelView):
__name__ = 'ir.ui.menu'
specie = fields.Many2One('farm.specie', 'Specie', ondelete='CASCADE')
class ActWindow(ModelSQL, ModelView):
__name__ = 'ir.action.act_window'
specie = fields.Many2One('farm.specie', 'Specie', ondelete='CASCADE')

298
specie.xml Normal file
View File

@ -0,0 +1,298 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- Groups -->
<record model="res.group" id="group_farm_admin">
<field name="name">Farm Administration</field>
</record>
<record model="res.user-res.group" id="user_admin_group_farm_admin">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="res.user-res.group" id="user_trigger_group_farm_admin">
<field name="user" ref="res.user_trigger"/>
<field name="group" ref="group_farm_admin"/>
</record>
<record model="res.group" id="group_farm">
<field name="name">Farm</field>
</record>
<record model="res.user-res.group" id="user_admin_group_farm">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_farm"/>
</record>
<record model="res.user-res.group" id="user_trigger_group_farm">
<field name="user" ref="res.user_trigger"/>
<field name="group" ref="group_farm"/>
</record>
<record model="res.group" id="group_farm_males">
<field name="name">Farm / Males</field>
</record>
<record model="res.group" id="group_farm_females">
<field name="name">Farm / Females</field>
</record>
<record model="res.group" id="group_farm_individuals">
<field name="name">Farm / Individuals</field>
</record>
<record model="res.group" id="group_farm_groups">
<field name="name">Farm / Groups</field>
</record>
<!-- Sequences Lot TODO: moure a stock_lot -->
<record model="ir.sequence.type" id="sequence_type_lot">
<field name="name">Stock Lot</field>
<field name="code">stock.lot</field>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_lot_group_farm">
<field name="sequence_type" ref="sequence_type_lot"/>
<field name="group" ref="group_farm"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_group_group_lot_admin">
<field name="sequence_type" ref="sequence_type_lot"/>
<field name="group" ref="group_farm"/>
</record>
<!--
farm.specie
-->
<!-- Translations -->
<record model="ir.translation" id="males_translation">
<field name="name">farm.specie</field>
<field name="lang">en_US</field>
<field name="type">view</field>
<field name="src">Males</field>
<field name="module">farm</field>
</record>
<record model="ir.translation" id="males_orders_translation">
<field name="name">farm.specie</field>
<field name="lang">en_US</field>
<field name="type">view</field>
<field name="src">Males Orders</field>
<field name="module">farm</field>
</record>
<record model="ir.translation" id="females_translation">
<field name="name">farm.specie</field>
<field name="lang">en_US</field>
<field name="type">view</field>
<field name="src">Females</field>
<field name="module">farm</field>
</record>
<record model="ir.translation" id="females_orders_translation">
<field name="name">farm.specie</field>
<field name="lang">en_US</field>
<field name="type">view</field>
<field name="src">Females Orders</field>
<field name="module">farm</field>
</record>
<record model="ir.translation" id="individuals_translation">
<field name="name">farm.specie</field>
<field name="lang">en_US</field>
<field name="type">view</field>
<field name="src">Individuals</field>
<field name="module">farm</field>
</record>
<record model="ir.translation" id="individuals_orders_translation">
<field name="name">farm.specie</field>
<field name="lang">en_US</field>
<field name="type">view</field>
<field name="src">Individuals Orders</field>
<field name="module">farm</field>
</record>
<record model="ir.translation" id="groups_translation">
<field name="name">farm.specie</field>
<field name="lang">en_US</field>
<field name="type">view</field>
<field name="src">Groups</field>
<field name="module">farm</field>
</record>
<record model="ir.translation" id="groups_orders_translation">
<field name="name">farm.specie</field>
<field name="lang">en_US</field>
<field name="type">view</field>
<field name="src">Groups Orders</field>
<field name="module">farm</field>
</record>
<record model="ir.translation" id="feed_inventories_translation">
<field name="name">farm.specie</field>
<field name="lang">en_US</field>
<field name="type">view</field>
<field name="src">Silo Inventories</field>
<field name="module">farm</field>
</record>
<!-- Buttons -->
<record model="ir.model.button" id="create_menu_entries_button">
<field name="name">create_menu_entries</field>
<field name="model" search="[('model', '=', 'farm.specie')]"/>
</record>
<record model="ir.model.button-res.group"
id="farm_menu_entries_button_group_farm_admin">
<field name="button" ref="create_menu_entries_button"/>
<field name="group" ref="group_farm_admin"/>
</record>
<!-- Views -->
<record model="ir.ui.view" id="farm_specie_form_view">
<field name="model">farm.specie</field>
<field name="type">form</field>
<field name="name">farm_specie_form</field>
</record>
<record model="ir.ui.view" id="farm_specie_list_view">
<field name="model">farm.specie</field>
<field name="type">tree</field>
<field name="name">farm_specie_list</field>
</record>
<!-- Actions -->
<record model="ir.action.act_window" id="act_farm_specie">
<field name="name">Species</field>
<field name="res_model">farm.specie</field>
<field name="search_value"></field>
</record>
<record model="ir.action.act_window.view" id="act_farm_specie_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_specie_list_view"/>
<field name="act_window" ref="act_farm_specie"/>
</record>
<record model="ir.action.act_window.view" id="act_farm_specie_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_specie_form_view"/>
<field name="act_window" ref="act_farm_specie"/>
</record>
<!-- Permissions -->
<record model="ir.model.access" id="access_farm_specie">
<field name="model" search="[('model', '=', 'farm.specie')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_farm_specie_admin">
<field name="model" search="[('model', '=', 'farm.specie')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!--
farm.specie.breed
-->
<!-- Views -->
<record model="ir.ui.view" id="farm_specie_breed_form_view">
<field name="model">farm.specie.breed</field>
<field name="type">form</field>
<field name="name">farm_specie_breed_form</field>
</record>
<record model="ir.ui.view" id="farm_specie_breed_list_view">
<field name="model">farm.specie.breed</field>
<field name="type">tree</field>
<field name="name">farm_specie_breed_list</field>
</record>
<!-- Actions -->
<record model="ir.action.act_window" id="act_farm_specie_breed">
<field name="name">Breeds</field>
<field name="res_model">farm.specie.breed</field>
<field name="search_value"></field>
</record>
<record model="ir.action.act_window.view" id="act_farm_specie_breed_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="farm_specie_breed_list_view"/>
<field name="act_window" ref="act_farm_specie_breed"/>
</record>
<record model="ir.action.act_window.view" id="act_farm_specie_breed_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="farm_specie_breed_form_view"/>
<field name="act_window" ref="act_farm_specie_breed"/>
</record>
<!-- Permissions -->
<record model="ir.model.access" id="access_farm_specie_breed">
<field name="model" search="[('model', '=', 'farm.specie.breed')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_farm_specie_breed_admin">
<field name="model" search="[('model', '=', 'farm.specie.breed')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!--
farm.specie.farm_line
-->
<!-- Views -->
<record model="ir.ui.view" id="farm_specie_farm_line_form_view">
<field name="model">farm.specie.farm_line</field>
<field name="type">form</field>
<field name="name">farm_specie_farm_line_form</field>
</record>
<record model="ir.ui.view" id="farm_specie_farm_line_list_view">
<field name="model">farm.specie.farm_line</field>
<field name="type">tree</field>
<field name="name">farm_specie_farm_line_list</field>
</record>
<!-- Permissions -->
<record model="ir.model.access" id="access_farm_specie_farm_line">
<field name="model"
search="[('model', '=', 'farm.specie.farm_line')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access"
id="access_farm_specie_farm_line_admin">
<field name="model"
search="[('model', '=', 'farm.specie.farm_line')]"/>
<field name="group" ref="group_farm_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!-- Menus -->
<record model="ir.ui.icon" id="farm_icon">
<field name="name">tryton-farm</field>
<field name="path">icons/tryton-farm.svg</field>
</record>
<menuitem name="Farm" sequence="2" id="menu_farm"
icon="tryton-farm"/>
<menuitem name="Configuration" parent="menu_farm"
id="menu_configuration" sequence="0" icon="tryton-preferences"/>
<menuitem action="act_farm_specie" id="menu_farm_specie"
parent="menu_configuration" sequence="1"/>
<menuitem action="act_farm_specie_breed" id="menu_farm_specie_breed"
parent="menu_farm_specie" sequence="1"/>
</data>
</tryton>

351
stock.py Normal file
View File

@ -0,0 +1,351 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
import datetime
from collections import defaultdict
from decimal import Decimal
from trytond.model import ModelView, ModelSQL, fields, Workflow
from trytond.pyson import Equal, Eval, Not, Or
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
__all__ = ['Location', 'LocationSiloLocation', 'Lot', 'LotAnimal',
'LotAnimalGroup', 'Move']
__metaclass__ = PoolMeta
class Lot:
__name__ = 'stock.lot'
animal_type = fields.Selection([
(None, ''),
('male', 'Male'),
('female', 'Female'),
('individual', 'Individual'),
('group', 'Group'),
], 'Animal Type', readonly=True)
animal = fields.One2One('stock.lot-farm.animal', 'lot', 'animal',
string='Animal', readonly=True,
states={'invisible': Equal(Eval('animal_type'), 'group')},
depends=['animal_type'])
animal_group = fields.One2One('stock.lot-farm.animal.group', 'lot',
'animal_group', string='Group', readonly=True,
states={
'invisible': Not(Equal(Eval('animal_type'), 'group')),
}, depends=['animal_type'])
#TODO aquestes restriccions les deixem aqui o les passem a 'animal'
# (i group?). Afegir-ho a la tasca
# Add constraint that ensures that if the stock lot is of
# animal_type in (male, female, individual), the lot can only be
# in a single non-virtual location at any given point in time.
# Consider restricting only one unit should be available at any
# given time (may not be easy because then the order in which stock
# moves are done may be relevant).
# Consider making configurable per specie if that constraint should
# apply to 'group' too but with more than one unit.
@staticmethod
def default_animal_type():
return ''
def get_rec_name(self, name):
rec_name = super(Lot, self).get_rec_name(name)
if not self.animal_type:
return rec_name
if self.animal_type == 'group' and self.animal_group:
if not self.animal_group.active:
rec_name += " (*)"
elif self.animal:
if not self.animal.active:
rec_name += " (*)"
return rec_name
@classmethod
def quantity_by_location(cls, lots, location_ids, with_childs=False):
Product = Pool().get('product.product')
pbl = Product.products_by_location(location_ids,
product_ids=list(set(l.product.id for l in lots)),
with_childs=with_childs,
grouping=('product', 'lot'))
quantities = {}
for (location_id, product_id, lot_id), quantity in pbl.iteritems():
if lot_id is None:
continue
lot_quantities = quantities.setdefault(lot_id, {})
lot_quantities[location_id] = quantity
return quantities
class LotAnimal(ModelSQL):
"Lot - Animal"
__name__ = 'stock.lot-farm.animal'
lot = fields.Many2One('stock.lot', 'Lot', required=True,
ondelete='RESTRICT')
animal = fields.Many2One('farm.animal', 'Animal', required=True,
ondelete='RESTRICT')
@classmethod
def __setup__(cls):
super(LotAnimal, cls).__setup__()
cls._sql_constraints += [
('lot_unique', 'UNIQUE(lot)', 'The Lot must be unique.'),
('animal_unique', 'UNIQUE(animal)', 'The Animal must be unique.'),
]
class LotAnimalGroup(ModelSQL):
"Lot - Animal Group"
__name__ = 'stock.lot-farm.animal.group'
lot = fields.Many2One('stock.lot', 'Lot', required=True,
ondelete='RESTRICT')
animal_group = fields.Many2One('farm.animal.group', 'Animal Group',
required=True, ondelete='RESTRICT')
@classmethod
def __setup__(cls):
super(LotAnimalGroup, cls).__setup__()
cls._sql_constraints += [
('lot_unique', 'UNIQUE(lot)', 'The lot must be unique.'),
('animal_group_unique', 'UNIQUE(animal_group)',
'The group must be unique.'),
]
class Location:
__name__ = 'stock.location'
silo = fields.Boolean('Silo', select=True,
help='Indicates that the location is a silo.')
current_lot = fields.Function(fields.Many2One('stock.lot',
'Current Lot', states={
'invisible': Not(Eval('silo', False)),
}, depends=['silo']),
'get_current_lot')
locations_to_fed = fields.Many2Many('stock.location.silo-stock.location',
'silo', 'location', 'Locations to fed', states={
'invisible': Not(Eval('silo', False)),
}, depends=['silo'],
help='Indicates the locations the silo feeds. Note that this will '
'only be a default value.')
feed_inventory_lines = fields.One2Many('farm.feed.inventory.line',
'dest_location', 'Input Inventory Lines', readonly=True, states={
'invisible': Or(Not(Equal(Eval('type'), 'storage')),
Eval('silo', False)),
}, depends=['type', 'silo'])
@staticmethod
def default_silo():
return False
@classmethod
def get_current_lot(cls, locations, name):
'''
It suposes that a silo is never filled by same lot two times.
If letters represent lots and list represents the silo, this case never
happens: [A, B, A, C]
'''
pool = Pool()
Move = pool.get('stock.move')
Product = pool.get('product.product')
current_lots = {}.fromkeys([l.id for l in locations], None)
silo_locations = [l for l in locations if l.silo]
if not silo_locations:
return current_lots
pbl = Product.products_by_location([l.id for l in silo_locations],
with_childs=False, grouping=('product', 'lot'))
location_lots = defaultdict(set)
for (location_id, product_id, lot_id), quantity in pbl.iteritems():
if (lot_id is not None and
Decimal(str(quantity)).quantize(Decimal('0.01'))
> Decimal('0.01')):
location_lots[location_id].add(lot_id)
for location in silo_locations:
if not location_lots[location.id]:
continue
first_moves = Move.search([
('lot', 'in', list(location_lots[location.id])),
('state', '=', 'done'),
('to_location', '=', location.id),
], offset=0, limit=1,
order=[('effective_date', 'ASC'), ('id', 'ASC')])
current_lots[location.id] = first_moves and first_moves[0].lot.id
return current_lots
@classmethod
def search(cls, args, offset=0, limit=None, order=None, count=False,
query_string=False):
FarmLine = Pool().get('farm.specie.farm_line')
args = args[:]
context = Transaction().context
if context.get('restrict_by_specie_animal_type'):
specie_id = context.get('specie')
animal_type = context.get('animal_type')
farm_lines = FarmLine.search([
('specie', '=', specie_id),
('has_' + animal_type, '=', True),
])
if not farm_lines:
return []
storage_locations = [fl.farm.storage_location.id
for fl in farm_lines]
#args.append(('parent', 'child_of', storage_locations))
args += [[
'OR', [
('parent', 'child_of', storage_locations),
], [
('id', 'in', [fl.farm.id for fl in farm_lines]),
],
]]
res = super(Location, cls).search(args, offset=offset, limit=limit,
order=order, count=count, query_string=query_string)
return res
def get_lot_fifo(self, stock_date=None, to_uom=None):
'''
Only for 'silo' locations, it returns the list of tuples of lots in
location at specified date and their available stock in specified UOM,
sorted by input date (FIFO):
[(<lot instance>, <available quantity in 'to_uom'>)]
It suposes that a silo is never filled by same lot two times.
If letters represent lots and list represents the silo, this case never
happens: [A, B, A, C]
It doesn't computes child locations, raise an exception if some product
in location dosen't have a compatible UoM and returns a Decimal.
If 'stock_date' is not specified, today is used.
If 'to_uom' is not specified, it is returned in the default_uom of the
product of each lot.
'''
pool = Pool()
Move = pool.get('stock.move')
Product = pool.get('product.product')
Uom = pool.get('product.uom')
if not self.silo:
return []
if stock_date is None:
stock_date = datetime.date.today()
with Transaction().set_context(stock_date_end=stock_date):
pbl = Product.products_by_location([self.id], with_childs=False,
grouping=('product', 'lot'))
lot_quantities = {}
for (location_id, product_id, lot_id), quantity in pbl.iteritems():
if lot_id is not None and quantity >= 0.0:
lot_quantities[lot_id] = quantity
lot_fifo = []
moves = Move.search([
('lot', 'in', lot_quantities.keys()),
('state', '=', 'done'),
('to_location', '=', self.id),
('effective_date', '<=', stock_date),
], offset=0,
order=[
('effective_date', 'ASC'),
('id', 'ASC'),
])
for move in moves:
if to_uom is None or move.product.default_uom.id == to_uom.id:
quantity = lot_quantities[move.lot.id]
else:
assert (move.product.default_uom.category.id ==
to_uom.category.id), ('Invalid to_uom "%s" in '
'Location.get_lot_fifo(). Incompatible with default UoM '
'of product "%s" in silo "%s"'
% (to_uom.rec_name, move.product.rec_name, self.rec_name))
quantity = Uom.compute_qty(move.product.default_uom,
lot_quantities[move.lot.id], to_uom, round=True)
lot_fifo.append((move.lot, Decimal(str(quantity))))
return lot_fifo
def get_total_quantity(self, stock_date=None, to_uom=None):
'''
Returns the total amount of any product in location at specified date
in the specified UOM.
It doesn't computes child locations, raise an exception if some product
in location dosen't have a compatible UoM and returns a Decimal.
'''
pool = Pool()
Product = pool.get('product.product')
Uom = pool.get('product.uom')
if stock_date is None:
stock_date = datetime.date.today()
with Transaction().set_context(stock_date_end=stock_date):
pbl = Product.products_by_location([self.id], with_childs=False)
total_quantity = Decimal('0.0')
for (location_id, product_id), quantity in pbl.iteritems():
product = Product(product_id)
if to_uom is not None and product.default_uom.id != to_uom.id:
assert (product.default_uom.category.id ==
to_uom.category.id), ('Invalid to_uom "%s" in '
'Location.get_total_quantity(). Incompatible with default '
'UoM of product "%s" in location "%s"'
% (to_uom.rec_name, product.rec_name, self.rec_name))
quantity = Uom.compute_qty(product.default_uom, quantity,
to_uom, round=True)
total_quantity += Decimal(str(quantity))
return total_quantity
class LocationSiloLocation(ModelSQL):
'Silo - Location'
__name__ = 'stock.location.silo-stock.location'
silo = fields.Many2One('stock.location', 'Silo', required=True)
location = fields.Many2One('stock.location', 'Location', required=True)
class Move:
__name__ = 'stock.move'
@classmethod
def _get_origin(cls):
models = super(Move, cls)._get_origin()
models += [
'farm.animal',
'farm.animal.group',
'farm.move.event',
'farm.transformation.event',
'farm.removal.event',
'farm.feed.event',
'farm.medication.event',
'farm.semen_extraction.event',
'farm.semen_extraction.delivery',
'farm.insemination.event',
'farm.farrowing.event',
'farm.foster.event',
'farm.weaning.event',
]
return models
@classmethod
@ModelView.button
@Workflow.transition('done')
def do(cls, moves):
res = super(Move, cls).do(moves)
for move in moves:
if (not move.lot or not move.lot.animal_type or
move.lot.animal_type == 'group'):
continue
move.lot.animal.location = move.to_location.id
move.lot.animal.save()
return res

28
stock.xml Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- stock.lot -->
<record model="ir.ui.view" id="lot_view_form">
<field name="model">stock.lot</field>
<field name="type">form</field>
<field name="inherit" ref="stock_lot.lot_view_form"/>
<field name="name">stock_lot_form</field>
</record>
<!-- stock.location -->
<record model="ir.ui.view" id="location_view_form">
<field name="model">stock.location</field>
<field name="type">form</field>
<field name="inherit" ref="stock.location_view_form"/>
<field name="name">stock_location_form</field>
</record>
<record model="ir.ui.view" id="location_view_list">
<field name="model">stock.location</field>
<field name="type">tree</field>
<field name="inherit" ref="stock.location_view_list"/>
<field name="name">stock_location_list</field>
</record>
</data>
</tryton>

3
tests/__init__.py Normal file
View File

@ -0,0 +1,3 @@
#the copyright file at the top level of this repository contains the full
#copyright notices and license terms.
from .test_product import suite

View File

@ -0,0 +1,339 @@
=====================
Abort Events Scenario
=====================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create specie's products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> cm3, = ProductUom.find([('name', '=', 'Cubic centimeter')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> female_template = ProductTemplate(
... name='Female Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> female_template.save()
>>> female_product = Product(template=female_template)
>>> female_product.save()
>>> semen_template = ProductTemplate(
... name='Pig Semen',
... default_uom=cm3,
... type='goods',
... consumable=True,
... list_price=Decimal('400'),
... cost_price=Decimal('250'))
>>> semen_template.save()
>>> semen_product = Product(template=semen_template)
>>> semen_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> female_sequence = Sequence(
... name='Female Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> female_sequence.save()
Prepare locations::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> production_location = Location(
... name='Production Location',
... code='PROD',
... type='production',
... parent=warehouse)
>>> production_location.save()
>>> warehouse.production_location=production_location
>>> warehouse.save()
>>> warehouse.reload()
>>> production_location.reload()
Create specie::
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=False,
... female_enabled=True,
... female_product=female_product,
... semen_product=semen_product,
... individual_enabled=False,
... group_enabled=False,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... farm=warehouse,
... event_order_sequence=event_order_sequence,
... has_male=False,
... has_female=True,
... female_sequence=female_sequence,
... has_individual=False,
... has_group=False)
>>> pigs_farm_line.save()
Set animal_type and specie in context to work as in the menus::
>>> config._context['specie'] = pigs_specie.id
>>> config._context['animal_type'] = 'female'
Create female to be inseminated, check it's pregnancy state and abort two
times::
>>> Animal = Model.get('farm.animal')
>>> female = Animal(
... type='female',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=warehouse.storage_location)
>>> female.save()
>>> female.location.code
u'STO'
>>> female.farm.code
u'WH'
>>> female.current_cycle
>>> female.state
u'prospective'
Create insemination event without dose BoM nor Lot and validate it::
>>> InseminationEvent = Model.get('farm.insemination.event')
>>> now = datetime.datetime.now()
>>> inseminate_female = InseminationEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female)
>>> inseminate_female.save()
>>> InseminationEvent.validate_event([inseminate_female.id],
... config.context)
>>> inseminate_female.reload()
>>> inseminate_female.state
u'validated'
Check female is mated::
>>> female.reload()
>>> female.state
u'mated'
>>> female.current_cycle.state
u'mated'
Create pregnancy diagnosis event with positive result and validate it::
>>> PregnancyDiagnosisEvent = Model.get('farm.pregnancy_diagnosis.event')
>>> now = datetime.datetime.now()
>>> diagnose_female = PregnancyDiagnosisEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female,
... result='positive')
>>> diagnose_female.save()
>>> PregnancyDiagnosisEvent.validate_event([diagnose_female.id],
... config.context)
>>> diagnose_female.reload()
>>> diagnose_female.state
u'validated'
Check female is pregnant::
>>> female.reload()
>>> female.current_cycle.state
u'pregnant'
>>> female.current_cycle.pregnant
1
Create abort event::
>>> AbortEvent = Model.get('farm.abort.event')
>>> now = datetime.datetime.now()
>>> abort_female = AbortEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female)
>>> abort_female.save()
Validate abort event::
>>> AbortEvent.validate_event([abort_female.id], config.context)
>>> abort_female.reload()
>>> abort_female.state
u'validated'
Check female is not pregnant, it is in 'prospective' state and its current
cycle is 'unmated'::
>>> female.reload()
>>> female.current_cycle.pregnant
0
>>> female.current_cycle.state
u'unmated'
>>> female.state
u'prospective'
Create second insemination event without dose BoM nor Lot and validate it::
>>> now = datetime.datetime.now()
>>> inseminate_female2 = InseminationEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female)
>>> inseminate_female2.save()
>>> InseminationEvent.validate_event([inseminate_female2.id],
... config.context)
>>> inseminate_female2.reload()
>>> inseminate_female2.state
u'validated'
Check female has two cycles but both with the same sequence, it and its current
cycle is mated and the first cycle (old) is unmated::
>>> female.reload()
>>> len(female.cycles)
2
>>> female.cycles[0].sequence == female.cycles[1].sequence
1
>>> female.state
u'mated'
>>> female.current_cycle.state
u'mated'
>>> female.cycles[0].state
u'unmated'
Create second pregnancy diagnosis event with positive result and validate it::
>>> now = datetime.datetime.now()
>>> diagnose_female2 = PregnancyDiagnosisEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female,
... result='positive')
>>> diagnose_female2.save()
>>> PregnancyDiagnosisEvent.validate_event([diagnose_female2.id],
... config.context)
>>> diagnose_female2.reload()
>>> diagnose_female2.state
u'validated'
Check female is pregnant::
>>> female.reload()
>>> female.current_cycle.state
u'pregnant'
>>> female.current_cycle.pregnant
1
Create second abort event::
>>> now = datetime.datetime.now()
>>> abort_female2 = AbortEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female)
>>> abort_female2.save()
Validate abort event::
>>> AbortEvent.validate_event([abort_female2.id], config.context)
>>> abort_female2.reload()
>>> abort_female2.state
u'validated'
Check female is not pregnant and it and its current cycle is 'unmated'::
>>> female.reload()
>>> female.current_cycle.pregnant
0
>>> female.current_cycle.state
u'unmated'
>>> female.state
u'unmated'

View File

@ -0,0 +1,373 @@
=========================
Farrowing Events Scenario
=========================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create specie's products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> cm3, = ProductUom.find([('name', '=', 'Cubic centimeter')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> female_template = ProductTemplate(
... name='Female Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> female_template.save()
>>> female_product = Product(template=female_template)
>>> female_product.save()
>>> group_template = ProductTemplate(
... name='Group of Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('30'),
... cost_price=Decimal('20'))
>>> group_template.save()
>>> group_product = Product(template=group_template)
>>> group_product.save()
>>> semen_template = ProductTemplate(
... name='Pig Semen',
... default_uom=cm3,
... type='goods',
... consumable=True,
... list_price=Decimal('400'),
... cost_price=Decimal('250'))
>>> semen_template.save()
>>> semen_product = Product(template=semen_template)
>>> semen_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> female_sequence = Sequence(
... name='Female Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> female_sequence.save()
>>> group_sequence = Sequence(
... name='Groups Pig Warehouse 1',
... code='farm.animal.group',
... padding=4)
>>> group_sequence.save()
Prepare locations::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> production_location = Location(
... name='Production Location',
... code='PROD',
... type='production',
... parent=warehouse)
>>> production_location.save()
>>> warehouse.production_location=production_location
>>> warehouse.save()
>>> warehouse.reload()
>>> production_location.reload()
Create specie::
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=False,
... female_enabled=True,
... female_product=female_product,
... semen_product=semen_product,
... individual_enabled=False,
... group_enabled=True,
... group_product=group_product,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... farm=warehouse,
... event_order_sequence=event_order_sequence,
... has_male=False,
... has_female=True,
... female_sequence=female_sequence,
... has_individual=False,
... has_group=True,
... group_sequence=group_sequence)
>>> pigs_farm_line.save()
Set animal_type and specie in context to work as in the menus::
>>> config._context['specie'] = pigs_specie.id
>>> config._context['animal_type'] = 'female'
Create female to be inseminated, check it's pregnancy state and farrow two
times (one without lives and second with)::
>>> Animal = Model.get('farm.animal')
>>> female = Animal(
... type='female',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=warehouse.storage_location)
>>> female.save()
>>> female.location.code
u'STO'
>>> female.farm.code
u'WH'
>>> female.current_cycle
>>> female.state
u'prospective'
Create insemination event without dose BoM nor Lot and validate it::
>>> InseminationEvent = Model.get('farm.insemination.event')
>>> now = datetime.datetime.now()
>>> inseminate_female = InseminationEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female)
>>> inseminate_female.save()
>>> InseminationEvent.validate_event([inseminate_female.id],
... config.context)
>>> inseminate_female.reload()
>>> inseminate_female.state
u'validated'
Check female is mated::
>>> female.reload()
>>> female.state
u'mated'
>>> female.current_cycle.state
u'mated'
Create pregnancy diagnosis event with positive result and validate it::
>>> PregnancyDiagnosisEvent = Model.get('farm.pregnancy_diagnosis.event')
>>> now = datetime.datetime.now()
>>> diagnose_female = PregnancyDiagnosisEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female,
... result='positive')
>>> diagnose_female.save()
>>> PregnancyDiagnosisEvent.validate_event([diagnose_female.id],
... config.context)
>>> diagnose_female.reload()
>>> diagnose_female.state
u'validated'
Check female is pregnant::
>>> female.reload()
>>> female.current_cycle.state
u'pregnant'
>>> female.current_cycle.pregnant
1
Create farrowing event without lives::
>>> FarrowingEvent = Model.get('farm.farrowing.event')
>>> FarrowingProblem = Model.get('farm.farrowing.problem')
>>> farrowing_problem = FarrowingProblem.find([], limit=1)[0]
>>> now = datetime.datetime.now()
>>> farrow_event = FarrowingEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female,
... live=0,
... stillborn=4,
... mummified=2,
... problem=farrowing_problem)
>>> farrow_event.save()
Validate farrowing event::
>>> FarrowingEvent.validate_event([farrow_event.id], config.context)
>>> farrow_event.reload()
>>> farrow_event.state
u'validated'
Check female is not pregnant, its current cycle is in 'unmated' state, it is in
'prospective' state and check female functional fields values::
>>> female.reload()
>>> female.current_cycle.pregnant
False
>>> female.current_cycle.state
u'unmated'
>>> female.state
u'prospective'
>>> female.last_produced_group
>>> female.current_cycle.live
0
>>> female.current_cycle.dead
6
Create second insemination event without dose BoM nor Lot and validate it::
>>> now = datetime.datetime.now()
>>> inseminate_female2 = InseminationEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female)
>>> inseminate_female2.save()
>>> InseminationEvent.validate_event([inseminate_female2.id],
... config.context)
>>> inseminate_female2.reload()
>>> inseminate_female2.state
u'validated'
Check female has two cycles with diferent sequences, it and its current
cycle is mated and the first cycle (old) is unmated::
>>> female.reload()
>>> len(female.cycles)
2
>>> female.cycles[0].sequence != female.cycles[1].sequence
1
>>> female.current_cycle.state
u'mated'
>>> female.state
u'mated'
>>> female.cycles[0].state
u'unmated'
Create second pregnancy diagnosis event with positive result and validate it::
>>> now = datetime.datetime.now()
>>> diagnose_female2 = PregnancyDiagnosisEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female,
... result='positive')
>>> diagnose_female2.save()
>>> PregnancyDiagnosisEvent.validate_event([diagnose_female2.id],
... config.context)
>>> diagnose_female2.reload()
>>> diagnose_female2.state
u'validated'
Check female is pregnant::
>>> female.reload()
>>> female.current_cycle.pregnant
1
>>> female.current_cycle.state
u'pregnant'
Create second farrowing event with lives::
>>> now = datetime.datetime.now()
>>> farrow_event2 = FarrowingEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female,
... live=7,
... stillborn=2)
>>> farrow_event2.save()
Validate farrowing event::
>>> FarrowingEvent.validate_event([farrow_event2.id], config.context)
>>> farrow_event2.reload()
>>> farrow_event2.state
u'validated'
Check female is not pregnant, its current cycle are in 'lactating' state,
it is 'mated' and check female functional fields values::
>>> female.reload()
>>> female.current_cycle.pregnant
0
>>> female.current_cycle.state
u'lactating'
>>> female.state
u'mated'
>>> female.current_cycle.live
7
>>> female.current_cycle.dead
2

View File

@ -0,0 +1,300 @@
====================
Feed Events Scenario
====================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> individual_template = ProductTemplate(
... name='Male Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> individual_template.save()
>>> individual_product = Product(template=individual_template)
>>> individual_product.save()
>>> group_template = ProductTemplate(
... name='Group of Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('30'),
... cost_price=Decimal('20'))
>>> group_template.save()
>>> group_product = Product(template=group_template)
>>> group_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> individual_sequence = Sequence(
... name='Individual Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> individual_sequence.save()
>>> group_sequence = Sequence(
... name='Groups Pig Warehouse 1',
... code='farm.animal.group',
... padding=4)
>>> group_sequence.save()
Prepare farm locations::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> production_location = Location(
... name='Production Location',
... code='PROD',
... type='production',
... parent=warehouse)
>>> production_location.save()
>>> warehouse.production_location=production_location
>>> warehouse.save()
>>> warehouse.reload()
>>> production_location.reload()
>>> location1_id, location2_id = Location.create([{
... 'name': 'Location 1',
... 'code': 'L1',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }, {
... 'name': 'Location 2',
... 'code': 'L2',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }], config.context)
>>> silo1 = Location(
... name='Silo 1',
... code='S1',
... type='storage',
... parent=warehouse.storage_location,
... silo=True,
... locations_to_fed=[location1_id, location2_id])
>>> silo1.save()
Create specie::
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=False,
... female_enabled=False,
... individual_enabled=True,
... individual_product=individual_product,
... group_enabled=True,
... group_product=group_product,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... event_order_sequence=event_order_sequence,
... farm=warehouse,
... has_individual=True,
... individual_sequence=individual_sequence,
... has_group=True,
... group_sequence=group_sequence)
>>> pigs_farm_line.save()
Create feed Product and Lot::
>>> ProductUom = Model.get('product.uom')
>>> kg, = ProductUom.find([('name', '=', 'Kilogram')])
>>> feed_template = ProductTemplate(
... name='Pig Feed',
... default_uom=kg,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> feed_template.save()
>>> feed_product = Product(template=feed_template)
>>> feed_product.save()
>>> Lot = Model.get('stock.lot')
>>> feed_lot = Lot(
... number='F001',
... product=feed_product)
>>> feed_lot.save()
Put 5,1 Kg of feed into the silo location::
>>> Move = Model.get('stock.move')
>>> now = datetime.datetime.now()
>>> provisioning_moves = Move.create([{
... 'product': feed_product.id,
... 'uom': kg.id,
... 'quantity': 5.10,
... 'from_location': party.supplier_location.id,
... 'to_location': silo1.id,
... 'planned_date': now.date(),
... 'effective_date': now.date(),
... 'company': config.context.get('company'),
... 'lot': feed_lot.id,
... 'unit_price': feed_product.template.list_price,
... }],
... config.context)
>>> Move.assign(provisioning_moves, config.context)
>>> Move.do(provisioning_moves, config.context)
Set animal_type and specie in context to work as in the menus::
>>> config._context['specie'] = pigs_specie.id
>>> config._context['animal_type'] = 'individual'
Create individual::
>>> Animal = Model.get('farm.animal')
>>> individual = Animal(
... type='individual',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=location1_id)
>>> individual.save()
>>> individual.location.code
u'L1'
>>> individual.farm.code
u'WH'
Create individual feed event::
>>> FeedEvent = Model.get('farm.feed.event')
>>> gr, = ProductUom.find([('name', '=', 'Gram')])
>>> feed_individual = FeedEvent(
... animal_type='individual',
... specie=pigs_specie,
... farm=warehouse,
... animal=individual,
... timestamp=now,
... location=individual.location,
... feed_location=silo1,
... feed_product=feed_product,
... feed_lot=feed_lot,
... uom=gr,
... quantity=Decimal('2100.0'))
>>> feed_individual.save()
Validate individual feed event::
>>> FeedEvent.validate_event([feed_individual.id], config.context)
>>> feed_individual.reload()
>>> feed_individual.state
u'validated'
>>> silo1.current_lot.id == feed_lot.id
True
Create group::
>>> AnimalGroup = Model.get('farm.animal.group')
>>> animal_group = AnimalGroup(
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=location2_id,
... initial_quantity=4)
>>> animal_group.save()
Create animal_group feed event::
>>> feed_animal_group = FeedEvent(
... animal_type='group',
... specie=pigs_specie,
... farm=warehouse,
... animal_group=animal_group,
... timestamp=now,
... location=location2_id,
... feed_location=silo1,
... feed_product=feed_product,
... feed_lot=feed_lot,
... uom=gr,
... quantity=Decimal('3000.0'),
... start_date=(now.date() - datetime.timedelta(days=7)),
... end_date=now)
>>> feed_animal_group.save()
Validate animal_group feed event::
>>> FeedEvent.validate_event([feed_animal_group.id], config.context)
>>> feed_animal_group.reload()
>>> feed_animal_group.state
u'validated'
>>> animal_group.reload()
>>> unused = config.set_context({'locations': [silo1.id]})
>>> silo1.current_lot.reload()
>>> silo1.current_lot.quantity
0.0
>>> silo1.current_lot.product.reload()
>>> silo1.current_lot.product.quantity
0.0

View File

@ -0,0 +1,448 @@
====================
Feed Events Scenario
====================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal, ROUND_HALF_EVEN
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> individual_template = ProductTemplate(
... name='Male Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> individual_template.save()
>>> individual_product = Product(template=individual_template)
>>> individual_product.save()
>>> group_template = ProductTemplate(
... name='Group of Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('30'),
... cost_price=Decimal('20'))
>>> group_template.save()
>>> group_product = Product(template=group_template)
>>> group_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> individual_sequence = Sequence(
... name='Individual Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> individual_sequence.save()
>>> group_sequence = Sequence(
... name='Groups Pig Warehouse 1',
... code='farm.animal.group',
... padding=4)
>>> group_sequence.save()
Prepare farm locations L1, L2 and L3, and Silo location::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> production_location = Location(
... name='Production Location',
... code='PROD',
... type='production',
... parent=warehouse)
>>> production_location.save()
>>> warehouse.production_location=production_location
>>> warehouse.save()
>>> warehouse.reload()
>>> production_location.reload()
>>> location1_id, location2_id, location3_id = Location.create([{
... 'name': 'Location 1',
... 'code': 'L1',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }, {
... 'name': 'Location 2',
... 'code': 'L2',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }, {
... 'name': 'Location 3',
... 'code': 'L3',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }], config.context)
>>> silo1 = Location(
... name='Silo 1',
... code='S1',
... type='storage',
... parent=warehouse.storage_location,
... silo=True,
... locations_to_fed=[location1_id, location2_id, location3_id])
>>> silo1.save()
Create specie::
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=False,
... female_enabled=False,
... individual_enabled=True,
... individual_product=individual_product,
... group_enabled=True,
... group_product=group_product,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... event_order_sequence=event_order_sequence,
... farm=warehouse,
... has_individual=True,
... individual_sequence=individual_sequence,
... has_group=True,
... group_sequence=group_sequence)
>>> pigs_farm_line.save()
Create Feed Product and 2 Lots::
>>> ProductUom = Model.get('product.uom')
>>> kg, = ProductUom.find([('name', '=', 'Kilogram')])
>>> feed_template = ProductTemplate(
... name='Pig Feed',
... default_uom=kg,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> feed_template.save()
>>> feed_product = Product(template=feed_template)
>>> feed_product.save()
>>> Lot = Model.get('stock.lot')
>>> feed_lot1_id, feed_lot2_id = Lot.create([{
... 'number': 'F001',
... 'product': feed_product.id,
... }, {
... 'number': 'F002',
... 'product': feed_product.id,
... }], config.context)
Set animal_type as 'individual' and specie in context to work as in the menus::
>>> config._context['specie'] = pigs_specie.id
>>> config._context['animal_type'] = 'individual'
Create individual I1 in location L1 arrived 10 days before::
>>> Animal = Model.get('farm.animal')
>>> individual1 = Animal(
... type='individual',
... specie=pigs_specie,
... breed=pigs_breed,
... number='I1',
... arrival_date=(now.date() - datetime.timedelta(days=10)),
... initial_location=location1_id)
>>> individual1.save()
Create individual I2 in location L2 arrived 6 days before::
>>> individual2 = Animal(
... type='individual',
... specie=pigs_specie,
... breed=pigs_breed,
... number='I2',
... arrival_date=(now.date() - datetime.timedelta(days=6)),
... initial_location=location2_id)
>>> individual2.save()
Move individual I2 to location L1 5 days before::
>>> MoveEvent = Model.get('farm.move.event')
>>> move_individual2 = MoveEvent(
... farm=warehouse,
... animal=individual2,
... timestamp=(now - datetime.timedelta(days=5)),
... from_location=location2_id,
... to_location=location1_id)
>>> move_individual2.save()
>>> MoveEvent.validate_event([move_individual2.id], config.context)
Create individuals I3, I4 and I5 in location L3 arrived 5 days before::
>>> individual3_id, individual4_id, individual5_id = Animal.create([{
... 'breed': pigs_breed.id,
... 'number': 'I3',
... 'arrival_date': now.date() - datetime.timedelta(days=5),
... 'initial_location': location3_id,
... }, {
... 'breed': pigs_breed.id,
... 'number': 'I4',
... 'arrival_date': now.date() - datetime.timedelta(days=5),
... 'initial_location': location3_id,
... }, {
... 'breed': pigs_breed.id,
... 'number': 'I5',
... 'arrival_date': now.date() - datetime.timedelta(days=5),
... 'initial_location': location3_id,
... }], config.context)
Move individual I4 to location L2 3 days before::
>>> move_individual4 = MoveEvent(
... farm=warehouse,
... animal=individual4_id,
... timestamp=(now - datetime.timedelta(days=3)),
... from_location=location3_id,
... to_location=location2_id)
>>> move_individual4.save()
>>> MoveEvent.validate_event([move_individual4.id], config.context)
Set animal_type as 'group' in context::
>>> config._context['animal_type'] = 'group'
Create group G1 with 4 units in location L1 arrived 7 days before::
>>> AnimalGroup = Model.get('farm.animal.group')
>>> animal_group1 = AnimalGroup(
... specie=pigs_specie,
... breed=pigs_breed,
... arrival_date=(now.date() - datetime.timedelta(days=7)),
... initial_location=location1_id,
... initial_quantity=4)
>>> animal_group1.save()
Move 2 units of group G1 to location L2 1 day before::
>>> move_group1 = MoveEvent(
... animal_type='group',
... specie=pigs_specie,
... farm=warehouse,
... animal_group=animal_group1,
... timestamp=(now - datetime.timedelta(days=1)),
... from_location=location1_id,
... to_location=location2_id,
... quantity=2)
>>> move_group1.save()
>>> MoveEvent.validate_event([move_group1.id], config.context)
Remove animal_type from context::
>>> del config._context['animal_type']
Put 2000 Kg of first Lot of Feed into the silo 10 days before, and 1500 Kg of
second Lot of Feed 3 days before::
>>> Move = Model.get('stock.move')
>>> now = datetime.datetime.now()
>>> provisioning_moves = Move.create([{
... 'product': feed_product.id,
... 'lot': feed_lot1_id,
... 'uom': kg.id,
... 'quantity': 2000.00,
... 'from_location': party.supplier_location.id,
... 'to_location': silo1.id,
... 'planned_date': now.date() - datetime.timedelta(days=8),
... 'effective_date': now.date() - datetime.timedelta(days=8),
... 'company': config.context.get('company'),
... 'unit_price': feed_product.template.list_price,
... }, {
... 'product': feed_product.id,
... 'lot': feed_lot2_id,
... 'uom': kg.id,
... 'quantity': 1500.00,
... 'from_location': party.supplier_location.id,
... 'to_location': silo1.id,
... 'planned_date': now.date() - datetime.timedelta(days=3),
... 'effective_date': now.date() - datetime.timedelta(days=3),
... 'company': config.context.get('company'),
... 'unit_price': feed_product.template.list_price,
... }], config.context)
>>> Move.assign(provisioning_moves, config.context)
>>> Move.do(provisioning_moves, config.context)
Create initial (real) feed inventory for silo S1 and silo's locations to fed at
8 days before::
>>> FeedInventory = Model.get('farm.feed.inventory')
>>> feed_inventory0 = FeedInventory(
... location=silo1,
... timestamp=(now - datetime.timedelta(days=8)),
... quantity=Decimal('2000.00'),
... uom=kg,
... )
>>> feed_inventory0.save()
>>> feed_inventory0.state
u'draft'
>>> set([l.id for l in feed_inventory0.dest_locations]) == set([
... location1_id, location2_id, location3_id])
True
Confirm initial feed inventory. As it is the initial, it doesn't have any line
nor feed event::
>>> FeedInventory.confirm([feed_inventory0.id], config.context)
>>> feed_inventory0.reload()
>>> feed_inventory0.state
u'validated'
>>> feed_inventory0.lines
[]
>>> feed_inventory0.feed_events
[]
.. Create first privisional feed inventory for silo S1 and silo's locations to fed
.. with 1000.00 Kg at 5 days before::
..
.. >>> FeedProvisionalInventory = Model.get('farm.feed.provisional_inventory')
.. >>> feed_provisional_inventory1 = FeedProvisionalInventory(
.. ... location=silo1,
.. ... timestamp=(now - datetime.timedelta(days=5)),
.. ... quantity=Decimal('1000.00'),
.. ... uom=kg,
.. ... )
.. >>> feed_provisional_inventory1.save()
.. >>> feed_provisional_inventory1.state
.. u'draft'
..
.. Confirm first provisional feed inventory and check it has an stock inventory in
.. state 'Done' and the median of Consumed Quantity per Animal/Day is
.. approximately 50 Kg::
..
.. >>> FeedProvisionalInventory.confirm([feed_provisional_inventory1.id],
.. ... config.context)
.. >>> feed_provisional_inventory1.reload()
.. >>> feed_provisional_inventory1.state
.. u'validated'
.. >>> (feed_provisional_inventory1.lines[0].consumed_qty_animal_day
.. ... - Decimal('50.0')) < Decimal('3.0')
.. True
.. >>> feed_provisional_inventory1.inventory.state
.. u'done'
..
.. Create second privisional feed inventory for silo S1 and silo's locations to
.. fed with 1100.00 Kg at 2 days before::
..
.. >>> feed_provisional_inventory2 = FeedProvisionalInventory(
.. ... location=silo1,
.. ... timestamp=(now - datetime.timedelta(days=2)),
.. ... quantity=Decimal('1100.00'),
.. ... uom=kg,
.. ... )
.. >>> feed_provisional_inventory2.save()
.. >>> feed_provisional_inventory2.state
.. u'draft'
..
.. Confirm second provisional feed inventory and check it has an stock inventory
.. state 'Done' and the median of Consumed Quantity per Animal/Day is
.. approximately 50 Kg::
..
.. >>> FeedProvisionalInventory.confirm([feed_provisional_inventory2.id],
.. ... config.context)
.. >>> feed_provisional_inventory2.reload()
.. >>> feed_provisional_inventory2.state
.. u'validated'
.. >>> (feed_provisional_inventory2.lines[0].consumed_qty_animal_day
.. ... - Decimal('50.0')) < Decimal('3.0')
.. True
.. >>> feed_provisional_inventory2.inventory.state
.. u'done'
Create (real) feed inventory for silo S1 and silo's locations to fed with
200.00 Kg at today::
>>> feed_inventory1 = FeedInventory(
... location=silo1,
... timestamp=(now - datetime.timedelta(days=0)),
... quantity=Decimal('200.00'),
... uom=kg,
... )
>>> feed_inventory1.save()
>>> feed_inventory1.state
u'draft'
Confirm feed inventory. Check the current stock of Silo is 200.00 Kg and the
current lot is the second Feed Lot::
>>> FeedInventory.confirm([feed_inventory1.id], config.context)
>>> feed_inventory1.reload()
>>> feed_inventory1.state
u'validated'
>>> silo1.reload()
>>> silo1.current_lot.id == feed_lot2_id
True
>>> unused = config.set_context({'locations': [silo1.id]})
>>> (Decimal(Lot(feed_lot2_id).quantity).quantize(Decimal('0.01'))
... - Decimal('200.00')) < Decimal('0.01')
True

View File

@ -0,0 +1,441 @@
======================
Foster Events Scenario
======================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create specie's products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> cm3, = ProductUom.find([('name', '=', 'Cubic centimeter')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> female_template = ProductTemplate(
... name='Female Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> female_template.save()
>>> female_product = Product(template=female_template)
>>> female_product.save()
>>> group_template = ProductTemplate(
... name='Group of Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('30'),
... cost_price=Decimal('20'))
>>> group_template.save()
>>> group_product = Product(template=group_template)
>>> group_product.save()
>>> semen_template = ProductTemplate(
... name='Pig Semen',
... default_uom=cm3,
... type='goods',
... consumable=True,
... list_price=Decimal('400'),
... cost_price=Decimal('250'))
>>> semen_template.save()
>>> semen_product = Product(template=semen_template)
>>> semen_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> female_sequence = Sequence(
... name='Female Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> female_sequence.save()
>>> group_sequence = Sequence(
... name='Groups Pig Warehouse 1',
... code='farm.animal.group',
... padding=4)
>>> group_sequence.save()
Prepare locations::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> production_location = Location(
... name='Production Location',
... code='PROD',
... type='production',
... parent=warehouse)
>>> production_location.save()
>>> warehouse.production_location=production_location
>>> warehouse.save()
>>> warehouse.reload()
>>> production_location.reload()
Create specie::
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=False,
... female_enabled=True,
... female_product=female_product,
... semen_product=semen_product,
... individual_enabled=False,
... group_enabled=True,
... group_product=group_product,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... farm=warehouse,
... event_order_sequence=event_order_sequence,
... has_male=False,
... has_female=True,
... female_sequence=female_sequence,
... has_individual=False,
... has_group=True,
... group_sequence=group_sequence)
>>> pigs_farm_line.save()
Set animal_type and specie in context to work as in the menus::
>>> config._context['specie'] = pigs_specie.id
>>> config._context['animal_type'] = 'female'
Create two females to be inseminated, check their pregnancy state, farrow them
and do some foster events between them::
>>> Animal = Model.get('farm.animal')
>>> female1 = Animal(
... type='female',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=warehouse.storage_location)
>>> female1.save()
>>> female1.location.code
u'STO'
>>> female1.farm.code
u'WH'
>>> female1.current_cycle
>>> female1.state
u'prospective'
>>> female2 = Animal(
... type='female',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=warehouse.storage_location)
>>> female2.save()
>>> female2.location.code
u'STO'
>>> female2.farm.code
u'WH'
>>> female2.current_cycle
>>> female2.state
u'prospective'
Create insemination events for the females without dose BoM nor Lot and
validate them::
>>> InseminationEvent = Model.get('farm.insemination.event')
>>> now = datetime.datetime.now()
>>> inseminate_events = InseminationEvent.create([{
... 'animal_type': 'female',
... 'specie': pigs_specie.id,
... 'farm': warehouse.id,
... 'timestamp': now,
... 'animal': female1.id,
... }, {
... 'animal_type': 'female',
... 'specie': pigs_specie.id,
... 'farm': warehouse.id,
... 'timestamp': now,
... 'animal': female2.id,
... }], config.context)
>>> InseminationEvent.validate_event(inseminate_events, config.context)
>>> all(InseminationEvent(i).state == 'validated'
... for i in inseminate_events)
True
Check the females are mated::
>>> female1.reload()
>>> female1.state
u'mated'
>>> female1.current_cycle.state
u'mated'
>>> female2.reload()
>>> female2.state
u'mated'
>>> female2.current_cycle.state
u'mated'
Create pregnancy diagnosis events with positive result and validate them::
>>> PregnancyDiagnosisEvent = Model.get('farm.pregnancy_diagnosis.event')
>>> now = datetime.datetime.now()
>>> diagnosis_events = PregnancyDiagnosisEvent.create([{
... 'animal_type': 'female',
... 'specie': pigs_specie.id,
... 'farm': warehouse.id,
... 'timestamp': now,
... 'animal': female1.id,
... 'result': 'positive',
... }, {
... 'animal_type': 'female',
... 'specie': pigs_specie.id,
... 'farm': warehouse.id,
... 'timestamp': now,
... 'animal': female2.id,
... 'result': 'positive',
... }], config.context)
>>> PregnancyDiagnosisEvent.validate_event(diagnosis_events, config.context)
>>> all(PregnancyDiagnosisEvent(i).state == 'validated'
... for i in diagnosis_events)
True
Check females are pregnant::
>>> female1.reload()
>>> female1.current_cycle.state
u'pregnant'
>>> female1.current_cycle.pregnant
1
>>> female2.reload()
>>> female2.current_cycle.state
u'pregnant'
>>> female2.current_cycle.pregnant
1
Create a farrowing event for each female with 7 and 8 lives and validate them::
>>> FarrowingEvent = Model.get('farm.farrowing.event')
>>> now = datetime.datetime.now()
>>> farrow_events = FarrowingEvent.create([{
... 'animal_type': 'female',
... 'specie': pigs_specie.id,
... 'farm': warehouse.id,
... 'timestamp': now,
... 'animal': female1.id,
... 'live': 7,
... 'stillborn': 2,
... }, {
... 'animal_type': 'female',
... 'specie': pigs_specie.id,
... 'farm': warehouse.id,
... 'timestamp': now,
... 'animal': female2.id,
... 'live': 8,
... 'stillborn': 1,
... 'mummified': 2,
... }], config.context)
>>> FarrowingEvent.validate_event(farrow_events, config.context)
>>> all(FarrowingEvent(i).state == 'validated' for i in farrow_events)
True
Check females are not pregnant, their current cycle are in 'lactating' state,
they are 'mated' and check females functional fields values::
>>> female1.reload()
>>> female1.current_cycle.pregnant
0
>>> female1.current_cycle.state
u'lactating'
>>> female1.state
u'mated'
>>> female1.current_cycle.live
7
>>> female1.current_cycle.dead
2
>>> female2.reload()
>>> female2.current_cycle.pregnant
0
>>> female2.current_cycle.state
u'lactating'
>>> female2.state
u'mated'
>>> female2.current_cycle.live
8
>>> female2.current_cycle.dead
3
Create a foster event for first female with -1 quantity (foster out) and
without pair female::
>>> FosterEvent = Model.get('farm.foster.event')
>>> now = datetime.datetime.now()
>>> foster_event1 = FosterEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female1,
... quantity=-1)
>>> foster_event1.save()
Validate foster event::
>>> FosterEvent.validate_event([foster_event1.id], config.context)
>>> foster_event1.reload()
>>> foster_event1.state
u'validated'
Check female's current cycle is still 'lactating', it has 1 foster event and
it's fostered value is -1::
>>> female1.reload()
>>> female1.current_cycle.pregnant
False
>>> female1.current_cycle.state
u'lactating'
>>> len(female1.current_cycle.foster_events)
1
>>> female1.current_cycle.fostered
-1
Create a foster event for second female with +2 quantity (foster in) and
without pair female::
>>> foster_event2 = FosterEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female2,
... quantity=2)
>>> foster_event2.save()
Validate foster event::
>>> FosterEvent.validate_event([foster_event2.id], config.context)
>>> foster_event2.reload()
>>> foster_event2.state
u'validated'
Check female's current cycle is still 'lactating', it has 1 foster event and
it's fostered value is 2::
>>> female2.reload()
>>> female2.current_cycle.pregnant
False
>>> female2.current_cycle.state
u'lactating'
>>> len(female2.current_cycle.foster_events)
1
>>> female2.current_cycle.fostered
2
Create a foster event for first female with +4 quantity (foster in) and
with the second female as pair female::
>>> now = datetime.datetime.now()
>>> foster_event3= FosterEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female1,
... quantity=4,
... pair_female=female2)
>>> foster_event3.save()
Validate foster event::
>>> FosterEvent.validate_event([foster_event3.id], config.context)
>>> foster_event3.reload()
>>> foster_event3.state
u'validated'
Check foster event has Pair female foster event and it is validated:
>>> foster_event3.pair_event != False
True
>>> foster_event3.pair_event.state
u'validated'
Check the current cycle of the both females are still 'lactating', they has 2
foster events and their fostered value is +3 and -2 respectively::
>>> female1.reload()
>>> female2.reload()
>>> any(f.current_cycle.pregnant for f in [female1, female2])
False
>>> all(f.current_cycle.state == 'lactating' for f in [female1, female2])
True
>>> len(female1.current_cycle.foster_events)
2
>>> female1.current_cycle.fostered
3
>>> len(female2.current_cycle.foster_events)
2
>>> female2.current_cycle.fostered
-2

View File

@ -0,0 +1,369 @@
============================
Insemination Events Scenario
============================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create specie's products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> cm3, = ProductUom.find([('name', '=', 'Cubic centimeter')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> female_template = ProductTemplate(
... name='Female Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> female_template.save()
>>> female_product = Product(template=female_template)
>>> female_product.save()
>>> semen_template = ProductTemplate(
... name='Pig Semen',
... default_uom=cm3,
... type='goods',
... list_price=Decimal('400'),
... cost_price=Decimal('250'))
>>> semen_template.save()
>>> semen_product = Product(template=semen_template)
>>> semen_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> female_sequence = Sequence(
... name='Female Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> female_sequence.save()
Prepare locations::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> production_location = Location(
... name='Production Location',
... code='PROD',
... type='production',
... parent=warehouse)
>>> production_location.save()
>>> warehouse.production_location=production_location
>>> warehouse.save()
>>> warehouse.reload()
>>> production_location.reload()
Create specie::
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=False,
... female_enabled=True,
... female_product=female_product,
... semen_product=semen_product,
... individual_enabled=False,
... group_enabled=False,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... farm=warehouse,
... event_order_sequence=event_order_sequence,
... has_male=False,
... has_female=True,
... female_sequence=female_sequence,
... has_individual=False,
... has_group=False)
>>> pigs_farm_line.save()
Create dose Product, BoM and Lot::
>>> blister_template = ProductTemplate(
... name='100 cm3 blister',
... default_uom=unit,
... type='goods',
... consumable=True,
... list_price=Decimal('1'),
... cost_price=Decimal('1'))
>>> blister_template.save()
>>> blister_product = Product(template=blister_template)
>>> blister_product.save()
>>> dose_template = ProductTemplate(
... name='100 cm3 semen dose',
... default_uom=unit,
... type='goods',
... list_price=Decimal('10'),
... cost_price=Decimal('8'))
>>> dose_template.save()
>>> dose_product = Product(template=dose_template)
>>> dose_product.save()
>>> Bom = Model.get('production.bom')
>>> BomInput = Model.get('production.bom.input')
>>> BomOutput = Model.get('production.bom.output')
>>> dose_bom = Bom(
... name='100 cm3 semen dose',
... semen_dose=True,
... specie=pigs_specie.id,
... inputs=[
... BomInput(
... product=blister_product,
... uom=unit,
... quantity=1),
... BomInput(
... product=semen_product,
... uom=cm3,
... quantity=100.00),
... ],
... outputs=[
... BomOutput(
... product=dose_product,
... uom=unit,
... quantity=1.00),
... ],
... )
>>> dose_bom.save()
>>> dose_bom.reload()
>>> ProductBom = Model.get('product.product-production.bom')
>>> dose_product.boms.append(ProductBom(
... bom=dose_bom,
... sequence=1))
>>> dose_product.save()
>>> dose_product.reload()
>>> Lot = Model.get('stock.lot')
>>> dose_lot = Lot(
... number='S001',
... product=dose_product)
>>> dose_lot.save()
Put two units of dose and one of semen in farm storage location::
>>> Move = Model.get('stock.move')
>>> now = datetime.datetime.now()
>>> provisioning_moves_vals = [{
... 'product': dose_product.id,
... 'uom': unit.id,
... 'quantity': 2.0,
... 'from_location': production_location.id,
... 'to_location': warehouse.storage_location.id,
... 'planned_date': now.date(),
... 'effective_date': now.date(),
... 'company': config.context.get('company'),
... 'lot': dose_lot.id,
... 'unit_price': dose_product.template.list_price,
... }, {
... 'product': semen_product.id,
... 'uom': cm3.id,
... 'quantity': 1.0,
... 'from_location': production_location.id,
... 'to_location': warehouse.storage_location.id,
... 'planned_date': now.date(),
... 'effective_date': now.date(),
... 'company': config.context.get('company'),
... 'unit_price': semen_product.template.list_price,
... }]
>>> provisioning_moves = Move.create(provisioning_moves_vals,
... config.context)
>>> Move.assign(provisioning_moves, config.context)
>>> Move.do(provisioning_moves, config.context)
Set animal_type and specie in context to work as in the menus::
>>> config._context['specie'] = pigs_specie.id
>>> config._context['animal_type'] = 'female'
Create first female to be inseminated::
>>> Animal = Model.get('farm.animal')
>>> female1 = Animal(
... type='female',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=warehouse.storage_location)
>>> female1.save()
>>> female1.location.code
u'STO'
>>> female1.farm.code
u'WH'
>>> female1.current_cycle
>>> female1.state
u'prospective'
Create insemination event with dose BoM and Lot::
>>> InseminationEvent = Model.get('farm.insemination.event')
>>> now = datetime.datetime.now()
>>> inseminate_female1 = InseminationEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female1,
... dose_bom=dose_bom,
... dose_lot=dose_lot)
>>> inseminate_female1.save()
Validate insemination event::
>>> InseminationEvent.validate_event([inseminate_female1.id],
... config.context)
>>> inseminate_female1.reload()
>>> inseminate_female1.state
u'validated'
>>> inseminate_female1.move.state
u'done'
Check female is mated::
>>> female1.reload()
>>> female1.state
u'mated'
>>> female1.current_cycle.state
u'mated'
Create insemination event with dose BoM but not Lot::
>>> now = datetime.datetime.now()
>>> inseminate_female12 = InseminationEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female1,
... dose_bom=dose_bom)
>>> inseminate_female12.save()
Validate insemination event::
>>> InseminationEvent.validate_event([inseminate_female12.id],
... config.context)
>>> inseminate_female12.reload()
>>> inseminate_female12.state
u'validated'
>>> inseminate_female12.move.state
u'done'
Check female is mated and has two insemination events::
>>> female1.reload()
>>> female1.state
u'mated'
>>> female1.current_cycle.state
u'mated'
>>> len(female1.current_cycle.insemination_events)
2
Create second female to be inseminated::
>>> female2 = Animal(
... type='female',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=warehouse.storage_location)
>>> female2.save()
>>> female2.location.code
u'STO'
>>> female2.farm.code
u'WH'
>>> female2.current_cycle
>>> female2.state
u'prospective'
Create insemination event without dose BoM nor Lot::
>>> now = datetime.datetime.now()
>>> inseminate_female2 = InseminationEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female2)
>>> inseminate_female2.save()
Validate insemination event::
>>> InseminationEvent.validate_event([inseminate_female2.id],
... config.context)
>>> inseminate_female2.reload()
>>> inseminate_female2.state
u'validated'
Check female is mated::
>>> female2.reload()
>>> female2.state
u'mated'
>>> female2.current_cycle.state
u'mated'

View File

@ -0,0 +1,294 @@
==========================
Medication Events Scenario
==========================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> individual_template = ProductTemplate(
... name='Male Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> individual_template.save()
>>> individual_product = Product(template=individual_template)
>>> individual_product.save()
>>> group_template = ProductTemplate(
... name='Group of Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('30'),
... cost_price=Decimal('20'))
>>> group_template.save()
>>> group_product = Product(template=group_template)
>>> group_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> individual_sequence = Sequence(
... name='Individual Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> individual_sequence.save()
>>> group_sequence = Sequence(
... name='Groups Pig Warehouse 1',
... code='farm.animal.group',
... padding=4)
>>> group_sequence.save()
Prepare farm locations::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> production_location = Location(
... name='Production Location',
... code='PROD',
... type='production',
... parent=warehouse)
>>> production_location.save()
>>> warehouse.production_location=production_location
>>> warehouse.save()
>>> warehouse.reload()
>>> production_location.reload()
>>> location1_id, location2_id = Location.create([{
... 'name': 'Location 1',
... 'code': 'L1',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }, {
... 'name': 'Location 2',
... 'code': 'L2',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }], config.context)
>>> lab1 = Location(
... name='Laboratory 1',
... code='Lab1',
... type='storage',
... parent=warehouse.storage_location)
>>> lab1.save()
Create specie::
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=False,
... female_enabled=False,
... individual_enabled=True,
... individual_product=individual_product,
... group_enabled=True,
... group_product=group_product,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... farm=warehouse,
... event_order_sequence=event_order_sequence,
... has_individual=True,
... individual_sequence=individual_sequence,
... has_group=True,
... group_sequence=group_sequence)
>>> pigs_farm_line.save()
Create Medication Product and Lot::
>>> ProductUom = Model.get('product.uom')
>>> g, = ProductUom.find([('name', '=', 'Gram')])
>>> medication_template = ProductTemplate(
... name='Pig Medication',
... default_uom=g,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> medication_template.save()
>>> medication_product = Product(template=medication_template)
>>> medication_product.save()
>>> Lot = Model.get('stock.lot')
>>> medication_lot = Lot(
... number='M001',
... product=medication_product)
>>> medication_lot.save()
Put 500 g of medication into the laboratory location::
>>> Move = Model.get('stock.move')
>>> now = datetime.datetime.now()
>>> provisioning_moves = Move.create([{
... 'product': medication_product.id,
... 'uom': g.id,
... 'quantity': 500,
... 'from_location': party.supplier_location.id,
... 'to_location': lab1.id,
... 'planned_date': now.date(),
... 'effective_date': now.date(),
... 'company': config.context.get('company'),
... 'lot': medication_lot.id,
... 'unit_price': medication_product.template.list_price,
... }],
... config.context)
>>> Move.assign(provisioning_moves, config.context)
>>> Move.do(provisioning_moves, config.context)
Set animal_type and specie in context to work as in the menus::
>>> config._context['specie'] = pigs_specie.id
>>> config._context['animal_type'] = 'individual'
Create individual::
>>> Animal = Model.get('farm.animal')
>>> individual = Animal(
... type='individual',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=location1_id)
>>> individual.save()
>>> individual.location.code
u'L1'
>>> individual.farm.code
u'WH'
Create individual medication event::
>>> MedicationEvent = Model.get('farm.medication.event')
>>> medication_individual = MedicationEvent(
... animal_type='individual',
... specie=pigs_specie,
... farm=warehouse,
... animal=individual,
... timestamp=now,
... location=individual.location,
... feed_location=lab1,
... feed_product=medication_product,
... feed_lot=medication_lot,
... uom=g,
... quantity=Decimal('154.0'))
>>> medication_individual.save()
Validate individual medication event::
>>> MedicationEvent.validate_event([medication_individual.id],
... config.context)
>>> medication_individual.reload()
>>> medication_individual.state
u'validated'
Create group::
>>> AnimalGroup = Model.get('farm.animal.group')
>>> animal_group = AnimalGroup(
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=location2_id,
... initial_quantity=4)
>>> animal_group.save()
Create animal_group medication event::
>>> medication_animal_group = MedicationEvent(
... animal_type='group',
... specie=pigs_specie,
... farm=warehouse,
... animal_group=animal_group,
... timestamp=now,
... location=location2_id,
... feed_location=lab1,
... feed_product=medication_product,
... feed_lot=medication_lot,
... uom=g,
... quantity=Decimal('320.0'),
... start_date=(now.date() - datetime.timedelta(days=1)),
... end_date=now)
>>> medication_animal_group.save()
Validate animal_group medication event::
>>> MedicationEvent.validate_event([medication_animal_group.id],
... config.context)
>>> medication_animal_group.reload()
>>> medication_animal_group.state
u'validated'
>>> animal_group.reload()
>>> unused = config.set_context({'locations': [lab1.id]})
>>> medication_lot.reload()
>>> medication_lot.quantity
26.0

View File

@ -0,0 +1,255 @@
====================
Move Events Scenario
====================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> individual_template = ProductTemplate(
... name='Male Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> individual_template.save()
>>> individual_product = Product(template=individual_template)
>>> individual_product.save()
>>> group_template = ProductTemplate(
... name='Group of Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('30'),
... cost_price=Decimal('20'))
>>> group_template.save()
>>> group_product = Product(template=group_template)
>>> group_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> individual_sequence = Sequence(
... name='Individual Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> individual_sequence.save()
>>> group_sequence = Sequence(
... name='Groups Pig Warehouse 1',
... code='farm.animal.group',
... padding=4)
>>> group_sequence.save()
Create specie::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=False,
... female_enabled=False,
... individual_enabled=True,
... individual_product=individual_product,
... group_enabled=True,
... group_product=group_product,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... farm=warehouse,
... event_order_sequence=event_order_sequence,
... has_individual=True,
... individual_sequence=individual_sequence,
... has_group=True,
... group_sequence=group_sequence)
>>> pigs_farm_line.save()
Create farm locations::
>>> location1_id, location2_id = Location.create([{
... 'name': 'Location 1',
... 'code': 'L1',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }, {
... 'name': 'Location 2',
... 'code': 'L2',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }], config.context)
Create individual::
>>> Animal = Model.get('farm.animal')
>>> individual = Animal(
... type='individual',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=location1_id)
>>> individual.save()
>>> individual.location.code
u'L1'
>>> individual.farm.code
u'WH'
Create individual move event::
>>> MoveEvent = Model.get('farm.move.event')
>>> move_individual = MoveEvent(
... animal_type='individual',
... specie=pigs_specie,
... farm=warehouse,
... animal=individual,
... timestamp=now,
... from_location=individual.location,
... to_location=location2_id,
... weight=Decimal('80.50'))
>>> move_individual.save()
Animal doesn't chage its values::
>>> individual.reload()
>>> individual.location.id == location1_id
True
>>> individual.current_weight
Validate individual move event::
>>> MoveEvent.validate_event([move_individual.id], config.context)
>>> move_individual.reload()
>>> move_individual.state
u'validated'
>>> individual.reload()
>>> individual.location.id == location2_id
True
>>> individual.current_weight.weight
Decimal('80.50')
Create group::
>>> AnimalGroup = Model.get('farm.animal.group')
>>> animal_group = AnimalGroup(
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=location2_id,
... initial_quantity=4)
>>> animal_group.save()
>>> unused = config.set_context({
... 'locations': [location2_id]})
>>> animal_group.reload()
>>> animal_group.lot.quantity
4.0
Create animal_group move event::
>>> MoveEvent = Model.get('farm.move.event')
>>> move_animal_group = MoveEvent(
... animal_type='group',
... specie=pigs_specie,
... farm=warehouse,
... animal_group=animal_group,
... timestamp=now,
... from_location=location2_id,
... to_location=location1_id,
... quantity=3,
... weight=Decimal('80.50'))
>>> move_animal_group.save()
Group doesn't chage its values::
>>> animal_group.reload()
>>> animal_group.current_weight
>>> animal_group.lot.quantity
4.0
Validate animal_group move event::
>>> MoveEvent.validate_event([move_animal_group.id], config.context)
>>> move_animal_group.reload()
>>> move_animal_group.state
u'validated'
>>> animal_group.reload()
>>> animal_group.current_weight.weight
Decimal('80.50')
>>> animal_group.lot.quantity
1.0
>>> unused = config.set_context({'locations': [location1_id]})
>>> animal_group.reload()
>>> animal_group.lot.quantity
3.0
>>> unused = config.set_context({'locations': None})

View File

@ -0,0 +1,395 @@
===================================
Pregnancy Diagnosis Events Scenario
===================================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create specie's products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> cm3, = ProductUom.find([('name', '=', 'Cubic centimeter')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> female_template = ProductTemplate(
... name='Female Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> female_template.save()
>>> female_product = Product(template=female_template)
>>> female_product.save()
>>> semen_template = ProductTemplate(
... name='Pig Semen',
... default_uom=cm3,
... type='goods',
... consumable=True,
... list_price=Decimal('400'),
... cost_price=Decimal('250'))
>>> semen_template.save()
>>> semen_product = Product(template=semen_template)
>>> semen_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> female_sequence = Sequence(
... name='Female Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> female_sequence.save()
Prepare locations::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> production_location = Location(
... name='Production Location',
... code='PROD',
... type='production',
... parent=warehouse)
>>> production_location.save()
>>> warehouse.production_location=production_location
>>> warehouse.save()
>>> warehouse.reload()
>>> production_location.reload()
Create specie::
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=False,
... female_enabled=True,
... female_product=female_product,
... semen_product=semen_product,
... individual_enabled=False,
... group_enabled=False,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... farm=warehouse,
... event_order_sequence=event_order_sequence,
... has_male=False,
... has_female=True,
... female_sequence=female_sequence,
... has_individual=False,
... has_group=False)
>>> pigs_farm_line.save()
Set animal_type and specie in context to work as in the menus::
>>> config._context['specie'] = pigs_specie.id
>>> config._context['animal_type'] = 'female'
Create female to be inseminated and check it's pregnancy state and restart the
cycle::
>>> Animal = Model.get('farm.animal')
>>> female = Animal(
... type='female',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=warehouse.storage_location)
>>> female.save()
>>> female.location.code
u'STO'
>>> female.farm.code
u'WH'
>>> female.current_cycle
>>> female.state
u'prospective'
Create insemination event without dose BoM nor Lot and validate it::
>>> InseminationEvent = Model.get('farm.insemination.event')
>>> now = datetime.datetime.now()
>>> inseminate_female = InseminationEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female)
>>> inseminate_female.save()
>>> inseminate_female.dose_lot
>>> InseminationEvent.validate_event([inseminate_female.id],
... config.context)
>>> inseminate_female.reload()
>>> inseminate_female.state
u'validated'
Check female is mated::
>>> female.reload()
>>> female.current_cycle.state
u'mated'
>>> female.state
u'mated'
Create pregnancy diagnosis event with negative result::
>>> PregnancyDiagnosisEvent = Model.get('farm.pregnancy_diagnosis.event')
>>> now = datetime.datetime.now()
>>> diagnose_female1 = PregnancyDiagnosisEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female,
... result='negative')
>>> diagnose_female1.save()
Validate pregnancy diagnosis event::
>>> PregnancyDiagnosisEvent.validate_event([diagnose_female1.id],
... config.context)
>>> diagnose_female1.reload()
>>> diagnose_female1.state
u'validated'
Check female is not pregnant, it is mated and has one pregnancy diagnosis
event::
>>> female.reload()
>>> female.current_cycle.pregnant
0
>>> female.current_cycle.state
u'mated'
>>> female.state
u'mated'
>>> len(female.current_cycle.diagnosis_events)
1
Create pregnancy diagnosis event with positive result::
>>> now = datetime.datetime.now()
>>> diagnose_female2 = PregnancyDiagnosisEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female,
... result='positive')
>>> diagnose_female2.save()
Validate pregnancy diagnosis event::
>>> PregnancyDiagnosisEvent.validate_event([diagnose_female2.id],
... config.context)
>>> diagnose_female2.reload()
>>> diagnose_female2.state
u'validated'
Check female is pregnant, it is mated and has two pregnancy diagnosis events::
>>> female.reload()
>>> female.state
u'mated'
>>> female.current_cycle.state
u'pregnant'
>>> female.current_cycle.pregnant
1
>>> len(female.current_cycle.diagnosis_events)
2
Create pregnancy diagnosis event with nonconclusive result::
>>> now = datetime.datetime.now()
>>> diagnose_female3 = PregnancyDiagnosisEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female,
... result='nonconclusive')
>>> diagnose_female3.save()
Validate pregnancy diagnosis event::
>>> PregnancyDiagnosisEvent.validate_event([diagnose_female3.id],
... config.context)
>>> diagnose_female3.reload()
>>> diagnose_female3.state
u'validated'
Check female is not pregnant, it is mated and has three pregnancy diagnosis
events::
>>> female.reload()
>>> female.state
u'mated'
>>> female.current_cycle.state
u'mated'
>>> female.current_cycle.pregnant
0
>>> len(female.current_cycle.diagnosis_events)
3
Create pregnancy diagnosis event with positive result::
>>> now = datetime.datetime.now()
>>> diagnose_female4 = PregnancyDiagnosisEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female,
... result='positive')
>>> diagnose_female4.save()
Validate pregnancy diagnosis event::
>>> PregnancyDiagnosisEvent.validate_event([diagnose_female4.id],
... config.context)
>>> diagnose_female4.reload()
>>> diagnose_female4.state
u'validated'
Check female is pregnant, it is mated and has four pregnancy diagnosis events::
>>> female.reload()
>>> female.state
u'mated'
>>> female.current_cycle.state
u'pregnant'
>>> female.current_cycle.pregnant
1
>>> len(female.current_cycle.diagnosis_events)
4
Create pregnancy diagnosis event with not-pregnant result::
>>> now = datetime.datetime.now()
>>> diagnose_female5 = PregnancyDiagnosisEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female,
... result='not-pregnant')
>>> diagnose_female5.save()
Validate pregnancy diagnosis event::
>>> PregnancyDiagnosisEvent.validate_event([diagnose_female5.id],
... config.context)
>>> diagnose_female5.reload()
>>> diagnose_female5.state
u'validated'
Check female is not pregnant, it is mated and has five pregnancy diagnosis
events::
>>> female.reload()
>>> female.state
u'mated'
>>> female.current_cycle.state
u'mated'
>>> female.current_cycle.pregnant
0
>>> len(female.current_cycle.diagnosis_events)
5
Create second insemination event without dose BoM nor Lot and validate it::
>>> now = datetime.datetime.now()
>>> inseminate_female2 = InseminationEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female)
>>> inseminate_female2.save()
>>> InseminationEvent.validate_event([inseminate_female2.id],
... config.context)
>>> inseminate_female2.reload()
>>> inseminate_female2.state
u'validated'
Check female has two cycles but both with the same sequence, it and the both of
its cycles are mated::
>>> female.reload()
>>> len(female.cycles)
2
>>> female.cycles[0].sequence == female.cycles[1].sequence
1
>>> female.state
u'mated'
>>> female.current_cycle.state
u'mated'
>>> all([c.state == 'mated' for c in female.cycles])
1

View File

@ -0,0 +1,203 @@
=======================
Removal Events Scenario
=======================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create product::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> liter, = ProductUom.find([('name', '=', 'Liter')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> male_template = ProductTemplate(
... name='Male Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> male_template.save()
>>> male_product = Product(template=male_template)
>>> male_product.save()
>>> semen_template = ProductTemplate(
... name='Pig Semen',
... default_uom=liter,
... type='goods',
... list_price=Decimal('400'),
... cost_price=Decimal('250'))
>>> semen_template.save()
>>> semen_product = Product(template=semen_template)
>>> semen_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> male_sequence = Sequence(
... name='Male Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> male_sequence.save()
>>> semen_lot_sequence = Sequence(
... name='Semen Extracted Lot Pig Warehouse 1',
... code='stock.lot',
... padding=4)
>>> semen_lot_sequence.save()
>>> semen_dose_lot_sequence = Sequence(
... name='Semen Dose Lot Pig Warehouse 1',
... code='stock.lot',
... padding=4)
>>> semen_dose_lot_sequence.save()
Create specie::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=True,
... male_product=male_product,
... semen_product=semen_product,
... female_enabled=False,
... individual_enabled=False,
... group_enabled=False,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... farm=warehouse,
... event_order_sequence=event_order_sequence,
... has_male=True,
... male_sequence=male_sequence,
... semen_lot_sequence=semen_lot_sequence,
... dose_lot_sequence=semen_dose_lot_sequence)
>>> pigs_farm_line.save()
Create male::
>>> Animal = Model.get('farm.animal')
>>> male = Animal(
... type='male',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=warehouse.storage_location)
>>> male.save()
>>> male.location.code
u'STO'
>>> male.farm.code
u'WH'
Create removal event::
>>> RemovalType = Model.get('farm.removal.type')
>>> removal_type = RemovalType.find([], limit=1)[0]
>>> RemovalReason = Model.get('farm.removal.reason')
>>> removal_reason = RemovalReason.find([], limit=1)[0]
>>> RemovalEvent = Model.get('farm.removal.event')
>>> remove_male = RemovalEvent(
... animal_type='male',
... specie=pigs_specie,
... farm=warehouse,
... animal=male,
... timestamp=now,
... from_location=male.location,
... removal_type=removal_type,
... reason=removal_reason)
>>> remove_male.save()
Animal doesn't chage its values::
>>> male.reload()
>>> male.removal_date
>>> male.removal_reason
>>> male.active
1
Validate removal event::
>>> RemovalEvent.validate_event([remove_male.id], config.context)
>>> remove_male.reload()
>>> remove_male.state
u'validated'
>>> male.reload()
>>> male.removal_date == today
True
>>> male.removal_reason == removal_reason
True
>>> male.location == male.specie.removed_location
True
.. >>> male.active
.. 0

View File

@ -0,0 +1,378 @@
================================
Semen Extraction Events Scenario
================================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create specie's products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> cm3, = ProductUom.find([('name', '=', 'Cubic centimeter')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> male_template = ProductTemplate(
... name='Male Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> male_template.save()
>>> male_product = Product(template=male_template)
>>> male_product.save()
>>> semen_template = ProductTemplate(
... name='Pig Semen',
... default_uom=cm3,
... type='goods',
... list_price=Decimal('400'),
... cost_price=Decimal('250'))
>>> semen_template.save()
>>> semen_product = Product(template=semen_template)
>>> semen_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> male_sequence = Sequence(
... name='Male Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> male_sequence.save()
>>> semen_lot_sequence = Sequence(
... name='Semen Extracted Lot Pig Warehouse 1',
... code='stock.lot',
... padding=4)
>>> semen_lot_sequence.save()
>>> semen_dose_lot_sequence = Sequence(
... name='Semen Dose Lot Pig Warehouse 1',
... code='stock.lot',
... padding=4)
>>> semen_dose_lot_sequence.save()
Prepare locations::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> production_location = Location(
... name='Production Location',
... code='PROD',
... type='production',
... parent=warehouse)
>>> production_location.save()
>>> warehouse.production_location=production_location
>>> warehouse.save()
>>> warehouse.reload()
>>> production_location.reload()
>>> location1 = Location(
... name='Location 1',
... code='L1',
... type='storage',
... parent=warehouse.storage_location)
>>> location1.save()
>>> lab1 = Location(
... name='Laboratory 1',
... code='Lab1',
... type='storage',
... parent=warehouse.storage_location)
>>> lab1.save()
Create Quality Configuration and Semen Quality Test Template::
>>> quality_sequence, = Sequence.find([('code','=','quality.test')])
>>> Model_ = Model.get('ir.model')
>>> product_model, = Model_.find([('model','=','product.product')])
>>> lot_model, = Model_.find([('model','=','stock.lot')])
>>> QualityConfiguration = Model.get('quality.configuration')
>>> QualityConfigLine = Model.get('quality.configuration.line')
>>> QualityConfiguration(
... allowed_documents=[
... QualityConfigLine(
... quality_sequence=quality_sequence,
... document=product_model),
... QualityConfigLine(
... quality_sequence=quality_sequence,
... document=lot_model),
... ]).save()
>>> QualityTemplate = Model.get('quality.template')
>>> quality_template = QualityTemplate(
... name='Semen Quality Template',
... document=semen_product,
... formula='1.5',
... unit=cm3)
>>> quality_template.save()
Create specie::
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=True,
... male_product=male_product,
... female_enabled=False,
... semen_product=semen_product,
... individual_enabled=False,
... group_enabled=False,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... farm=warehouse,
... event_order_sequence=event_order_sequence,
... has_male=True,
... male_sequence=male_sequence,
... semen_lot_sequence=semen_lot_sequence,
... dose_lot_sequence=semen_dose_lot_sequence,
... has_female=False,
... has_individual=False,
... has_group=False)
>>> pigs_farm_line.save()
Create dose Product and BoM::
>>> blister_template = ProductTemplate(
... name='100 cm3 blister',
... default_uom=unit,
... type='goods',
... consumable=True,
... list_price=Decimal('1'),
... cost_price=Decimal('1'))
>>> blister_template.save()
>>> blister_product = Product(template=blister_template)
>>> blister_product.save()
>>> dose_template = ProductTemplate(
... name='100 cm3 semen dose',
... default_uom=unit,
... type='goods',
... list_price=Decimal('10'),
... cost_price=Decimal('8'))
>>> dose_template.save()
>>> dose_product = Product(template=dose_template)
>>> dose_product.save()
>>> Bom = Model.get('production.bom')
>>> BomInput = Model.get('production.bom.input')
>>> BomOutput = Model.get('production.bom.output')
>>> dose_bom = Bom(
... name='100 cm3 semen dose',
... semen_dose=True,
... specie=pigs_specie.id,
... inputs=[
... BomInput(
... product=blister_product,
... uom=unit,
... quantity=1),
... BomInput(
... product=semen_product,
... uom=cm3,
... quantity=100.00),
... ],
... outputs=[
... BomOutput(
... product=dose_product,
... uom=unit,
... quantity=1),
... ],
... )
>>> dose_bom.save()
>>> dose_bom.reload()
>>> ProductBom = Model.get('product.product-production.bom')
>>> dose_product.boms.append(ProductBom(
... bom=dose_bom,
... sequence=1))
>>> dose_product.save()
>>> dose_product.reload()
Set animal_type and specie in context to work as in the menus::
>>> config._context['specie'] = pigs_specie.id
>>> config._context['animal_type'] = 'male'
Create a male::
>>> Animal = Model.get('farm.animal')
>>> male1 = Animal(
... type='male',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=location1)
>>> male1.save()
>>> male1.location.code
u'L1'
>>> male1.farm.code
u'WH'
Create semen extraction event::
>>> SemenExtractionEvent = Model.get('farm.semen_extraction.event')
>>> now = datetime.datetime.now()
>>> extraction1 = SemenExtractionEvent(
... animal_type='male',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=male1,
... untreated_semen_uom=cm3,
... untreated_semen_qty=Decimal('410.0'),
... dose_location=lab1,
... dose_bom=dose_bom)
>>> extraction1.save()
Check test is created and functional fields::
>>> extraction1.test is not None
True
>>> extraction1.test.unit.name
u'Cubic centimeter'
>>> extraction1.formula_result
1.5
>>> extraction1.semen_calculated_qty
615.0
>>> extraction1.solvent_calculated_qty
205.0
Set real semen produced quantity and check calculated doses::
>>> extraction1.semen_qty = Decimal('610.0')
>>> extraction1.save()
>>> extraction1.reload()
>>> extraction1.dose_calculated_units
6.1
Add produced doses and deliveries::
>>> Dose = Model.get('farm.semen_extraction.dose')
>>> dose1 = Dose(
... event=extraction1,
... sequence=1,
... bom=dose_bom,
... quantity=6)
>>> dose1.save()
>>> Delivery = Model.get('farm.semen_extraction.delivery')
>>> delivery1 = Delivery(
... event=extraction1,
... dose=dose1,
... quantity=3,
... to_location=warehouse.storage_location)
>>> delivery1.save()
>>> delivery2 = Delivery(
... event=extraction1,
... dose=dose1,
... quantity=2,
... to_location=warehouse.output_location)
>>> delivery2.save()
>>> extraction1.reload()
Check dose and deliveries functional fields::
>>> extraction1.doses_semen_qty
600.0
>>> extraction1.semen_remaining_qty
10.0
>>> extraction1.dose_remaining_units
1
Create a draft shipment to check it is used in deliveries::
>>> Move = Model.get('stock.move')
>>> Shipment = Model.get('stock.shipment.internal')
>>> previous_shipment = Shipment(
... from_location=lab1,
... to_location=warehouse.storage_location,
... planned_date=(now - datetime.timedelta(days=1)),
... moves=[
... Move(
... product=blister_product,
... quantity=5,
... unit_price=blister_product.template.cost_price),
... ])
... previous_shipment.save()
Validate semen extraction event::
>>> SemenExtractionEvent.validate_event([extraction1.id], config.context)
>>> extraction1.reload()
>>> extraction1.state
u'validated'
>>> extraction1.semen_lot is not None
True
>>> extraction1.doses[0].production.state
u'done'
>>> extraction1.doses[0].lot is not None
True
>>> extraction1.deliveries[0].move.shipment.id != previous_shipment.id
True
>>> extraction1.deliveries[1].move.shipment.id != previous_shipment.id
True
>>> extraction1.animal.reload()
>>> extraction1.animal.last_extraction == now.date()
True

View File

@ -0,0 +1,448 @@
==============================
Transformation Events Scenario
==============================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> liter, = ProductUom.find([('name', '=', 'Liter')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> male_template = ProductTemplate(
... name='Male Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> male_template.save()
>>> male_product = Product(template=male_template)
>>> male_product.save()
>>> semen_template = ProductTemplate(
... name='Pig Semen',
... default_uom=liter,
... type='goods',
... list_price=Decimal('400'),
... cost_price=Decimal('250'))
>>> semen_template.save()
>>> semen_product = Product(template=semen_template)
>>> semen_product.save()
>>> female_template = ProductTemplate(
... name='Female Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> female_template.save()
>>> female_product = Product(template=female_template)
>>> female_product.save()
>>> individual_template = ProductTemplate(
... name='Male Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> individual_template.save()
>>> individual_product = Product(template=individual_template)
>>> individual_product.save()
>>> group_template = ProductTemplate(
... name='Group of Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('30'),
... cost_price=Decimal('20'))
>>> group_template.save()
>>> group_product = Product(template=group_template)
>>> group_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> male_sequence = Sequence(
... name='Male Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> male_sequence.save()
>>> semen_lot_sequence = Sequence(
... name='Semen Extracted Lot Pig Warehouse 1',
... code='stock.lot',
... padding=4)
>>> semen_lot_sequence.save()
>>> semen_dose_lot_sequence = Sequence(
... name='Semen Dose Lot Pig Warehouse 1',
... code='stock.lot',
... padding=4)
>>> semen_dose_lot_sequence.save()
>>> female_sequence = Sequence(
... name='Female Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> female_sequence.save()
>>> individual_sequence = Sequence(
... name='Individual Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> individual_sequence.save()
>>> group_sequence = Sequence(
... name='Groups Pig Warehouse 1',
... code='farm.animal.group',
... padding=4)
>>> group_sequence.save()
Prepare locations::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> production_location = Location(
... name='Production Location',
... code='PROD',
... type='production',
... parent=warehouse)
>>> production_location.save()
>>> warehouse.production_location=production_location
>>> warehouse.save()
>>> warehouse.reload()
>>> production_location.reload()
Create specie::
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=True,
... male_product=male_product,
... semen_product=semen_product,
... female_enabled=True,
... female_product=female_product,
... individual_enabled=True,
... individual_product=individual_product,
... group_enabled=True,
... group_product=group_product,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... farm=warehouse,
... event_order_sequence=event_order_sequence,
... has_male=True,
... male_sequence=male_sequence,
... semen_lot_sequence=semen_lot_sequence,
... dose_lot_sequence=semen_dose_lot_sequence,
... has_female=True,
... female_sequence=female_sequence,
... has_individual=True,
... individual_sequence=individual_sequence,
... has_group=True,
... group_sequence=group_sequence)
>>> pigs_farm_line.save()
Create male to be transformed to individual::
>>> Animal = Model.get('farm.animal')
>>> male_to_individual = Animal(
... type='male',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=warehouse.storage_location)
>>> male_to_individual.save()
>>> male_to_individual.location.code
u'STO'
>>> male_to_individual.farm.code
u'WH'
Create transformation event::
>>> TransformationEvent = Model.get('farm.transformation.event')
>>> transform_male_to_individual = TransformationEvent(
... animal_type='male',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=male_to_individual,
... from_location=male_to_individual.location,
... to_animal_type='individual',
... to_location=warehouse.storage_location)
>>> transform_male_to_individual.save()
Animal doesn't chage its values::
>>> male_to_individual.reload()
>>> male_to_individual.location=warehouse.storage_location
>>> male_to_individual.active
1
Validate transformation event::
>>> TransformationEvent.validate_event([transform_male_to_individual.id],
... config.context)
>>> transform_male_to_individual.reload()
>>> transform_male_to_individual.state
u'validated'
>>> to_animal = transform_male_to_individual.to_animal
>>> to_animal.active
1
>>> to_animal.type
u'individual'
>>> to_animal.location == transform_male_to_individual.to_location
True
>>> male_to_individual.reload()
>>> male_to_individual.removal_date == today
True
>>> male_to_individual.location == warehouse.production_location
True
.. >>> male_to_individual.active
.. 0
Create female to be transformed to a new group::
>>> female_to_group = Animal(
... type='female',
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=warehouse.storage_location)
>>> female_to_group.save()
>>> female_to_group.location.code
u'STO'
>>> female_to_group.farm.code
u'WH'
Create transformation event::
>>> transform_female_to_group = TransformationEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female_to_group,
... from_location=female_to_group.location,
... to_animal_type='group',
... to_location=warehouse.storage_location)
>>> transform_female_to_group.save()
Animal doesn't chage its values::
>>> female_to_group.reload()
>>> female_to_group.location=warehouse.storage_location
>>> female_to_group.active
1
Validate transformation event::
>>> TransformationEvent.validate_event([transform_female_to_group.id],
... config.context)
>>> transform_female_to_group.reload()
>>> transform_female_to_group.state
u'validated'
>>> to_group = transform_female_to_group.to_animal_group
>>> to_group.active
1
>>> to_animal.initial_location == transform_female_to_group.to_location
True
>>> female_to_group.reload()
>>> female_to_group.removal_date == today
True
>>> female_to_group.location == warehouse.production_location
True
.. >>> female_to_group.active
.. 0
.. TODO maybe more test over group: quantitites, locations...
Create individual to be transformed to female::
>>> individual_to_female = Animal(
... type='individual',
... specie=pigs_specie,
... breed=pigs_breed,
... sex='female',
... initial_location=warehouse.storage_location)
>>> individual_to_female.save()
>>> individual_to_female.location.code
u'STO'
>>> individual_to_female.farm.code
u'WH'
Create transformation event::
>>> transform_individual_to_female = TransformationEvent(
... animal_type='individual',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=individual_to_female,
... from_location=individual_to_female.location,
... to_animal_type='female',
... to_location=warehouse.storage_location)
>>> transform_individual_to_female.save()
Animal doesn't chage its values::
>>> individual_to_female.reload()
>>> individual_to_female.location=warehouse.storage_location
>>> individual_to_female.active
1
Validate transformation event::
>>> TransformationEvent.validate_event([transform_individual_to_female.id],
... config.context)
>>> transform_individual_to_female.reload()
>>> transform_individual_to_female.state
u'validated'
>>> to_animal = transform_individual_to_female.to_animal
>>> to_animal.active
1
>>> to_animal.type
u'female'
>>> to_animal.location == transform_individual_to_female.to_location
True
>>> individual_to_female.reload()
>>> individual_to_female.removal_date == today
True
>>> individual_to_female.location == warehouse.production_location
True
.. >>> individual_to_female.active
.. 0
Create individual to be transformed to existing group::
>>> individual_to_group = Animal(
... type='individual',
... specie=pigs_specie,
... breed=pigs_breed,
... sex='undetermined',
... initial_location=warehouse.storage_location)
>>> individual_to_group.save()
>>> individual_to_group.location.code
u'STO'
>>> individual_to_group.farm.code
u'WH'
Create existing group::
>>> AnimalGroup = Model.get('farm.animal.group')
>>> context_tmp = config.context.copy()
>>> config._context.update({
... 'animal_type': 'group',
... })
>>> existing_group = AnimalGroup(
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=warehouse.storage_location,
... initial_quantity=4,
... arrival_date=(today - relativedelta(days=3)),
... )
>>> existing_group.save()
>>> config._context = context_tmp
Create transformation event::
>>> transform_individual_to_group = TransformationEvent(
... animal_type='individual',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=individual_to_group,
... from_location=individual_to_group.location,
... to_animal_type='group',
... to_location=warehouse.storage_location,
... to_animal_group=existing_group)
>>> transform_individual_to_group.save()
Validate transformation event::
>>> TransformationEvent.validate_event([transform_individual_to_group.id],
... config.context)
>>> transform_individual_to_group.reload()
>>> transform_individual_to_group.state
u'validated'
>>> individual_to_group.reload()
>>> individual_to_group.removal_date == today
True
>>> individual_to_group.location == warehouse.production_location
True
.. >>> individual_to_group.active
.. 0
>>> existing_group.reload()
>>> existing_group.active
1
.. TODO maybe more test over group: quantitites, locations...

View File

@ -0,0 +1,466 @@
=======================
Weaning Events Scenario
=======================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> now = datetime.datetime.now()
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install farm::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', '=', 'farm'),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='NaN·tic')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'EUR')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=now.date() + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create specie's products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> cm3, = ProductUom.find([('name', '=', 'Cubic centimeter')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> female_template = ProductTemplate(
... name='Female Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('40'),
... cost_price=Decimal('25'))
>>> female_template.save()
>>> female_product = Product(template=female_template)
>>> female_product.save()
>>> group_template = ProductTemplate(
... name='Group of Pig',
... default_uom=unit,
... type='goods',
... list_price=Decimal('30'),
... cost_price=Decimal('20'))
>>> group_template.save()
>>> group_product = Product(template=group_template)
>>> group_product.save()
>>> semen_template = ProductTemplate(
... name='Pig Semen',
... default_uom=cm3,
... type='goods',
... consumable=True,
... list_price=Decimal('400'),
... cost_price=Decimal('250'))
>>> semen_template.save()
>>> semen_product = Product(template=semen_template)
>>> semen_product.save()
Create sequence::
>>> Sequence = Model.get('ir.sequence')
>>> event_order_sequence = Sequence(
... name='Event Order Pig Warehouse 1',
... code='farm.event.order',
... padding=4)
>>> event_order_sequence.save()
>>> female_sequence = Sequence(
... name='Female Pig Warehouse 1',
... code='farm.animal',
... padding=4)
>>> female_sequence.save()
>>> group_sequence = Sequence(
... name='Groups Pig Warehouse 1',
... code='farm.animal.group',
... padding=4)
>>> group_sequence.save()
Prepare locations::
>>> Location = Model.get('stock.location')
>>> lost_found_location, = Location.find([('type', '=', 'lost_found')])
>>> warehouse, = Location.find([('type', '=', 'warehouse')])
>>> production_location = Location(
... name='Production Location',
... code='PROD',
... type='production',
... parent=warehouse)
>>> production_location.save()
>>> warehouse.production_location=production_location
>>> warehouse.save()
>>> warehouse.reload()
>>> production_location.reload()
>>> other_location_ids = Location.create([{
... 'name': 'Other Location 1',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }, {
... 'name': 'Other Location 2',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }, {
... 'name': 'Other Location 3',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }, {
... 'name': 'Other Location 4',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }, {
... 'name': 'Other Location 5',
... 'type': 'storage',
... 'parent': warehouse.storage_location.id,
... }], config.context)
Create specie::
>>> Specie = Model.get('farm.specie')
>>> SpecieBreed = Model.get('farm.specie.breed')
>>> SpecieFarmLine = Model.get('farm.specie.farm_line')
>>> pigs_specie = Specie(
... name='Pigs',
... male_enabled=False,
... female_enabled=True,
... female_product=female_product,
... semen_product=semen_product,
... individual_enabled=False,
... group_enabled=True,
... group_product=group_product,
... removed_location=lost_found_location,
... foster_location=lost_found_location,
... lost_found_location=lost_found_location,
... feed_lost_found_location=lost_found_location)
>>> pigs_specie.save()
>>> pigs_breed = SpecieBreed(
... specie=pigs_specie,
... name='Holland')
>>> pigs_breed.save()
>>> pigs_farm_line = SpecieFarmLine(
... specie=pigs_specie,
... farm=warehouse,
... event_order_sequence=event_order_sequence,
... has_male=False,
... has_female=True,
... female_sequence=female_sequence,
... has_individual=False,
... has_group=True,
... group_sequence=group_sequence)
>>> pigs_farm_line.save()
Set animal_type and specie in context to work as in the menus::
>>> config._context['specie'] = pigs_specie.id
>>> config._context['animal_type'] = 'female'
Create some females to be inseminated, check their pregnancy state, farrow them
to could test different weaning events::
>>> Animal = Model.get('farm.animal')
>>> female_ids = Animal.create([{
... 'type': 'female',
... 'specie': pigs_specie.id,
... 'breed': pigs_breed.id,
... 'initial_location': other_location_ids[0],
... }, {
... 'type': 'female',
... 'specie': pigs_specie.id,
... 'breed': pigs_breed.id,
... 'initial_location': other_location_ids[1],
... }, {
... 'type': 'female',
... 'specie': pigs_specie.id,
... 'breed': pigs_breed.id,
... 'initial_location': other_location_ids[2],
... }, {
... 'type': 'female',
... 'specie': pigs_specie.id,
... 'breed': pigs_breed.id,
... 'initial_location': other_location_ids[3],
... }], config.context)
>>> females = [Animal(i) for i in female_ids]
>>> all(f.farm.code == 'WH' for f in females)
True
>>> not any(bool(f.current_cycle) for f in females)
True
>>> all(f.state == 'prospective' for f in females)
True
Create insemination events for the females without dose BoM nor Lot and
validate them and check the females state::
>>> InseminationEvent = Model.get('farm.insemination.event')
>>> now = datetime.datetime.now()
>>> inseminate_events = InseminationEvent.create([{
... 'animal_type': 'female',
... 'specie': pigs_specie.id,
... 'farm': warehouse.id,
... 'timestamp': now,
... 'animal': f.id,
... } for f in females], config.context)
>>> InseminationEvent.validate_event(inseminate_events, config.context)
>>> all(InseminationEvent(i).state == 'validated'
... for i in inseminate_events)
True
>>> females = [Animal(i) for i in female_ids]
>>> all(f.current_cycle.state == 'mated' for f in females)
True
>>> all(f.state == 'mated' for f in females)
True
Create pregnancy diagnosis events with positive result, validate them and check
females state and pregnancy state::
>>> PregnancyDiagnosisEvent = Model.get('farm.pregnancy_diagnosis.event')
>>> now = datetime.datetime.now()
>>> diagnosis_events = PregnancyDiagnosisEvent.create([{
... 'animal_type': 'female',
... 'specie': pigs_specie.id,
... 'farm': warehouse.id,
... 'timestamp': now,
... 'animal': f.id,
... 'result': 'positive',
... } for f in females], config.context)
>>> PregnancyDiagnosisEvent.validate_event(diagnosis_events, config.context)
>>> all(PregnancyDiagnosisEvent(i).state == 'validated'
... for i in diagnosis_events)
True
>>> females = [Animal(i) for i in female_ids]
>>> all(f.current_cycle.pregnant for f in females)
True
>>> all(f.current_cycle.state == 'pregnant' for f in females)
True
Create a farrowing event for each female with 6, 7, 8 and 9 lives respectively,
validate them and check females state and female's live values::
>>> FarrowingEvent = Model.get('farm.farrowing.event')
>>> now = datetime.datetime.now()
>>> farrow_events = FarrowingEvent.create([{
... 'animal_type': 'female',
... 'specie': pigs_specie.id,
... 'farm': warehouse.id,
... 'timestamp': now,
... 'animal': females[i].id,
... 'live': 6 + i,
... } for i in range(0, len(females))], config.context)
>>> FarrowingEvent.validate_event(farrow_events, config.context)
>>> all(FarrowingEvent(i).state == 'validated' for i in farrow_events)
True
>>> females = [Animal(i) for i in female_ids]
>>> not any(f.current_cycle.pregnant for f in females)
True
>>> all(f.current_cycle.state == 'lactating' for f in females)
True
>>> all(f.state == 'mated' for f in females)
True
>>> females[0].current_cycle.live
6
>>> females[-1].current_cycle.live == (6 + len(females) - 1)
True
Create a weaning event for first female (6 lives) with 6 as quantity, with
current female location as destination location for female and group and
without weaned group::
>>> WeaningEvent = Model.get('farm.weaning.event')
>>> now = datetime.datetime.now()
>>> female1 = females[0]
>>> weaning_event1 = WeaningEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female1,
... quantity=6,
... female_to_location=female1.location,
... weaned_to_location=female1.location)
>>> weaning_event1.save()
Validate weaning event::
>>> WeaningEvent.validate_event([weaning_event1.id], config.context)
>>> weaning_event1.reload()
>>> weaning_event1.state
u'validated'
Check female's current cycle state is 'unmated' and its weaned value is 6 and
the weaning event doesn't have female, weaned nor lost moves::
>>> female1.reload()
>>> female1.current_cycle.state
u'unmated'
>>> female1.current_cycle.weaned
6
>>> female1.current_cycle.weaning_event.female_move
>>> female1.current_cycle.weaning_event.weaned_move
>>> female1.current_cycle.weaning_event.lost_move
Create a weaning event for second female (7 lives) with 6 as quantity, with
current female location as destination of weaned group but not for destination
female location and without weaned group::
>>> WeaningEvent = Model.get('farm.weaning.event')
>>> now = datetime.datetime.now()
>>> female2 = females[1]
>>> weaning_event2 = WeaningEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female2,
... quantity=6,
... female_to_location=other_location_ids[-1],
... weaned_to_location=female2.location)
>>> weaning_event2.save()
Validate weaning event::
>>> WeaningEvent.validate_event([weaning_event2.id], config.context)
>>> weaning_event2.reload()
>>> weaning_event2.state
u'validated'
Check female's current cycle state is 'unmated' and its weaned value is 6 and
the weaning event has female and lost moves but not weaned group move::
>>> female2.reload()
>>> female2.current_cycle.state
u'unmated'
>>> female2.current_cycle.weaned
6
>>> female2.current_cycle.weaning_event.female_move.state
u'done'
>>> female2.current_cycle.weaning_event.weaned_move
>>> female2.current_cycle.weaning_event.lost_move.quantity
1.0
Create a weaning event for third female (8 lives) with 8 as quantity, with
different destination location for female and group and without weaned group::
>>> WeaningEvent = Model.get('farm.weaning.event')
>>> now = datetime.datetime.now()
>>> female3 = females[2]
>>> weaning_event3 = WeaningEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female3,
... quantity=8,
... female_to_location=other_location_ids[-1],
... weaned_to_location=other_location_ids[-1])
>>> weaning_event3.save()
Validate weaning event::
>>> WeaningEvent.validate_event([weaning_event3.id], config.context)
>>> weaning_event3.reload()
>>> weaning_event3.state
u'validated'
Check female's current cycle state is 'unmated' and its weaned value is 8 and
the weaning event has female and weaned group moves but not lost move::
>>> female3.reload()
>>> female3.current_cycle.state
u'unmated'
>>> female3.current_cycle.weaned
8
>>> female3.current_cycle.weaning_event.female_move.state
u'done'
>>> female3.current_cycle.weaning_event.weaned_move.quantity
8.0
>>> female3.current_cycle.weaning_event.lost_move
Create a group::
>>> AnimalGroup = Model.get('farm.animal.group')
>>> animal_group = AnimalGroup(
... specie=pigs_specie,
... breed=pigs_breed,
... initial_location=other_location_ids[-1],
... initial_quantity=4)
>>> animal_group.save()
Create a weaning event for third female (9 lives) with 7 as quantity, with
current female location as destination of female and group but with weaned
group::
>>> WeaningEvent = Model.get('farm.weaning.event')
>>> now = datetime.datetime.now()
>>> female4 = females[3]
>>> weaning_event4 = WeaningEvent(
... animal_type='female',
... specie=pigs_specie,
... farm=warehouse,
... timestamp=now,
... animal=female4,
... quantity=7,
... female_to_location=female4.location,
... weaned_to_location=female4.location,
... weaned_group=animal_group)
>>> weaning_event4.save()
Validate weaning event::
>>> WeaningEvent.validate_event([weaning_event4.id], config.context)
>>> weaning_event4.reload()
>>> weaning_event4.state
u'validated'
Check female's current cycle state is 'unmated' and its weaned value is 7 and
the weaning event has lost move and **transformation event** but not female nor
weaned group moves::
>>> female4.reload()
>>> female4.current_cycle.state
u'unmated'
>>> female4.current_cycle.weaned
7
>>> female4.current_cycle.weaning_event.female_move
>>> female4.current_cycle.weaning_event.weaned_move
>>> female4.current_cycle.weaning_event.lost_move.quantity
2.0
>>> female4.current_cycle.weaning_event.transformation_event.state
u'validated'

119
tests/test_farm.py Normal file
View File

@ -0,0 +1,119 @@
#!/usr/bin/env python
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
import sys
import os
DIR = os.path.abspath(os.path.normpath(os.path.join(__file__,
'..', '..', '..', '..', '..', 'trytond')))
if os.path.isdir(DIR):
sys.path.insert(0, os.path.dirname(DIR))
import doctest
import unittest
import warnings
import trytond.tests.test_tryton
from trytond.tests.test_tryton import POOL, test_view, test_depends
from trytond.backend.sqlite.database import Database as SQLiteDatabase
SCENARIOS = [
'scenario_move_event_test.rst',
'scenario_feed_event_test.rst',
'scenario_medication_event_test.rst',
'scenario_transformation_event_test.rst',
'scenario_removal_event_test.rst',
'scenario_semen_extraction_event_test.rst',
'scenario_insemination_event_test.rst',
'scenario_pregnancy_diagnosis_event_test.rst',
'scenario_abort_event_test.rst',
'scenario_farrowing_event_test.rst',
'scenario_foster_event_test.rst',
'scenario_weaning_event_test.rst',
'scenario_feed_inventory_test.rst',
]
class FarmTestCase(unittest.TestCase):
'''
Test Farm module.
'''
def setUp(self):
trytond.tests.test_tryton.install_module('farm')
self.template = POOL.get('product.template')
self.product = POOL.get('product.product')
self.uom = POOL.get('product.uom')
self.lot = POOL.get('stock.lot')
self.location = POOL.get('stock.location')
self.move = POOL.get('stock.move')
self.company = POOL.get('company.company')
self.user = POOL.get('res.user')
self.period = POOL.get('stock.period')
self.cache = POOL.get('stock.period.cache')
def test0005views(self):
'''
Test views.
'''
test_view('farm')
def test0006depends(self):
'''
Test depends.
'''
test_depends()
def doctest_dropdb(test):
database = SQLiteDatabase().connect()
cursor = database.cursor(autocommit=True)
try:
database.drop(cursor, ':memory:')
cursor.commit()
finally:
cursor.close()
def suite():
suite = trytond.tests.test_tryton.suite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(FarmTestCase))
for scenario in SCENARIOS:
suite.addTests(doctest.DocFileSuite(scenario,
setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite
if __name__ == '__main__':
warnings.filterwarnings(action='ignore', category=DeprecationWarning)
if sys.argv and len(sys.argv) == 2 and '--only-unittest' in sys.argv:
print "ONLY UNITTEST"
unittest_suite = trytond.tests.test_tryton.suite()
unittest_suite.addTests(
unittest.TestLoader().loadTestsFromTestCase(FarmTestCase))
unittest.TextTestRunner(verbosity=2).run(unittest_suite)
sys.exit(0)
if sys.argv and len(sys.argv) == 2 and '--only-scenario' in sys.argv:
print "ONLY SCENARIO"
scenario_suite = trytond.tests.test_tryton.suite()
for scenario in SCENARIOS:
scenario_suite.addTests(doctest.DocFileSuite(scenario,
setUp=doctest_dropdb, tearDown=doctest_dropdb,
encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
unittest.TextTestRunner(verbosity=2).run(scenario_suite)
sys.exit(0)
if sys.argv and len(sys.argv) == 3 and '--only-scenario' in sys.argv:
scenario = sys.argv[2]
print "ONLY ONE SCENARIO: %s" % scenario
scenario_suite = trytond.tests.test_tryton.suite()
scenario_suite.addTests(doctest.DocFileSuite(scenario,
setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
unittest.TextTestRunner(verbosity=2).run(scenario_suite)
sys.exit(0)
if sys.argv and len(sys.argv) > 1:
sys.exit("Unexpected number of params: %s.\n"
"USAGE: test_farm.py [--only-unittest|--only-scenario]"
% (sys.argv,))
unittest.TextTestRunner(verbosity=2).run(suite())

34
tryton.cfg Normal file
View File

@ -0,0 +1,34 @@
[tryton]
version=2.9.0
depends:
ir
res
stock
stock_location_warehouse
stock_lot
production
quality_control
quality_control_formula
xml:
specie.xml
stock.xml
production.xml
quality.xml
user.xml
animal.xml
animal_group.xml
events/abstract_event.xml
events/move_event.xml
events/feed_event.xml
events/medication_event.xml
events/transformation_event.xml
events/removal_event.xml
events/semen_extraction_event.xml
events/pregnancy_diagnosis_event.xml
events/insemination_event.xml
events/abort_event.xml
events/farrowing_event.xml
events/foster_event.xml
events/weaning_event.xml
events/feed_inventory.xml
events/event_order.xml

30
user.py Normal file
View File

@ -0,0 +1,30 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import fields, ModelSQL
from trytond.pool import PoolMeta
from trytond.pyson import Eval, Id, Not
__all__ = ['User', 'UserLocation']
__metaclass__ = PoolMeta
class User:
__name__ = 'res.user'
farms = fields.Many2Many('res.user-stock.location', 'user', 'location',
'Farms', domain=[
('type', '=', 'warehouse'),
],
states={
'readonly': Not(Eval('groups', []).contains(
Id('farm', 'group_farm_admin'))),
},
help="Farms to which this user is assigned. Determine animals that "
"he/she can manage.")
class UserLocation(ModelSQL):
'User - Location'
__name__ = 'res.user-stock.location'
user = fields.Many2One('res.user', 'User', required=True)
location = fields.Many2One('stock.location', 'Location', required=True)

13
user.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="res_user_form_view">
<field name="model">res.user</field>
<field name="type">form</field>
<field name="inherit" ref="res.user_view_form"/>
<field name="name">res_user_form</field>
</record>
</data>
</tryton>

View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='employee']" position="after">
<group id="abort_fields_group" colspan="4" col="4">
<separator id="abort_fields_separator" colspan="4"/>
<label name="female_cycle"/>
<field name="female_cycle"/>
</group>
</xpath>
</data>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='timestamp']" position="after">
<field name="female_cycle"/>
</xpath>
</data>

View File

@ -0,0 +1,45 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<form string="Female Cycle">
<field name="animal"/>
<newline/>
<group id="header" colspan="4" col="6">
<label name="sequence"/>
<field name="sequence"/>
<label name="pregnant"/>
<field name="pregnant"/>
<label name="state"/>
<field name="state"/>
</group>
<group id="header2" colspan="4" col="8">
<label name="days_between_weaning_and_insemination"/>
<field name="days_between_weaning_and_insemination"/>
<label name="days_between_farrowing_weaning"/>
<field name="days_between_farrowing_weaning"/>
<label name="fostered"/>
<field name="fostered"/>
<label name="removed"/>
<field name="removed"/>
</group>
<separator id="events" string="Events" colspan="4"/>
<field name="insemination_events" colspan="4"/>
<field name="diagnosis_events" colspan="4"/>
<group id="abort_and_farrowing" colspan="4" col="6">
<label name="abort_event"/>
<field name="abort_event"/>
<newline/>
<label name="farrowing_event"/>
<field name="farrowing_event"/>
<label name="live"/>
<field name="live"/>
<label name="dead"/>
<field name="dead"/>
</group>
<field name="foster_events" colspan="4"/>
<newline/>
<label name="weaning_event"/>
<field name="weaning_event"/>
<label name="weaned"/>
<field name="weaned"/>
</form>

View File

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tree string="Female Cycles">
<field name="animal"/>
<field name="sequence"/>
<field name="days_between_weaning_and_insemination"/>
<field name="pregnant"/>
<field name="live"/>
<field name="dead"/>
<field name="fostered"/>
<field name="weaned"/>
<field name="removed"/>
<field name="days_between_farrowing_weaning"/>
<field name="state"/>
</tree>

81
view/farm_animal_form.xml Normal file
View File

@ -0,0 +1,81 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<form string="Animal">
<group id="header" col="4">
<label name="number"/>
<field name="number"/>
<label name="breed"/>
<field name="breed"/>
<label name="sex"/>
<field name="sex"/>
<label name="purpose"/>
<field name="purpose"/>
</group>
<notebook>
<page string="General" id="general">
<group id="dates" colspan="3" col="4" yfill="1">
<label name="active"/>
<field name="active"/>
<newline/>
<label name="origin"/>
<field name="origin"/>
<label name="purchase_shipment"/>
<field name="purchase_shipment"/>
<label name="arrival_date"/>
<field name="arrival_date"/>
<label name="birthdate"/>
<field name="birthdate"/>
<label name="removal_date"/>
<field name="removal_date"/>
<label name="removal_reason"/>
<field name="removal_reason"/>
<newline/>
<label name="initial_location"/>
<field name="initial_location" colspan="3"/>
<label name="location"/>
<field name="location" colspan="3"/>
<!--
<label name="lot"/>
<field name="lot"/>
<label name="farm"/>
<field name="farm"/>
-->
</group>
<field name="tags"/>
</page>
<page name="extractions"
states="{'invisible': Not(Equal(Eval('animal_type'), 'male'))}">
<label name="last_extraction"/>
<field name="last_extraction"/>
<newline/>
<field name="extractions" colspan="4"/>
</page>
<page name="cycles">
<label name="current_cycle"/>
<field name="current_cycle"/>
<label name="state"/>
<field name="state"/>
<field name="cycles" colspan="4"/>
<group id="female_event_fields" colspan="4" col="6">
<label name="first_mating"/>
<field name="first_mating"/>
<label name="days_from_insemination"/>
<field name="days_from_insemination"/>
<label name="days_from_farrowing"/>
<field name="days_from_farrowing"/>
<newline/>
<label name="last_produced_group"/>
<field name="last_produced_group"/>
</group>
</page>
<page string="Weights" id="weights">
<label name="current_weight"/>
<field name="current_weight" colspan="3"/>
<field name="weights" colspan="4"/>
</page>
<page string="Notes" id="notes">
<field name="notes"/>
</page>
</notebook>
</form>

View File

@ -0,0 +1,47 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<form string="Animal Group">
<group colspan="4" col="4" id="header">
<label name="number"/>
<field name="number"/>
<label name="breed"/>
<field name="breed"/>
</group>
<notebook>
<page string="General" id="general">
<group id="dates" colspan="3" col="4" yfill="1">
<label name="active"/>
<field name="active"/>
<newline/>
<label name="origin"/>
<field name="origin"/>
<label name="purchase_shipment"/>
<field name="purchase_shipment"/>
<label name="arrival_date"/>
<field name="arrival_date"/>
<label name="removal_date"/>
<field name="removal_date"/>
<newline/>
<label name="initial_location"/>
<field name="initial_location"/>
<label name="initial_quantity"/>
<field name="initial_quantity"/>
<!--
<label name="lot"/>
<field name="lot"/>
<field name="farms"/>
-->
</group>
<field name="tags"/>
</page>
<page string="Weights" id="weights">
<label name="current_weight"/>
<field name="current_weight" colspan="3"/>
<field name="weights" colspan="4"/>
</page>
<page string="Notes" id="notes">
<field name="notes"/>
</page>
</notebook>
</form>

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tree string="Animal Group">
<field name="number"/>
<field name="breed"/>
<field name="arrival_date"/>
<field name="removal_date"/>
<field name="initial_quantity"/>
<field name="tags" tree_invisible="1"/>
</tree>

View File

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<form string="Weight">
<label name="group"/>
<field name="group"/>
<newline/>
<label name="quantity"/>
<field name="quantity"/>
<label name="timestamp"/>
<field name="timestamp"/>
<label name="weight"/>
<field name="weight"/>
<label name="uom"/>
<field name="uom"/>
</form>

View File

@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tree string="Weights" editable="bottom">
<field name="group"/>
<field name="quantity"/>
<field name="timestamp"/>
<field name="weight"/>
<field name="uom"/>
</tree>

16
view/farm_animal_list.xml Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tree string="Animals">
<field name="number"/>
<field name="breed"/>
<field name="arrival_date"/>
<field name="birthdate"/>
<field name="removal_date"/>
<field name="location"/>
<field name="last_extraction"/>
<field name="days_from_insemination"/>
<field name="days_from_farrowing"/>
<field name="state"/>
<field name="tags" tree_invisible="1"/>
</tree>

View File

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<form string="Weight" col="6">
<label name="animal"/>
<field name="animal"/>
<newline/>
<label name="timestamp"/>
<field name="timestamp"/>
<label name="weight"/>
<field name="weight"/>
<label name="uom"/>
<field name="uom"/>
</form>

View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tree string="Weights" editable="bottom">
<field name="animal"/>
<field name="timestamp"/>
<field name="weight"/>
<field name="uom"/>
</tree>

32
view/farm_event_form.xml Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<form string="Farm Event">
<group id="header" colspan="4" col="6">
<!--<label name="order"/>
<field name="order"/>-->
<label name="farm"/>
<field name="farm"/>
<label name="timestamp"/>
<field name="timestamp"/>
</group>
<group id="animal_or_group" colspan="2" col="2">
<label name="animal"/>
<field name="animal"/>
<label name="animal_group"/>
<field name="animal_group"/>
</group>
<label name="employee"/>
<field name="employee"/>
<separator name="notes" colspan="4"/>
<field name="notes" colspan="4"/>
<group id="state_and_buttons" colspan="4" col="12">
<label name="state"/>
<field name="state"/>
<!--<button name="cancel" string="_Cancel" icon="tryton-cancel"
help="Are you sure to cancel this event?"/>
<button name="draft" string="_Draft" icon="tryton-go-previous"/>-->
<button name="validate_event" string="_Validate" icon="tryton-ok"
help="Are you sure to validate this event?"/>
</group>
</form>

11
view/farm_event_list.xml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tree string="Farm Events">
<field name="animal"/>
<field name="animal_group"/>
<field name="timestamp"/>
<field name="employee"/>
<field name="farm"/>
<field name="state"/>
</tree>

View File

@ -0,0 +1,31 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<form string="Work Order">
<label name="name"/>
<field name="name"/>
<newline/>
<field name="animal_type"/>
<field name="specie"/>
<field name="event_type"/>
<newline/>
<label name="farm"/>
<field name="farm"/>
<label name="timestamp"/>
<field name="timestamp"/>
<label name="employee"/>
<field name="employee"/>
<separator id="events" string="" colspan="4"/>
<field name="medication_events" colspan="4"/>
<field name="insemination_events" colspan="4"/>
<field name="pregnancy_diagnosis_events" colspan="4"/>
<field name="abort_events" colspan="4"/>
<field name="farrowing_events" colspan="4"/>
<field name="foster_events" colspan="4"/>
<field name="weaning_events" colspan="4"/>
<group colspan="4" col="3" id="buttons">
<!--<button string="Cancel" name="cancel" icon="tryton-cancel" />-->
<button string="Confirm" name="confirm" icon="tryton-ok"/>
<!--<button string="Draft" name="draft" icon="tryton-go-previous"/>-->
</group>
</form>

View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tree string="Work Orders">
<field name="name"/>
<field name="farm"/>
<field name="timestamp"/>
<field name="employee"/>
</tree>

View File

@ -0,0 +1,29 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='employee']" position="after">
<group id="farrowing_fields_group" colspan="4" col="4">
<separator id="farrowing_fields_separator" colspan="4"/>
<group id="live_stillborn_mummified" colspan="4" col="6">
<label name="live"/>
<field name="live"/>
<label name="stillborn"/>
<field name="stillborn"/>
<label name="mummified"/>
<field name="mummified"/>
<label name="dead"/>
<field name="dead"/>
<label name="problem"/>
<field name="problem" colspan="3"/>
</group>
<separator id="validated_fields" colspan="4"/>
<label name="female_cycle"/>
<field name="female_cycle"/>
<label name="produced_group"/>
<field name="produced_group"/>
<label name="move"/>
<field name="move"/>
</group>
</xpath>
</data>

View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='timestamp']" position="after">
<field name="female_cycle"/>
<field name="problem"/>
<field name="live"/>
<field name="dead"/>
<field name="produced_group"/>
</xpath>
</data>

View File

@ -0,0 +1,6 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<form string="Farrowing Problem">
<field name="name"/>
</form>

View File

@ -0,0 +1,6 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tree string="Farrowing Problem">
<field name="name"/>
</tree>

View File

@ -0,0 +1,31 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='employee']" position="after">
<group id="feed_fields_group" colspan="4" col="4">
<separator id="feed_fields_separator" colspan="4"/>
<label name="location"/>
<field name="location"/>
<label name="feed_location"/>
<field name="feed_location"/>
<label name="feed_product"/>
<field name="feed_product"/>
<label name="feed_lot"/>
<field name="feed_lot"/>
<label name="quantity"/>
<field name="quantity"/>
<label name="uom"/>
<field name="uom"/>
<label name="start_date"/>
<field name="start_date"/>
<label name="end_date"/>
<field name="end_date"/>
<newline/>
<label name="feed_inventory"/>
<field name="feed_inventory"/>
<label name="move"/>
<field name="move"/>
</group>
</xpath>
</data>

View File

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='timestamp']" position="after">
<field name="location"/>
<field name="feed_location"/>
<field name="feed_product"/>
<field name="feed_lot"/>
<field name="quantity"/>
<field name="feed_inventory"/>
</xpath>
</data>

View File

@ -0,0 +1,33 @@
<?xml version="1.0"?>
<!--The COPYRIGHT file at the top level of this repository
contains the full copyright notices and license terms. -->
<form string="Feed Inventory">
<label name="location"/>
<field name="location"/>
<label name="timestamp"/>
<field name="timestamp"/>
<label name="quantity"/>
<field name="quantity"/>
<label name="uom"/>
<field name="uom"/>
<label name="prev_inventory"/>
<field name="prev_inventory"/>
<notebook colspan="4">
<page name="dest_locations">
<field name="dest_locations" colspan="4"/>
</page>
<page name="lines">
<field name="lines" colspan="4"/>
</page>
<page name="feed_events">
<field name="feed_events" colspan="4"/>
</page>
</notebook>
<group col="4" colspan="4" id="group_buttons">
<label name="state"/>
<field name="state"/>
<group colspan="2" col="3" id="buttons">
<button string="Confirm" name="confirm" icon="tryton-ok"/>
</group>
</group>
</form>

View File

@ -0,0 +1,27 @@
<?xml version="1.0"?>
<!--The COPYRIGHT file at the top level of this repository
contains the full copyright notices and license terms. -->
<form string="Feed Inventory Line">
<group id="inventory" colspan="4" col="6">
<label name="inventory"/>
<field name="inventory"/>
<label name="provisional_inventory"/>
<field name="provisional_inventory"/>
<label name="state"/>
<field name="state"/>
</group>
<label name="location"/>
<field name="location"/>
<label name="dest_location"/>
<field name="dest_location"/>
<label name="start_date"/>
<field name="start_date"/>
<label name="end_date"/>
<field name="end_date"/>
<label name="consumed_qty"/>
<field name="consumed_qty"/>
<label name="uom"/>
<field name="uom"/>
<label name="consumed_qty_animal_day"/>
<field name="consumed_qty_animal_day"/>
</form>

View File

@ -0,0 +1,12 @@
<tree string="Feed Inventory Line">
<field name="inventory"/>
<field name="provisional_inventory"/>
<field name="location"/>
<field name="dest_location"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="consumed_qty"/>
<field name="uom"/>
<field name="consumed_qty_animal_day"/>
<field name="state"/>
</tree>

View File

@ -0,0 +1,9 @@
<tree string="Feed Inventory">
<field name="location"/>
<field name="timestamp"/>
<field name="dest_locations"/>
<field name="prev_inventory"/>
<field name="quantity"/>
<field name="uom"/>
<field name="state"/>
</tree>

View File

@ -0,0 +1,37 @@
<?xml version="1.0"?>
<!--The COPYRIGHT file at the top level of this repository
contains the full copyright notices and license terms. -->
<form string="Feed Provisional Inventory">
<label name="location"/>
<field name="location"/>
<label name="timestamp"/>
<field name="timestamp"/>
<label name="quantity"/>
<field name="quantity"/>
<label name="uom"/>
<field name="uom"/>
<separator id="blank_sep" string="" colspan="4"/>
<label name="prev_inventory_date"/>
<field name="prev_inventory_date"/>
<label name="inventory"/>
<field name="inventory"/>
<label name="feed_inventory"/>
<field name="feed_inventory"/>
<notebook colspan="4">
<page name="dest_locations">
<field name="dest_locations" colspan="4"/>
</page>
<page name="lines">
<field name="lines" colspan="4"/>
</page>
</notebook>
<group col="4" colspan="4" id="group_buttons">
<label name="state"/>
<field name="state"/>
<group colspan="2" col="3" id="buttons">
<button string="Cancel" name="cancel" icon="tryton-cancel" />
<button string="Confirm" name="confirm" icon="tryton-ok"/>
<button string="Draft" name="draft" icon="tryton-go-previous"/>
</group>
</group>
</form>

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