Initial commit
This commit is contained in:
commit
048d7f1e39
|
@ -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/>.
|
|
@ -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.
|
|
@ -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>.
|
|
@ -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
|
|
@ -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/
|
|
@ -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')
|
|
@ -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)
|
|
@ -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>
|
|
@ -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
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
|||
Farm Module
|
||||
###########
|
||||
|
||||
The Farm module defines the following models: animal,
|
||||
|
||||
|
|
@ -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 *
|
|
@ -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.'),
|
||||
]
|
|
@ -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>
|
|
@ -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")
|
|
@ -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>
|
|
@ -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)
|
|
@ -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>
|
|
@ -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.'),
|
||||
]
|
|
@ -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>
|
|
@ -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)
|
|
@ -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)
|
|
@ -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>
|
File diff suppressed because it is too large
Load Diff
|
@ -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>
|
|
@ -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)
|
|
@ -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>
|
|
@ -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)
|
|
@ -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>
|
|
@ -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)'),
|
||||
]
|
|
@ -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>
|
|
@ -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)
|
|
@ -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>
|
|
@ -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)
|
|
@ -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>
|
|
@ -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)
|
|
@ -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>
|
|
@ -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)
|
|
@ -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>
|
|
@ -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)
|
|
@ -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>
|
|
@ -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.'),
|
||||
]
|
|
@ -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>
|
|
@ -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 |
File diff suppressed because it is too large
Load Diff
|
@ -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,))
|
|
@ -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>
|
|
@ -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)
|
|
@ -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>
|
|
@ -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,
|
||||
)
|
|
@ -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')
|
|
@ -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>
|
|
@ -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
|
|
@ -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>
|
|
@ -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
|
|
@ -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'
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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'
|
|
@ -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
|
|
@ -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})
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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...
|
|
@ -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'
|
||||
|
|
@ -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())
|
|
@ -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
|
|
@ -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)
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
Loading…
Reference in New Issue