mirror of
https://github.com/OCA/account-reconcile.git
synced 2025-01-20 12:27:39 +02:00
Merge remote-tracking branch 'upstream/7.0' into 8.0-port-base-import
Conflicts: .gitignore .travis.yml README.md __unported__/account_statement_base_completion/__openerp__.py __unported__/account_statement_commission/__openerp__.py __unported__/account_statement_transactionid_import/__openerp__.py __unported__/statement_voucher_killer/__openerp__.py
This commit is contained in:
661
LICENSE
Normal file
661
LICENSE
Normal file
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 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 Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are 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.
|
||||
|
||||
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.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
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 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 work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero 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 your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
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 AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
@@ -2,7 +2,8 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Guewen Baconnier
|
||||
# Copyright 2012 Camptocamp SA
|
||||
# Contributor: Leonardo Pistone
|
||||
# Copyright 2012-2014 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -22,3 +23,4 @@
|
||||
import easy_reconcile
|
||||
import base_advanced_reconciliation
|
||||
import advanced_reconciliation
|
||||
import res_config # noqa
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Guewen Baconnier
|
||||
# Copyright 2012 Camptocamp SA
|
||||
# Contributor: Leonardo Pistone
|
||||
# Copyright 2012-2014 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -63,9 +64,9 @@ The base class to find the reconciliations is built to be as efficient as
|
||||
possible.
|
||||
|
||||
So basically, if you have an invoice with 3 payments (one per month), the first
|
||||
month, it will partial reconcile the debit move line with the first payment, the second
|
||||
month, it will partial reconcile the debit move line with 2 first payments,
|
||||
the third month, it will make the full reconciliation.
|
||||
month, it will partial reconcile the debit move line with the first payment, the
|
||||
second month, it will partial reconcile the debit move line with 2 first
|
||||
payments, the third month, it will make the full reconciliation.
|
||||
|
||||
This module is perfectly adapted for E-Commerce business where a big volume of
|
||||
move lines and so, reconciliations, are involved and payments often come from
|
||||
@@ -73,11 +74,12 @@ many offices.
|
||||
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data': ['easy_reconcile_view.xml'],
|
||||
'data': ['easy_reconcile_view.xml',
|
||||
'res_config_view.xml'],
|
||||
'test': [],
|
||||
'images': [],
|
||||
'installable': False,
|
||||
'auto_install': False,
|
||||
'license': 'AGPL-3',
|
||||
'application': True,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Guewen Baconnier
|
||||
# Copyright 2012 Camptocamp SA
|
||||
# Contributor: Leonardo Pistone
|
||||
# Copyright 2012-2014 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -18,13 +19,18 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import logging
|
||||
|
||||
from itertools import product
|
||||
from openerp.osv import orm
|
||||
from openerp import pooler
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class easy_reconcile_advanced(orm.AbstractModel):
|
||||
|
||||
_name = 'easy.reconcile.advanced'
|
||||
_inherit = 'easy.reconcile.base'
|
||||
|
||||
@@ -34,11 +40,8 @@ class easy_reconcile_advanced(orm.AbstractModel):
|
||||
sql_from = self._from(rec)
|
||||
where, params = self._where(rec)
|
||||
where += " AND account_move_line.debit > 0 "
|
||||
|
||||
where2, params2 = self._get_filter(cr, uid, rec, context=context)
|
||||
|
||||
query = ' '.join((select, sql_from, where, where2))
|
||||
|
||||
cr.execute(query, params + params2)
|
||||
return cr.dictfetchall()
|
||||
|
||||
@@ -48,11 +51,8 @@ class easy_reconcile_advanced(orm.AbstractModel):
|
||||
sql_from = self._from(rec)
|
||||
where, params = self._where(rec)
|
||||
where += " AND account_move_line.credit > 0 "
|
||||
|
||||
where2, params2 = self._get_filter(cr, uid, rec, context=context)
|
||||
|
||||
query = ' '.join((select, sql_from, where, where2))
|
||||
|
||||
cr.execute(query, params + params2)
|
||||
return cr.dictfetchall()
|
||||
|
||||
@@ -170,14 +170,15 @@ class easy_reconcile_advanced(orm.AbstractModel):
|
||||
"""
|
||||
mkey, mvalue = matcher
|
||||
omkey, omvalue = opposite_matcher
|
||||
assert mkey == omkey, ("A matcher %s is compared with a matcher %s, "
|
||||
" the _matchers and _opposite_matchers are probably wrong" %
|
||||
(mkey, omkey))
|
||||
assert mkey == omkey, \
|
||||
(_("A matcher %s is compared with a matcher %s, the _matchers and "
|
||||
"_opposite_matchers are probably wrong") % (mkey, omkey))
|
||||
if not isinstance(mvalue, (list, tuple)):
|
||||
mvalue = mvalue,
|
||||
if not isinstance(omvalue, (list, tuple)):
|
||||
omvalue = omvalue,
|
||||
return easy_reconcile_advanced._compare_matcher_values(mkey, mvalue, omvalue)
|
||||
return easy_reconcile_advanced._compare_matcher_values(mkey, mvalue,
|
||||
omvalue)
|
||||
|
||||
def _compare_opposite(self, cr, uid, rec, move_line, opposite_move_line,
|
||||
matchers, context=None):
|
||||
@@ -206,13 +207,13 @@ class easy_reconcile_advanced(orm.AbstractModel):
|
||||
|
||||
return True
|
||||
|
||||
def _search_opposites(self, cr, uid, rec, move_line, opposite_move_lines, context=None):
|
||||
"""
|
||||
Search the opposite move lines for a move line
|
||||
def _search_opposites(self, cr, uid, rec, move_line, opposite_move_lines,
|
||||
context=None):
|
||||
"""Search the opposite move lines for a move line
|
||||
|
||||
:param dict move_line: the move line for which we search opposites
|
||||
:param list opposite_move_lines: list of dict of move lines values, the move
|
||||
lines we want to search for
|
||||
:param list opposite_move_lines: list of dict of move lines values,
|
||||
the move lines we want to search for
|
||||
:return: list of matching lines
|
||||
"""
|
||||
matchers = self._matchers(cr, uid, rec, move_line, context=context)
|
||||
@@ -221,10 +222,30 @@ class easy_reconcile_advanced(orm.AbstractModel):
|
||||
cr, uid, rec, move_line, op, matchers, context=context)]
|
||||
|
||||
def _action_rec(self, cr, uid, rec, context=None):
|
||||
credit_lines = self._query_credit(cr, uid, rec, context=context)
|
||||
debit_lines = self._query_debit(cr, uid, rec, context=context)
|
||||
return self._rec_auto_lines_advanced(
|
||||
cr, uid, rec, credit_lines, debit_lines, context=context)
|
||||
# we use a new cursor to be able to commit the reconciliation
|
||||
# often. We have to create it here and not later to avoid problems
|
||||
# where the new cursor sees the lines as reconciles but the old one
|
||||
# does not.
|
||||
if context is None:
|
||||
context = {}
|
||||
ctx = context.copy()
|
||||
ctx['commit_every'] = (
|
||||
rec.journal_id.company_id.reconciliation_commit_every
|
||||
)
|
||||
if ctx['commit_every']:
|
||||
new_cr = pooler.get_db(cr.dbname).cursor()
|
||||
else:
|
||||
new_cr = cr
|
||||
try:
|
||||
credit_lines = self._query_credit(new_cr, uid, rec, context=ctx)
|
||||
debit_lines = self._query_debit(new_cr, uid, rec, context=ctx)
|
||||
result = self._rec_auto_lines_advanced(
|
||||
new_cr, uid, rec, credit_lines, debit_lines, context=ctx)
|
||||
finally:
|
||||
if ctx['commit_every']:
|
||||
new_cr.commit()
|
||||
new_cr.close()
|
||||
return result
|
||||
|
||||
def _skip_line(self, cr, uid, rec, move_line, context=None):
|
||||
"""
|
||||
@@ -234,33 +255,41 @@ class easy_reconcile_advanced(orm.AbstractModel):
|
||||
"""
|
||||
return False
|
||||
|
||||
def _rec_auto_lines_advanced(self, cr, uid, rec, credit_lines, debit_lines, context=None):
|
||||
def _rec_auto_lines_advanced(self, cr, uid, rec, credit_lines, debit_lines,
|
||||
context=None):
|
||||
""" Advanced reconciliation main loop """
|
||||
reconciled_ids = []
|
||||
partial_reconciled_ids = []
|
||||
reconcile_groups = []
|
||||
|
||||
for credit_line in credit_lines:
|
||||
_logger.info("%d credit lines to reconcile", len(credit_lines))
|
||||
for idx, credit_line in enumerate(credit_lines, start=1):
|
||||
if idx % 50 == 0:
|
||||
_logger.info("... %d/%d credit lines inspected ...", idx,
|
||||
len(credit_lines))
|
||||
if self._skip_line(cr, uid, rec, credit_line, context=context):
|
||||
continue
|
||||
|
||||
opposite_lines = self._search_opposites(
|
||||
cr, uid, rec, credit_line, debit_lines, context=context)
|
||||
|
||||
if not opposite_lines:
|
||||
continue
|
||||
|
||||
opposite_ids = [l['id'] for l in opposite_lines]
|
||||
line_ids = opposite_ids + [credit_line['id']]
|
||||
for group in reconcile_groups:
|
||||
if any([lid in group for lid in opposite_ids]):
|
||||
_logger.debug("New lines %s matched with an existing "
|
||||
"group %s", line_ids, group)
|
||||
group.update(line_ids)
|
||||
break
|
||||
else:
|
||||
_logger.debug("New group of lines matched %s", line_ids)
|
||||
reconcile_groups.append(set(line_ids))
|
||||
|
||||
lines_by_id = dict([(l['id'], l) for l in credit_lines + debit_lines])
|
||||
for reconcile_group_ids in reconcile_groups:
|
||||
_logger.info("Found %d groups to reconcile", len(reconcile_groups))
|
||||
for group_count, reconcile_group_ids in enumerate(reconcile_groups,
|
||||
start=1):
|
||||
_logger.debug("Reconciling group %d/%d with ids %s",
|
||||
group_count, len(reconcile_groups),
|
||||
reconcile_group_ids)
|
||||
group_lines = [lines_by_id[lid] for lid in reconcile_group_ids]
|
||||
reconciled, full = self._reconcile_lines(
|
||||
cr, uid, rec, group_lines, allow_partial=True, context=context)
|
||||
@@ -269,4 +298,10 @@ class easy_reconcile_advanced(orm.AbstractModel):
|
||||
elif reconciled:
|
||||
partial_reconciled_ids += reconcile_group_ids
|
||||
|
||||
if (context['commit_every'] and
|
||||
group_count % context['commit_every'] == 0):
|
||||
cr.commit()
|
||||
_logger.info("Commit the reconciliations after %d groups",
|
||||
group_count)
|
||||
_logger.info("Reconciliation is over")
|
||||
return reconciled_ids, partial_reconciled_ids
|
||||
|
||||
59
__unported__/account_advanced_reconcile/res_config.py
Normal file
59
__unported__/account_advanced_reconcile/res_config.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Leonardo Pistone
|
||||
# Copyright 2014 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
|
||||
|
||||
class AccountConfigSettings(orm.TransientModel):
|
||||
_inherit = 'account.config.settings'
|
||||
|
||||
_columns = {
|
||||
'reconciliation_commit_every': fields.related(
|
||||
'company_id',
|
||||
'reconciliation_commit_every',
|
||||
type='integer',
|
||||
string='How often to commit when performing automatic '
|
||||
'reconciliation.',
|
||||
help="""Leave zero to commit only at the end of the process."""),
|
||||
}
|
||||
|
||||
def onchange_company_id(self, cr, uid, ids, company_id, context=None):
|
||||
company_obj = self.pool['res.company']
|
||||
|
||||
result = super(AccountConfigSettings, self).onchange_company_id(
|
||||
cr, uid, ids, company_id, context=None)
|
||||
|
||||
if company_id:
|
||||
company = company_obj.browse(cr, uid, company_id, context=context)
|
||||
result['value']['reconciliation_commit_every'] = (
|
||||
company.reconciliation_commit_every
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
class Company(orm.Model):
|
||||
_inherit = "res.company"
|
||||
_columns = {
|
||||
'reconciliation_commit_every': fields.integer(
|
||||
string='How often to commit when performing automatic '
|
||||
'reconciliation.',
|
||||
help="""Leave zero to commit only at the end of the process."""),
|
||||
}
|
||||
24
__unported__/account_advanced_reconcile/res_config_view.xml
Normal file
24
__unported__/account_advanced_reconcile/res_config_view.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="view_account_config" model="ir.ui.view">
|
||||
<field name="name">account settings</field>
|
||||
<field name="model">account.config.settings</field>
|
||||
<field name="inherit_id" ref="account.view_account_config_settings"/>
|
||||
<field name="arch" type="xml">
|
||||
<separator string="eInvoicing & Payments" position="before">
|
||||
<separator string="Reconciliation"/>
|
||||
<group>
|
||||
<label for="id" string="Options"/>
|
||||
<div name="reconciliation_config">
|
||||
<div>
|
||||
<label for="reconciliation_commit_every"/>
|
||||
<field name="reconciliation_commit_every" class="oe_inline"/>
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
</separator>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -36,5 +36,4 @@ Reconcile rules with transaction_ref
|
||||
'auto_install': False,
|
||||
'installable': False,
|
||||
'images': []
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
}
|
||||
|
||||
@@ -19,11 +19,10 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from itertools import product
|
||||
from openerp.osv import orm
|
||||
|
||||
|
||||
class easy_reconcile_advanced(orm.AbstractModel):
|
||||
class EasyReconcileAdvanced(orm.AbstractModel):
|
||||
|
||||
_inherit = 'easy.reconcile.advanced'
|
||||
|
||||
|
||||
@@ -36,4 +36,3 @@ class account_easy_reconcile_method(orm.Model):
|
||||
'Advanced. Partner and Transaction Ref. vs Ref.'),
|
||||
]
|
||||
return methods
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ from openerp.osv import fields, orm
|
||||
from operator import itemgetter, attrgetter
|
||||
|
||||
|
||||
class easy_reconcile_base(orm.AbstractModel):
|
||||
class EasyReconcileBase(orm.AbstractModel):
|
||||
|
||||
"""Abstract Model for reconciliation methods"""
|
||||
|
||||
_name = 'easy.reconcile.base'
|
||||
@@ -87,7 +88,6 @@ class easy_reconcile_base(orm.AbstractModel):
|
||||
# which returns a list, we have to
|
||||
# accomodate with that
|
||||
params = [rec.account_id.id]
|
||||
|
||||
if rec.partner_ids:
|
||||
where += " AND account_move_line.partner_id IN %s"
|
||||
params.append(tuple([l.id for l in rec.partner_ids]))
|
||||
@@ -115,14 +115,13 @@ class easy_reconcile_base(orm.AbstractModel):
|
||||
for key, value
|
||||
in line.iteritems()
|
||||
if key in keys), lines)
|
||||
|
||||
debit, credit = sums['debit'], sums['credit']
|
||||
writeoff_amount = round(debit - credit, precision)
|
||||
return bool(writeoff_limit >= abs(writeoff_amount)), debit, credit
|
||||
|
||||
def _get_rec_date(self, cr, uid, rec, lines,
|
||||
based_on='end_period_last_credit', context=None):
|
||||
period_obj = self.pool.get('account.period')
|
||||
period_obj = self.pool['account.period']
|
||||
|
||||
def last_period(mlines):
|
||||
period_ids = [ml['period_id'] for ml in mlines]
|
||||
@@ -153,7 +152,8 @@ class easy_reconcile_base(orm.AbstractModel):
|
||||
# when date is None
|
||||
return None
|
||||
|
||||
def _reconcile_lines(self, cr, uid, rec, lines, allow_partial=False, context=None):
|
||||
def _reconcile_lines(self, cr, uid, rec, lines, allow_partial=False,
|
||||
context=None):
|
||||
""" Try to reconcile given lines
|
||||
|
||||
:param list lines: list of dict of move lines, they must at least
|
||||
@@ -168,26 +168,23 @@ class easy_reconcile_base(orm.AbstractModel):
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
ml_obj = self.pool.get('account.move.line')
|
||||
writeoff = rec.write_off
|
||||
|
||||
line_ids = [l['id'] for l in lines]
|
||||
below_writeoff, sum_debit, sum_credit = self._below_writeoff_limit(
|
||||
cr, uid, rec, lines, writeoff, context=context)
|
||||
date = self._get_rec_date(
|
||||
cr, uid, rec, lines, rec.date_base_on, context=context)
|
||||
|
||||
rec_ctx = dict(context, date_p=date)
|
||||
if below_writeoff:
|
||||
if sum_credit < sum_debit:
|
||||
writeoff_account_id = rec.account_profit_id.id
|
||||
else:
|
||||
writeoff_account_id = rec.account_lost_id.id
|
||||
|
||||
period_id = self.pool.get('account.period').find(
|
||||
cr, uid, dt=date, context=context)[0]
|
||||
|
||||
if rec.analytic_account_id:
|
||||
rec_ctx['analytic_id'] = rec.analytic_account_id.id
|
||||
ml_obj.reconcile(
|
||||
cr, uid,
|
||||
line_ids,
|
||||
@@ -204,5 +201,4 @@ class easy_reconcile_base(orm.AbstractModel):
|
||||
type='manual',
|
||||
context=rec_ctx)
|
||||
return True, False
|
||||
|
||||
return False, False
|
||||
|
||||
@@ -19,13 +19,11 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields, osv, orm
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.osv import fields, orm
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class easy_reconcile_options(orm.AbstractModel):
|
||||
class EasyReconcileOptions(orm.AbstractModel):
|
||||
"""Options of a reconciliation profile
|
||||
|
||||
Columns shared by the configuration of methods
|
||||
@@ -37,12 +35,14 @@ class easy_reconcile_options(orm.AbstractModel):
|
||||
_name = 'easy.reconcile.options'
|
||||
|
||||
def _get_rec_base_date(self, cr, uid, context=None):
|
||||
return [('end_period_last_credit', 'End of period of most recent credit'),
|
||||
return [
|
||||
('end_period_last_credit', 'End of period of most recent credit'),
|
||||
('newest', 'Most recent move line'),
|
||||
('actual', 'Today'),
|
||||
('end_period', 'End of period of most recent move line'),
|
||||
('newest_credit', 'Date of most recent credit'),
|
||||
('newest_debit', 'Date of most recent debit')]
|
||||
('newest_debit', 'Date of most recent debit')
|
||||
]
|
||||
|
||||
_columns = {
|
||||
'write_off': fields.float('Write off allowed'),
|
||||
@@ -57,6 +57,9 @@ class easy_reconcile_options(orm.AbstractModel):
|
||||
required=True,
|
||||
string='Date of reconciliation'),
|
||||
'filter': fields.char('Filter', size=128),
|
||||
'analytic_account_id': fields.many2one(
|
||||
'account.analytic.account', 'Analytic Account',
|
||||
help="Analytic account for the write-off"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
@@ -65,20 +68,18 @@ class easy_reconcile_options(orm.AbstractModel):
|
||||
}
|
||||
|
||||
|
||||
class account_easy_reconcile_method(orm.Model):
|
||||
|
||||
class AccountEasyReconcileMethod(orm.Model):
|
||||
_name = 'account.easy.reconcile.method'
|
||||
_description = 'reconcile method for account_easy_reconcile'
|
||||
|
||||
_inherit = 'easy.reconcile.options'
|
||||
|
||||
_order = 'sequence'
|
||||
|
||||
def _get_all_rec_method(self, cr, uid, context=None):
|
||||
return [
|
||||
('easy.reconcile.simple.name', 'Simple. Amount and Name'),
|
||||
('easy.reconcile.simple.partner', 'Simple. Amount and Partner'),
|
||||
('easy.reconcile.simple.reference', 'Simple. Amount and Reference'),
|
||||
('easy.reconcile.simple.reference',
|
||||
'Simple. Amount and Reference'),
|
||||
]
|
||||
|
||||
def _get_rec_method(self, cr, uid, context=None):
|
||||
@@ -97,7 +98,7 @@ class account_easy_reconcile_method(orm.Model):
|
||||
string='Task',
|
||||
required=True,
|
||||
ondelete='cascade'),
|
||||
'company_id': fields.related('task_id','company_id',
|
||||
'company_id': fields.related('task_id', 'company_id',
|
||||
relation='res.company',
|
||||
type='many2one',
|
||||
string='Company',
|
||||
@@ -127,7 +128,7 @@ class account_easy_reconcile_method(orm.Model):
|
||||
""")
|
||||
|
||||
|
||||
class account_easy_reconcile(orm.Model):
|
||||
class AccountEasyReconcile(orm.Model):
|
||||
|
||||
_name = 'account.easy.reconcile'
|
||||
_description = 'account easy reconcile'
|
||||
@@ -199,6 +200,8 @@ class account_easy_reconcile(orm.Model):
|
||||
rec_method.account_lost_id.id),
|
||||
'account_profit_id': (rec_method.account_profit_id and
|
||||
rec_method.account_profit_id.id),
|
||||
'analytic_account_id': (rec_method.analytic_account_id and
|
||||
rec_method.analytic_account_id.id),
|
||||
'journal_id': (rec_method.journal_id and
|
||||
rec_method.journal_id.id),
|
||||
'date_base_on': rec_method.date_base_on,
|
||||
@@ -254,7 +257,7 @@ class account_easy_reconcile(orm.Model):
|
||||
be called when there is no history on the reconciliation
|
||||
task.
|
||||
"""
|
||||
raise osv.except_osv(
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('There is no history of reconciled '
|
||||
'items on the task: %s.') % rec.name)
|
||||
@@ -273,14 +276,10 @@ class account_easy_reconcile(orm.Model):
|
||||
}
|
||||
|
||||
def open_unreconcile(self, cr, uid, ids, context=None):
|
||||
""" Open the view of move line with the unreconciled move lines
|
||||
"""
|
||||
|
||||
assert len(ids) == 1 , \
|
||||
""" Open the view of move line with the unreconciled move lines"""
|
||||
assert len(ids) == 1, \
|
||||
"You can only open entries from one profile at a time"
|
||||
|
||||
obj_move_line = self.pool.get('account.move.line')
|
||||
res = {}
|
||||
for task in self.browse(cr, uid, ids, context=context):
|
||||
line_ids = obj_move_line.search(
|
||||
cr, uid,
|
||||
@@ -290,17 +289,14 @@ class account_easy_reconcile(orm.Model):
|
||||
context=context)
|
||||
|
||||
name = _('Unreconciled items')
|
||||
return self._open_move_line_list(cr, uid, line_ids, name, context=context)
|
||||
return self._open_move_line_list(cr, uid, line_ids, name,
|
||||
context=context)
|
||||
|
||||
def open_partial_reconcile(self, cr, uid, ids, context=None):
|
||||
""" Open the view of move line with the unreconciled move lines
|
||||
"""
|
||||
|
||||
assert len(ids) == 1 , \
|
||||
""" Open the view of move line with the unreconciled move lines"""
|
||||
assert len(ids) == 1, \
|
||||
"You can only open entries from one profile at a time"
|
||||
|
||||
obj_move_line = self.pool.get('account.move.line')
|
||||
res = {}
|
||||
for task in self.browse(cr, uid, ids, context=context):
|
||||
line_ids = obj_move_line.search(
|
||||
cr, uid,
|
||||
@@ -309,7 +305,8 @@ class account_easy_reconcile(orm.Model):
|
||||
('reconcile_partial_id', '!=', False)],
|
||||
context=context)
|
||||
name = _('Partial reconciled items')
|
||||
return self._open_move_line_list(cr, uid, line_ids, name, context=context)
|
||||
return self._open_move_line_list(cr, uid, line_ids, name,
|
||||
context=context)
|
||||
|
||||
def last_history_reconcile(self, cr, uid, rec_id, context=None):
|
||||
""" Get the last history record for this reconciliation profile
|
||||
|
||||
@@ -130,6 +130,7 @@ The lines should have the same amount (with the write-off) and the same referenc
|
||||
<field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/>
|
||||
<field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/>
|
||||
<field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/>
|
||||
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
|
||||
<field name="date_base_on"/>
|
||||
</form>
|
||||
</field>
|
||||
@@ -147,6 +148,7 @@ The lines should have the same amount (with the write-off) and the same referenc
|
||||
<field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/>
|
||||
<field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/>
|
||||
<field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/>
|
||||
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
|
||||
<field name="date_base_on"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
@@ -23,7 +23,7 @@ from openerp.osv import orm, fields
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class easy_reconcile_history(orm.Model):
|
||||
class EasyReconcileHistory(orm.Model):
|
||||
""" Store an history of the runs per profile
|
||||
Each history stores the list of reconciliations done"""
|
||||
|
||||
@@ -33,10 +33,8 @@ class easy_reconcile_history(orm.Model):
|
||||
|
||||
def _reconcile_line_ids(self, cr, uid, ids, name, args, context=None):
|
||||
result = {}
|
||||
|
||||
for history in self.browse(cr, uid, ids, context=context):
|
||||
result[history.id] = {}
|
||||
|
||||
move_line_ids = []
|
||||
for reconcile in history.reconcile_ids:
|
||||
move_line_ids += [line.id
|
||||
@@ -50,7 +48,6 @@ class easy_reconcile_history(orm.Model):
|
||||
for line
|
||||
in reconcile.line_partial_ids]
|
||||
result[history.id]['partial_line_ids'] = move_line_ids
|
||||
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
@@ -81,7 +78,7 @@ class easy_reconcile_history(orm.Model):
|
||||
relation='account.move.line',
|
||||
readonly=True,
|
||||
multi='lines'),
|
||||
'company_id': fields.related('easy_reconcile_id','company_id',
|
||||
'company_id': fields.related('easy_reconcile_id', 'company_id',
|
||||
relation='res.company',
|
||||
type='many2one',
|
||||
string='Company',
|
||||
@@ -90,7 +87,8 @@ class easy_reconcile_history(orm.Model):
|
||||
|
||||
}
|
||||
|
||||
def _open_move_lines(self, cr, uid, history_id, rec_type='full', context=None):
|
||||
def _open_move_lines(self, cr, uid, history_id, rec_type='full',
|
||||
context=None):
|
||||
""" For an history record, open the view of move line with
|
||||
the reconciled or partially reconciled move lines
|
||||
|
||||
@@ -100,18 +98,14 @@ class easy_reconcile_history(orm.Model):
|
||||
"""
|
||||
assert rec_type in ('full', 'partial'), \
|
||||
"rec_type must be 'full' or 'partial'"
|
||||
|
||||
history = self.browse(cr, uid, history_id, context=context)
|
||||
|
||||
if rec_type == 'full':
|
||||
field = 'reconcile_line_ids'
|
||||
name = _('Reconciliations')
|
||||
else:
|
||||
field = 'partial_line_ids'
|
||||
name = _('Partial Reconciliations')
|
||||
|
||||
move_line_ids = [line.id for line in getattr(history, field)]
|
||||
|
||||
return {
|
||||
'name': name,
|
||||
'view_mode': 'tree,form',
|
||||
|
||||
@@ -2,14 +2,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_easy_reconcile_options_acc_user,easy.reconcile.options,model_easy_reconcile_options,account.group_account_user,1,0,0,0
|
||||
access_account_easy_reconcile_method_acc_user,account.easy.reconcile.method,model_account_easy_reconcile_method,account.group_account_user,1,0,0,0
|
||||
access_account_easy_reconcile_acc_user,account.easy.reconcile,model_account_easy_reconcile,account.group_account_user,1,1,0,0
|
||||
access_easy_reconcile_simple_name_acc_user,easy.reconcile.simple.name,model_easy_reconcile_simple_name,account.group_account_user,1,0,0,0
|
||||
access_easy_reconcile_simple_partner_acc_user,easy.reconcile.simple.partner,model_easy_reconcile_simple_partner,account.group_account_user,1,0,0,0
|
||||
access_easy_reconcile_simple_reference_acc_user,easy.reconcile.simple.reference,model_easy_reconcile_simple_reference,account.group_account_user,1,0,0,0
|
||||
access_easy_reconcile_options_acc_mgr,easy.reconcile.options,model_easy_reconcile_options,account.group_account_user,1,0,0,0
|
||||
access_account_easy_reconcile_method_acc_mgr,account.easy.reconcile.method,model_account_easy_reconcile_method,account.group_account_user,1,1,1,1
|
||||
access_account_easy_reconcile_acc_mgr,account.easy.reconcile,model_account_easy_reconcile,account.group_account_user,1,1,1,1
|
||||
access_easy_reconcile_simple_name_acc_mgr,easy.reconcile.simple.name,model_easy_reconcile_simple_name,account.group_account_user,1,0,0,0
|
||||
access_easy_reconcile_simple_partner_acc_mgr,easy.reconcile.simple.partner,model_easy_reconcile_simple_partner,account.group_account_user,1,0,0,0
|
||||
access_easy_reconcile_simple_reference_acc_mgr,easy.reconcile.simple.reference,model_easy_reconcile_simple_reference,account.group_account_user,1,0,0,0
|
||||
access_easy_reconcile_history_acc_user,easy.reconcile.history,model_easy_reconcile_history,account.group_account_user,1,1,1,0
|
||||
access_easy_reconcile_history_acc_mgr,easy.reconcile.history,model_easy_reconcile_history,account.group_account_manager,1,1,1,1
|
||||
|
||||
|
@@ -22,8 +22,7 @@
|
||||
from openerp.osv.orm import AbstractModel, TransientModel
|
||||
|
||||
|
||||
class easy_reconcile_simple(AbstractModel):
|
||||
|
||||
class EasyReconcileSimple(AbstractModel):
|
||||
_name = 'easy.reconcile.simple'
|
||||
_inherit = 'easy.reconcile.base'
|
||||
|
||||
@@ -32,20 +31,14 @@ class easy_reconcile_simple(AbstractModel):
|
||||
_key_field = None
|
||||
|
||||
def rec_auto_lines_simple(self, cr, uid, rec, lines, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
if self._key_field is None:
|
||||
raise ValueError("_key_field has to be defined")
|
||||
|
||||
count = 0
|
||||
res = []
|
||||
while (count < len(lines)):
|
||||
for i in xrange(count+1, len(lines)):
|
||||
writeoff_account_id = False
|
||||
for i in xrange(count + 1, len(lines)):
|
||||
if lines[count][self._key_field] != lines[i][self._key_field]:
|
||||
break
|
||||
|
||||
check = False
|
||||
if lines[count]['credit'] > 0 and lines[i]['debit'] > 0:
|
||||
credit_line = lines[count]
|
||||
@@ -57,7 +50,6 @@ class easy_reconcile_simple(AbstractModel):
|
||||
check = True
|
||||
if not check:
|
||||
continue
|
||||
|
||||
reconciled, dummy = self._reconcile_lines(
|
||||
cr, uid, rec, [credit_line, debit_line],
|
||||
allow_partial=False, context=context)
|
||||
@@ -90,8 +82,7 @@ class easy_reconcile_simple(AbstractModel):
|
||||
return self.rec_auto_lines_simple(cr, uid, rec, lines, context)
|
||||
|
||||
|
||||
class easy_reconcile_simple_name(TransientModel):
|
||||
|
||||
class EasyReconcileSimpleName(TransientModel):
|
||||
_name = 'easy.reconcile.simple.name'
|
||||
_inherit = 'easy.reconcile.simple'
|
||||
|
||||
@@ -100,8 +91,7 @@ class easy_reconcile_simple_name(TransientModel):
|
||||
_key_field = 'name'
|
||||
|
||||
|
||||
class easy_reconcile_simple_partner(TransientModel):
|
||||
|
||||
class EasyReconcileSimplePartner(TransientModel):
|
||||
_name = 'easy.reconcile.simple.partner'
|
||||
_inherit = 'easy.reconcile.simple'
|
||||
|
||||
@@ -110,8 +100,7 @@ class easy_reconcile_simple_partner(TransientModel):
|
||||
_key_field = 'partner_id'
|
||||
|
||||
|
||||
class easy_reconcile_simple_reference(TransientModel):
|
||||
|
||||
class EasyReconcileSimpleReference(TransientModel):
|
||||
_name = 'easy.reconcile.simple.reference'
|
||||
_inherit = 'easy.reconcile.simple'
|
||||
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
{'name' : 'Invoices Reference',
|
||||
'version' : '1.0',
|
||||
'author' : 'Camptocamp',
|
||||
{'name': 'Invoices Reference',
|
||||
'version': '1.0',
|
||||
'author': 'Camptocamp',
|
||||
'maintainer': 'Camptocamp',
|
||||
'license': 'AGPL-3',
|
||||
'category': 'category',
|
||||
'complexity': "easy",
|
||||
'depends' : ['account',
|
||||
'depends': ['account',
|
||||
],
|
||||
'description': """
|
||||
Invoices Reference
|
||||
@@ -59,13 +59,15 @@ mandatory fields are:
|
||||
* Description
|
||||
* Internal Reference ("our reference")
|
||||
* External Reference ("customer or supplier reference")
|
||||
* Optionally, a technical transaction reference (credit card payment gateways, SEPA, ...)
|
||||
* Optionally, a technical transaction reference (credit card payment gateways,
|
||||
SEPA, ...)
|
||||
|
||||
Now, on the move lines:
|
||||
|
||||
* Name
|
||||
* Reference
|
||||
* Optionally, a technical transaction reference (added by the module `base_transaction_id`)
|
||||
* Optionally, a technical transaction reference (added by the module
|
||||
`base_transaction_id`)
|
||||
|
||||
Let's see how the information will be organized with this module.
|
||||
|
||||
@@ -143,4 +145,4 @@ Information propagated to the move lines:
|
||||
],
|
||||
'installable': False,
|
||||
'auto_install': False,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
from openerp.osv import orm
|
||||
|
||||
|
||||
class account_move(orm.Model):
|
||||
class AccountMove(orm.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
@@ -33,15 +33,16 @@ class account_move(orm.Model):
|
||||
if invoice:
|
||||
assert isinstance(invoice, orm.browse_record)
|
||||
invoice_obj = self.pool['account.invoice']
|
||||
ref = invoice_obj._ref_from_invoice(cr, uid, invoice, context=context)
|
||||
ref = invoice_obj._ref_from_invoice(
|
||||
cr, uid, invoice, context=context)
|
||||
vals = vals.copy()
|
||||
vals['ref'] = ref
|
||||
move_id = super(account_move, self).\
|
||||
create(cr, uid, vals, context=context)
|
||||
move_id = super(AccountMove, self).create(cr, uid, vals,
|
||||
context=context)
|
||||
return move_id
|
||||
|
||||
|
||||
class account_invoice(orm.Model):
|
||||
class AccountInvoice(orm.Model):
|
||||
_inherit = 'account.invoice'
|
||||
|
||||
def _ref_from_invoice(self, cr, uid, invoice, context=None):
|
||||
@@ -69,7 +70,8 @@ class account_invoice(orm.Model):
|
||||
cr.execute('UPDATE account_move_line SET ref=%s '
|
||||
'WHERE move_id=%s AND (ref is null OR ref = \'\')',
|
||||
(ref, move_id))
|
||||
cr.execute('UPDATE account_analytic_line SET ref=%s '
|
||||
cr.execute(
|
||||
'UPDATE account_analytic_line SET ref=%s '
|
||||
'FROM account_move_line '
|
||||
'WHERE account_move_line.move_id = %s '
|
||||
'AND account_analytic_line.move_id = account_move_line.id',
|
||||
@@ -80,7 +82,7 @@ class account_invoice(orm.Model):
|
||||
if (vals.get('supplier_invoice_reference') and not
|
||||
vals.get('reference')):
|
||||
vals['reference'] = vals['supplier_invoice_reference']
|
||||
return super(account_invoice, self).create(cr, uid, vals,
|
||||
return super(AccountInvoice, self).create(cr, uid, vals,
|
||||
context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
@@ -88,13 +90,12 @@ class account_invoice(orm.Model):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
for invoice in self.browse(cr, uid, ids, context=context):
|
||||
local_vals = vals
|
||||
if not invoice.reference:
|
||||
locvals = vals.copy()
|
||||
locvals['reference'] = vals['supplier_invoice_reference']
|
||||
super(account_invoice, self).write(cr, uid, [invoice.id],
|
||||
super(AccountInvoice, self).write(cr, uid, [invoice.id],
|
||||
locvals, context=context)
|
||||
return True
|
||||
else:
|
||||
return super(account_invoice, self).write(cr, uid, ids, vals,
|
||||
return super(AccountInvoice, self).write(cr, uid, ids, vals,
|
||||
context=context)
|
||||
|
||||
@@ -40,4 +40,4 @@ Needs `statement_voucher_killer`
|
||||
'test': [],
|
||||
'installable': False,
|
||||
'auto_install': True,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv.orm import Model
|
||||
from openerp.osv import fields
|
||||
from openerp.addons.account_statement_base_completion.statement import ErrorTooManyPartner
|
||||
from openerp.addons.account_statement_base_completion.statement import \
|
||||
ErrorTooManyPartner
|
||||
|
||||
|
||||
class AccountStatementCompletionRule(Model):
|
||||
@@ -38,10 +39,6 @@ class AccountStatementCompletionRule(Model):
|
||||
'From bank account number (Normal or IBAN)'))
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'function_to_call': fields.selection(_get_functions, 'Method'),
|
||||
}
|
||||
|
||||
def get_from_bank_account(self, cr, uid, st_line, context=None):
|
||||
"""
|
||||
Match the partner based on the partner account number field
|
||||
@@ -65,14 +62,16 @@ class AccountStatementCompletionRule(Model):
|
||||
[('acc_number', '=', partner_acc_number)],
|
||||
context=context)
|
||||
if len(ids) > 1:
|
||||
raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more than '
|
||||
'one partner for account number "%s".') % (st_line['name'], st_line['ref'], partner_acc_number))
|
||||
raise ErrorTooManyPartner(
|
||||
_('Line named "%s" (Ref:%s) was matched by more than one '
|
||||
'partner for account number "%s".') %
|
||||
(st_line['name'], st_line['ref'], partner_acc_number))
|
||||
if len(ids) == 1:
|
||||
partner = res_bank_obj.browse(cr, uid, ids[0], context=context).partner_id
|
||||
partner = res_bank_obj.browse(
|
||||
cr, uid, ids[0], context=context).partner_id
|
||||
res['partner_id'] = partner.id
|
||||
st_vals = st_obj.get_values_for_line(cr,
|
||||
uid,
|
||||
profile_id=st_line['profile_id'],
|
||||
st_vals = st_obj.get_values_for_line(
|
||||
cr, uid, profile_id=st_line['profile_id'],
|
||||
master_account_id=st_line['master_account_id'],
|
||||
partner_id=res.get('partner_id', False),
|
||||
line_type=st_line['type'],
|
||||
@@ -86,7 +85,6 @@ class AccountStatementLine(Model):
|
||||
_inherit = "account.bank.statement.line"
|
||||
|
||||
_columns = {
|
||||
# 'additional_bank_fields' : fields.serialized('Additional infos from bank', help="Used by completion and import system."),
|
||||
'partner_acc_number': fields.sparse(
|
||||
type='char',
|
||||
string='Account Number',
|
||||
|
||||
@@ -30,14 +30,19 @@ class bankaccount_completion(common.TransactionCase):
|
||||
def prepare(self):
|
||||
self.company_a = self.browse_ref('base.main_company')
|
||||
self.profile_obj = self.registry("account.statement.profile")
|
||||
self.account_bank_statement_obj = self.registry("account.bank.statement")
|
||||
self.account_bank_statement_line_obj = self.registry("account.bank.statement.line")
|
||||
self.completion_rule_id = self.ref('account_statement_bankaccount_completion.bank_statement_completion_rule_10')
|
||||
self.journal_id = self.registry("ir.model.data").get_object_reference(self.cr, self. uid, "account", "bank_journal")[1]
|
||||
self.st_obj = self.registry("account.bank.statement")
|
||||
self.st_line_obj = self.registry("account.bank.statement.line")
|
||||
self.completion_rule_id = self.ref(
|
||||
'account_statement_bankaccount_completion.'
|
||||
'bank_statement_completion_rule_10')
|
||||
self.journal_id = self.registry("ir.model.data").get_object_reference(
|
||||
self.cr, self. uid, "account", "bank_journal")[1]
|
||||
self.partner_id = self.ref('base.main_partner')
|
||||
# Create the profile
|
||||
self.account_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "account", "a_recv")[1]
|
||||
self.journal_id = self.registry("ir.model.data").get_object_reference(self.cr, self. uid, "account", "bank_journal")[1]
|
||||
self.account_id = self.registry("ir.model.data").get_object_reference(
|
||||
self.cr, self.uid, "account", "a_recv")[1]
|
||||
self.journal_id = self.registry("ir.model.data").get_object_reference(
|
||||
self.cr, self. uid, "account", "bank_journal")[1]
|
||||
self.profile_id = self.profile_obj.create(self.cr, self.uid, {
|
||||
"name": "TEST",
|
||||
"commission_account_id": self.account_id,
|
||||
@@ -46,17 +51,17 @@ class bankaccount_completion(common.TransactionCase):
|
||||
# Create the completion rule
|
||||
|
||||
# Create a bank statement
|
||||
self.statement_id = self.account_bank_statement_obj.create(self.cr, self.uid, {
|
||||
self.statement_id = self.st_obj.create(
|
||||
self.cr, self.uid, {
|
||||
"balance_end_real": 0.0,
|
||||
"balance_start": 0.0,
|
||||
"date": time.strftime('%Y-%m-%d'),
|
||||
"journal_id": self.journal_id,
|
||||
"profile_id": self.profile_id
|
||||
|
||||
})
|
||||
|
||||
# Create bank a statement line
|
||||
self.statement_line_id = self.account_bank_statement_line_obj.create(self.cr, self.uid, {
|
||||
self.statement_line_id = self.st_line_obj.create(self.cr, self.uid, {
|
||||
'amount': 1000.0,
|
||||
'name': 'EXT001',
|
||||
'ref': 'My ref',
|
||||
@@ -78,14 +83,20 @@ class bankaccount_completion(common.TransactionCase):
|
||||
def test_00(self):
|
||||
"""Test complete partner_id from bank account number
|
||||
|
||||
Test the automatic completion of the partner_id based on the account number associated to the
|
||||
Test the automatic completion of the partner_id based on the account
|
||||
number associated to the
|
||||
statement line
|
||||
"""
|
||||
self.prepare()
|
||||
statement_line = self.account_bank_statement_line_obj.browse(self.cr, self.uid, self.statement_line_id)
|
||||
statement_line = self.st_line_obj.browse(self.cr, self.uid,
|
||||
self.statement_line_id)
|
||||
# before import, the
|
||||
self.assertFalse(statement_line.partner_id, "Partner_id must be blank before completion")
|
||||
statement_obj = self.account_bank_statement_obj.browse(self.cr, self.uid, self.statement_id)
|
||||
self.assertFalse(
|
||||
statement_line.partner_id,
|
||||
"Partner_id must be blank before completion")
|
||||
statement_obj = self.st_obj.browse(self.cr, self.uid, self.statement_id)
|
||||
statement_obj.button_auto_completion()
|
||||
statement_line = self.account_bank_statement_line_obj.browse(self.cr, self.uid, self.statement_line_id)
|
||||
self.assertEquals(self.partner_id, statement_line.partner_id['id'], "Missing expected partner id after completion")
|
||||
statement_line = self.st_line_obj.browse(self.cr, self.uid,
|
||||
self.statement_line_id)
|
||||
self.assertEquals(self.partner_id, statement_line.partner_id[
|
||||
'id'], "Missing expected partner id after completion")
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
{'name': "Bank statement base completion",
|
||||
{
|
||||
'name': "Bank statement base completion",
|
||||
'version': '1.0.3',
|
||||
'author': 'Camptocamp',
|
||||
'maintainer': 'Camptocamp',
|
||||
@@ -27,34 +28,40 @@
|
||||
'complexity': 'normal',
|
||||
'depends': ['account_statement_ext'],
|
||||
'description': """
|
||||
The goal of this module is to improve the basic bank statement, help dealing with huge volume of
|
||||
reconciliation by providing basic rules to identify the partner of a bank statement line.
|
||||
Each bank statement profile can have its own rules to be applied according to a sequence order.
|
||||
The goal of this module is to improve the basic bank statement, help dealing
|
||||
with huge volume of reconciliation by providing basic rules to identify the
|
||||
partner of a bank statement line.
|
||||
Each bank statement profile can have its own rules to be applied according to a
|
||||
sequence order.
|
||||
|
||||
Some basic rules are provided in this module:
|
||||
|
||||
1) Match from statement line label (based on partner field 'Bank Statement Label')
|
||||
1) Match from statement line label (based on partner field 'Bank Statement
|
||||
Label')
|
||||
2) Match from statement line label (based on partner name)
|
||||
3) Match from statement line reference (based on Invoice number)
|
||||
|
||||
You can easily override this module and add your own rules in your own one. The basic rules only
|
||||
fill in the partner, but you can use them to fill in any value of the line (in the future, we will
|
||||
add a rule to automatically match and reconcile the line).
|
||||
You can easily override this module and add your own rules in your own one. The
|
||||
basic rules only fill in the partner, but you can use them to fill in any
|
||||
value of the line (in the future, we will add a rule to automatically match and
|
||||
reconcile the line).
|
||||
|
||||
It adds as well a label on the bank statement line (on which the pre-define rules can match) and
|
||||
a char field on the partner called 'Bank Statement Label'. Using the pre-define rules, you will be
|
||||
able to match various labels for a partner.
|
||||
It adds as well a label on the bank statement line (on which the pre-define
|
||||
rules can match) and a char field on the partner called 'Bank Statement Label'.
|
||||
Using the pre-define rules, you will be able to match various labels for a
|
||||
partner.
|
||||
|
||||
The reference of the line is always used by the reconciliation process. We're supposed to copy
|
||||
there (or write manually) the matching string. This can be: the order Number or an invoice number,
|
||||
or anything that will be found in the invoice accounting entry part to make the match.
|
||||
The reference of the line is always used by the reconciliation process. We're
|
||||
supposed to copy there (or write manually) the matching string. This can be:
|
||||
the order Number or an invoice number, or anything that will be found in the
|
||||
invoice accounting entry part to make the match.
|
||||
|
||||
You can use it with our account_advanced_reconcile module to automatize the reconciliation process.
|
||||
|
||||
|
||||
TODO: The rules that look for invoices to find out the partner should take back the payable / receivable
|
||||
account from there directly instead of retrieving it from partner properties !
|
||||
You can use it with our account_advanced_reconcile module to automatize the
|
||||
reconciliation process.
|
||||
|
||||
TODO: The rules that look for invoices to find out the partner should take back
|
||||
the payable / receivable account from there directly instead of retrieving it
|
||||
from partner properties !
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data': [
|
||||
|
||||
@@ -1,38 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#################################################################################
|
||||
# #
|
||||
##########################################################################
|
||||
#
|
||||
# Copyright (C) 2011 Akretion & Camptocamp
|
||||
# Author : Sébastien BEAU, Joel Grand-Guillaume #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU Affero 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 Affero General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU Affero General Public License #
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
#################################################################################
|
||||
# Author : Sébastien BEAU, Joel Grand-Guillaume
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
from openerp.osv.orm import Model
|
||||
from openerp.osv import fields
|
||||
from openerp.osv import orm, fields
|
||||
|
||||
|
||||
class res_partner(Model):
|
||||
"""
|
||||
Add a bank label on the partner so that we can use it to match
|
||||
class ResPartner(orm.Model):
|
||||
"""Add a bank label on the partner so that we can use it to match
|
||||
this partner when we found this in a statement line.
|
||||
"""
|
||||
_inherit = 'res.partner'
|
||||
|
||||
_columns = {
|
||||
'bank_statement_label': fields.char('Bank Statement Label', size=100,
|
||||
help="Enter the various label found on your bank statement separated by a ; If "
|
||||
"one of this label is include in the bank statement line, the partner will be automatically "
|
||||
"filled (as long as you use this method/rules in your statement profile)."),
|
||||
'bank_statement_label': fields.char(
|
||||
'Bank Statement Label', size=100,
|
||||
help="Enter the various label found on your bank statement "
|
||||
"separated by a ; If one of this label is include in the bank "
|
||||
"statement line, the partner will be automatically filled (as "
|
||||
"long as you use this method/rules in your statement "
|
||||
"profile)."),
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<record id="bk_view_partner_form" model="ir.ui.view">
|
||||
<field name="name">account_bank_statement_import.view.partner.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="type">form</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="account.view_partner_property_form"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
@@ -31,7 +31,7 @@ import psycopg2
|
||||
from collections import defaultdict
|
||||
import re
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv import osv, orm, fields
|
||||
from openerp.osv import orm, fields
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from operator import attrgetter
|
||||
|
||||
@@ -40,10 +40,10 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ErrorTooManyPartner(Exception):
|
||||
""" New Exception definition that is raised when more than one partner is
|
||||
matched by the completion rule.
|
||||
"""
|
||||
New Exception definition that is raised when more than one partner is matched by
|
||||
the completion rule.
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
@@ -55,19 +55,18 @@ class ErrorTooManyPartner(Exception):
|
||||
|
||||
|
||||
class AccountStatementProfil(orm.Model):
|
||||
"""Extend the class to add rules per profile that will match at least the
|
||||
partner, but it could also be used to match other values as well.
|
||||
"""
|
||||
Extend the class to add rules per profile that will match at least the partner,
|
||||
but it could also be used to match other values as well.
|
||||
"""
|
||||
|
||||
_inherit = "account.statement.profile"
|
||||
|
||||
_columns = {
|
||||
# @Akretion: For now, we don't implement this features, but this would probably be there:
|
||||
# 'auto_completion': fields.text('Auto Completion'),
|
||||
# 'transferts_account_id':fields.many2one('account.account', 'Transferts Account'),
|
||||
# => You can implement it in a module easily, we design it with your needs in mind
|
||||
# as well!
|
||||
# @Akretion: For now, we don't implement this features, but this would
|
||||
# probably be there: 'auto_completion': fields.text('Auto Completion'),
|
||||
# 'transferts_account_id':fields.many2one('account.account',
|
||||
# 'Transferts Account'),
|
||||
# => You can implement it in a module easily, we design it with your
|
||||
# needs in mind as well!
|
||||
|
||||
'rule_ids': fields.many2many(
|
||||
'account.statement.completion.rule',
|
||||
@@ -84,8 +83,7 @@ class AccountStatementProfil(orm.Model):
|
||||
return sorted(prof.rule_ids, key=attrgetter('sequence'))
|
||||
|
||||
def _find_values_from_rules(self, cr, uid, calls, line, context=None):
|
||||
"""
|
||||
This method will execute all related rules, in their sequence order,
|
||||
"""This method will execute all related rules, in their sequence order,
|
||||
to retrieve all the values returned by the first rules that will match.
|
||||
:param calls: list of lookup function name available in rules
|
||||
:param dict line: read of the concerned account.bank.statement.line
|
||||
@@ -97,12 +95,10 @@ class AccountStatementProfil(orm.Model):
|
||||
|
||||
...}
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if not calls:
|
||||
calls = self._get_rules(cr, uid, line['profile_id'], context=context)
|
||||
calls = self._get_rules(
|
||||
cr, uid, line['profile_id'], context=context)
|
||||
rule_obj = self.pool.get('account.statement.completion.rule')
|
||||
|
||||
for call in calls:
|
||||
method_to_call = getattr(rule_obj, call.function_to_call)
|
||||
if len(inspect.getargspec(method_to_call).args) == 6:
|
||||
@@ -116,37 +112,43 @@ class AccountStatementProfil(orm.Model):
|
||||
|
||||
|
||||
class AccountStatementCompletionRule(orm.Model):
|
||||
"""
|
||||
This will represent all the completion method that we can have to
|
||||
fullfill the bank statement lines. You'll be able to extend them in you own module
|
||||
and choose those to apply for every statement profile.
|
||||
"""This will represent all the completion method that we can have to
|
||||
fullfill the bank statement lines. You'll be able to extend them in you own
|
||||
module and choose those to apply for every statement profile.
|
||||
The goal of a rule is to fullfill at least the partner of the line, but
|
||||
if possible also the reference because we'll use it in the reconciliation
|
||||
process. The reference should contain the invoice number or the SO number
|
||||
or any reference that will be matched by the invoice accounting move.
|
||||
"""
|
||||
|
||||
_name = "account.statement.completion.rule"
|
||||
_order = "sequence asc"
|
||||
|
||||
def _get_functions(self, cr, uid, context=None):
|
||||
"""
|
||||
List of available methods for rules. Override this to add you own.
|
||||
"""
|
||||
"""List of available methods for rules. Override this to add you own."""
|
||||
return [
|
||||
('get_from_ref_and_invoice', 'From line reference (based on customer invoice number)'),
|
||||
('get_from_ref_and_supplier_invoice', 'From line reference (based on supplier invoice number)'),
|
||||
('get_from_label_and_partner_field', 'From line label (based on partner field)'),
|
||||
('get_from_label_and_partner_name', 'From line label (based on partner name)')]
|
||||
('get_from_ref_and_invoice',
|
||||
'From line reference (based on customer invoice number)'),
|
||||
('get_from_ref_and_supplier_invoice',
|
||||
'From line reference (based on supplier invoice number)'),
|
||||
('get_from_label_and_partner_field',
|
||||
'From line label (based on partner field)'),
|
||||
('get_from_label_and_partner_name',
|
||||
'From line label (based on partner name)')
|
||||
]
|
||||
|
||||
def __get_functions(self, cr, uid, context=None):
|
||||
""" Call method which can be inherited """
|
||||
return self._get_functions(cr, uid, context=context)
|
||||
|
||||
_columns = {
|
||||
'sequence': fields.integer('Sequence', help="Lower means parsed first."),
|
||||
'sequence': fields.integer('Sequence',
|
||||
help="Lower means parsed first."),
|
||||
'name': fields.char('Name', size=128),
|
||||
'profile_ids': fields.many2many(
|
||||
'account.statement.profile',
|
||||
rel='as_rul_st_prof_rel',
|
||||
string='Related statement profiles'),
|
||||
'function_to_call': fields.selection(_get_functions, 'Method'),
|
||||
'function_to_call': fields.selection(__get_functions, 'Method'),
|
||||
}
|
||||
|
||||
def _find_invoice(self, cr, uid, st_line, inv_type, context=None):
|
||||
@@ -159,7 +161,8 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
type_domain = ('out_invoice', 'out_refund')
|
||||
number_field = 'number'
|
||||
else:
|
||||
raise osv.except_osv(_('System error'),
|
||||
raise orm.except_orm(
|
||||
_('System error'),
|
||||
_('Invalid invoice type for completion: %') % inv_type)
|
||||
|
||||
inv_id = inv_obj.search(cr, uid,
|
||||
@@ -170,17 +173,19 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
if len(inv_id) == 1:
|
||||
inv = inv_obj.browse(cr, uid, inv_id[0], context=context)
|
||||
else:
|
||||
raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
|
||||
'than one partner while looking on %s invoices') %
|
||||
raise ErrorTooManyPartner(
|
||||
_('Line named "%s" (Ref:%s) was matched by more than one '
|
||||
'partner while looking on %s invoices') %
|
||||
(st_line['name'], st_line['ref'], inv_type))
|
||||
return inv
|
||||
return False
|
||||
|
||||
def _from_invoice(self, cr, uid, line, inv_type, context):
|
||||
"""Populate statement line values"""
|
||||
if not inv_type in ('supplier', 'customer'):
|
||||
raise osv.except_osv(_('System error'),
|
||||
_('Invalid invoice type for completion: %') % inv_type)
|
||||
if inv_type not in ('supplier', 'customer'):
|
||||
raise orm.except_orm(_('System error'),
|
||||
_('Invalid invoice type for completion: %') %
|
||||
inv_type)
|
||||
res = {}
|
||||
inv = self._find_invoice(cr, uid, line, inv_type, context=context)
|
||||
if inv:
|
||||
@@ -191,7 +196,6 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
partner_id = inv.commercial_partner_id.id
|
||||
else:
|
||||
partner_id = inv.partner_id.id
|
||||
|
||||
res = {'partner_id': partner_id,
|
||||
'account_id': inv.account_id.id,
|
||||
'type': inv_type}
|
||||
@@ -202,10 +206,10 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
|
||||
# Should be private but data are initialised with no update XML
|
||||
def get_from_ref_and_supplier_invoice(self, cr, uid, line, context=None):
|
||||
"""
|
||||
Match the partner based on the invoice supplier invoice number and the reference of the statement
|
||||
line. Then, call the generic get_values_for_line method to complete other values.
|
||||
If more than one partner matched, raise the ErrorTooManyPartner error.
|
||||
"""Match the partner based on the invoice supplier invoice number and
|
||||
the reference of the statement line. Then, call the generic
|
||||
get_values_for_line method to complete other values. If more than one
|
||||
partner matched, raise the ErrorTooManyPartner error.
|
||||
|
||||
:param dict line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
@@ -220,10 +224,10 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
|
||||
# Should be private but data are initialised with no update XML
|
||||
def get_from_ref_and_invoice(self, cr, uid, line, context=None):
|
||||
"""
|
||||
Match the partner based on the invoice number and the reference of the statement
|
||||
line. Then, call the generic get_values_for_line method to complete other values.
|
||||
If more than one partner matched, raise the ErrorTooManyPartner error.
|
||||
"""Match the partner based on the invoice number and the reference of
|
||||
the statement line. Then, call the generic get_values_for_line method to
|
||||
complete other values. If more than one partner matched, raise the
|
||||
ErrorTooManyPartner error.
|
||||
|
||||
:param dict line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
@@ -253,7 +257,7 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
|
||||
...}
|
||||
"""
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
partner_obj = self.pool['res.partner']
|
||||
st_obj = self.pool.get('account.bank.statement.line')
|
||||
res = {}
|
||||
# As we have to iterate on each partner for each line,
|
||||
@@ -263,12 +267,15 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
# but this option is not really maintanable
|
||||
if not context.get('label_memoizer'):
|
||||
context['label_memoizer'] = defaultdict(list)
|
||||
partner_ids = partner_obj.search(cr,
|
||||
uid,
|
||||
[('bank_statement_label', '!=', False)])
|
||||
partner_ids = partner_obj.search(
|
||||
cr, uid, [('bank_statement_label', '!=', False)],
|
||||
context=context)
|
||||
line_ids = context.get('line_ids', [])
|
||||
for partner in partner_obj.browse(cr, uid, partner_ids, context=context):
|
||||
vals = '|'.join(re.escape(x.strip()) for x in partner.bank_statement_label.split(';'))
|
||||
for partner in partner_obj.browse(cr, uid, partner_ids,
|
||||
context=context):
|
||||
vals = '|'.join(
|
||||
re.escape(x.strip())
|
||||
for x in partner.bank_statement_label.split(';'))
|
||||
or_regex = ".*%s.*" % vals
|
||||
sql = ("SELECT id from account_bank_statement_line"
|
||||
" WHERE id in %s"
|
||||
@@ -277,32 +284,29 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
pairs = cr.fetchall()
|
||||
for pair in pairs:
|
||||
context['label_memoizer'][pair[0]].append(partner)
|
||||
|
||||
if st_line['id'] in context['label_memoizer']:
|
||||
found_partner = context['label_memoizer'][st_line['id']]
|
||||
if len(found_partner) > 1:
|
||||
msg = (_('Line named "%s" (Ref:%s) was matched by '
|
||||
'more than one partner while looking on partner label: %s') %
|
||||
(st_line['name'], st_line['ref'], ','.join([x.name for x in found_partner])))
|
||||
msg = (_('Line named "%s" (Ref:%s) was matched by more than '
|
||||
'one partner while looking on partner label: %s') %
|
||||
(st_line['name'], st_line['ref'],
|
||||
','.join([x.name for x in found_partner])))
|
||||
raise ErrorTooManyPartner(msg)
|
||||
res['partner_id'] = found_partner[0].id
|
||||
st_vals = st_obj.get_values_for_line(cr,
|
||||
uid,
|
||||
profile_id=st_line['profile_id'],
|
||||
st_vals = st_obj.get_values_for_line(
|
||||
cr, uid, profile_id=st_line['profile_id'],
|
||||
master_account_id=st_line['master_account_id'],
|
||||
partner_id=found_partner[0].id,
|
||||
line_type=False,
|
||||
partner_id=found_partner[0].id, line_type=False,
|
||||
amount=st_line['amount'] if st_line['amount'] else 0.0,
|
||||
context=context)
|
||||
res.update(st_vals)
|
||||
return res
|
||||
|
||||
def get_from_label_and_partner_name(self, cr, uid, st_line, context=None):
|
||||
"""
|
||||
Match the partner based on the label field of the statement line
|
||||
and the name of the partner.
|
||||
Then, call the generic get_values_for_line method to complete other values.
|
||||
If more than one partner matched, raise the ErrorTooManyPartner error.
|
||||
"""Match the partner based on the label field of the statement line
|
||||
and the name of the partner. Then, call the generic get_values_for_line
|
||||
method to complete other values. If more than one partner matched, raise
|
||||
the ErrorTooManyPartner error.
|
||||
|
||||
:param dict st_line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
@@ -316,7 +320,8 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
res = {}
|
||||
# We memoize allowed partner
|
||||
if not context.get('partner_memoizer'):
|
||||
context['partner_memoizer'] = tuple(self.pool['res.partner'].search(cr, uid, []))
|
||||
context['partner_memoizer'] = tuple(
|
||||
self.pool['res.partner'].search(cr, uid, []))
|
||||
if not context['partner_memoizer']:
|
||||
return res
|
||||
st_obj = self.pool.get('account.bank.statement.line')
|
||||
@@ -324,33 +329,41 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
# example: 'John J. Doe (No 1)' is escaped to 'John J\. Doe \(No 1\)'
|
||||
# See http://stackoverflow.com/a/400316/1504003 for a list of
|
||||
# chars to escape. Postgres is POSIX-ARE, compatible with
|
||||
# POSIX-ERE excepted that '\' must be escaped inside brackets according to:
|
||||
# POSIX-ERE excepted that '\' must be escaped inside brackets according
|
||||
# to:
|
||||
# http://www.postgresql.org/docs/9.0/static/functions-matching.html
|
||||
# in chapter 9.7.3.6. Limits and Compatibility
|
||||
sql = """SELECT id FROM (
|
||||
SELECT id, regexp_matches(%s, regexp_replace(name,'([\.\^\$\*\+\?\(\)\[\{\\\|])', %s, 'g'), 'i') AS name_match FROM res_partner
|
||||
WHERE id IN %s) AS res_patner_matcher
|
||||
sql = """
|
||||
SELECT id FROM (
|
||||
SELECT id,
|
||||
regexp_matches(%s,
|
||||
regexp_replace(name,'([\.\^\$\*\+\?\(\)\[\{\\\|])', %s,
|
||||
'g'), 'i') AS name_match
|
||||
FROM res_partner
|
||||
WHERE id IN %s)
|
||||
AS res_patner_matcher
|
||||
WHERE name_match IS NOT NULL"""
|
||||
cr.execute(sql, (st_line['name'], r"\\\1", context['partner_memoizer']))
|
||||
cr.execute(
|
||||
sql, (st_line['name'], r"\\\1", context['partner_memoizer']))
|
||||
result = cr.fetchall()
|
||||
if not result:
|
||||
return res
|
||||
if len(result) > 1:
|
||||
raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
|
||||
'than one partner while looking on partner by name') %
|
||||
raise ErrorTooManyPartner(
|
||||
_('Line named "%s" (Ref:%s) was matched by more than one '
|
||||
'partner while looking on partner by name') %
|
||||
(st_line['name'], st_line['ref']))
|
||||
res['partner_id'] = result[0][0]
|
||||
st_vals = st_obj.get_values_for_line(cr,
|
||||
uid,
|
||||
profile_id=st_line['profile_id'],
|
||||
st_vals = st_obj.get_values_for_line(
|
||||
cr, uid, profile_id=st_line['profile_id'],
|
||||
master_account_id=st_line['master_account_id'],
|
||||
partner_id=res['partner_id'],
|
||||
line_type=False,
|
||||
partner_id=res['partner_id'], line_type=False,
|
||||
amount=st_line['amount'] if st_line['amount'] else 0.0,
|
||||
context=context)
|
||||
res.update(st_vals)
|
||||
return res
|
||||
|
||||
|
||||
class AccountStatement(orm.Model):
|
||||
_inherit = "account.bank.statement"
|
||||
|
||||
@@ -363,9 +376,10 @@ class AccountStatement(orm.Model):
|
||||
], context=context)
|
||||
if line_without_account:
|
||||
stat = self.browse(cr, uid, stat_id, context=context)
|
||||
raise orm.except_orm(_('User error'),
|
||||
raise orm.except_orm(
|
||||
_('User error'),
|
||||
_('You should fill all account on the line of the'
|
||||
' statement %s')%stat.name)
|
||||
' statement %s') % stat.name)
|
||||
return super(AccountStatement, self).button_confirm_bank(
|
||||
cr, uid, ids, context=context)
|
||||
|
||||
@@ -374,10 +388,11 @@ class AccountStatementLine(orm.Model):
|
||||
"""
|
||||
Add sparse field on the statement line to allow to store all the
|
||||
bank infos that are given by a bank/office. You can then add you own in your
|
||||
module. The idea here is to store all bank/office infos in the additionnal_bank_fields
|
||||
serialized field when importing the file. If many values, add a tab in the bank
|
||||
statement line to store your specific one. Have a look in account_statement_base_import
|
||||
module to see how we've done it.
|
||||
module. The idea here is to store all bank/office infos in the
|
||||
additionnal_bank_fields serialized field when importing the file. If many
|
||||
values, add a tab in the bank statement line to store your specific one.
|
||||
Have a look in account_statement_base_import module to see how we've done
|
||||
it.
|
||||
"""
|
||||
_inherit = "account.bank.statement.line"
|
||||
_order = "already_completed desc, date asc"
|
||||
@@ -407,30 +422,34 @@ class AccountStatementLine(orm.Model):
|
||||
}
|
||||
|
||||
def _get_line_values_from_rules(self, cr, uid, line, rules, context=None):
|
||||
"""
|
||||
We'll try to find out the values related to the line based on rules setted on
|
||||
the profile.. We will ignore line for which already_completed is ticked.
|
||||
"""We'll try to find out the values related to the line based on rules
|
||||
setted on the profile.. We will ignore line for which already_completed
|
||||
is ticked.
|
||||
|
||||
:return:
|
||||
A dict of dict value that can be passed directly to the write method of
|
||||
the statement line or {}. The first dict has statement line ID as a key:
|
||||
{117009: {'partner_id': 100997, 'account_id': 489L}}
|
||||
A dict of dict value that can be passed directly to the write
|
||||
method of the statement line or {}. The first dict has statement
|
||||
line ID as a key: {117009: {'partner_id': 100997,
|
||||
'account_id': 489L}}
|
||||
"""
|
||||
profile_obj = self.pool.get('account.statement.profile')
|
||||
profile_obj = self.pool['account.statement.profile']
|
||||
if line.get('already_completed'):
|
||||
return {}
|
||||
# Ask the rule
|
||||
vals = profile_obj._find_values_from_rules(cr, uid, rules, line, context)
|
||||
vals = profile_obj._find_values_from_rules(
|
||||
cr, uid, rules, line, context)
|
||||
if vals:
|
||||
vals['id'] = line['id']
|
||||
return vals
|
||||
return {}
|
||||
|
||||
def _get_available_columns(self, statement_store, include_serializable=False):
|
||||
def _get_available_columns(self, statement_store,
|
||||
include_serializable=False):
|
||||
"""Return writeable by SQL columns"""
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
model_cols = statement_line_obj._columns
|
||||
avail = [k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct')]
|
||||
avail = [
|
||||
k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct')]
|
||||
keys = [k for k in statement_store[0].keys() if k in avail]
|
||||
# add sparse fields..
|
||||
if include_serializable:
|
||||
@@ -468,7 +487,9 @@ class AccountStatementLine(orm.Model):
|
||||
"""
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
model_cols = statement_line_obj._columns
|
||||
sparse_fields = dict([(k, col) for k, col in model_cols.iteritems() if isinstance(col, fields.sparse) and col._type == 'char'])
|
||||
sparse_fields = dict(
|
||||
[(k, col) for k, col in model_cols.iteritems() if isinstance(
|
||||
col, fields.sparse) and col._type == 'char'])
|
||||
values = []
|
||||
for statement in statement_store:
|
||||
to_json_k = set()
|
||||
@@ -476,7 +497,8 @@ class AccountStatementLine(orm.Model):
|
||||
for k, col in sparse_fields.iteritems():
|
||||
if k in st_copy:
|
||||
to_json_k.add(col.serialization_field)
|
||||
serialized = st_copy.setdefault(col.serialization_field, {})
|
||||
serialized = st_copy.setdefault(
|
||||
col.serialization_field, {})
|
||||
serialized[k] = st_copy[k]
|
||||
for k in to_json_k:
|
||||
st_copy[k] = simplejson.dumps(st_copy[k])
|
||||
@@ -489,16 +511,21 @@ class AccountStatementLine(orm.Model):
|
||||
does not exist"""
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
statement_line_obj.check_access_rule(cr, uid, [], 'create')
|
||||
statement_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
|
||||
cols = self._get_available_columns(statement_store, include_serializable=True)
|
||||
statement_line_obj.check_access_rights(
|
||||
cr, uid, 'create', raise_exception=True)
|
||||
cols = self._get_available_columns(
|
||||
statement_store, include_serializable=True)
|
||||
statement_store = self._prepare_manyinsert(statement_store, cols)
|
||||
tmp_vals = (', '.join(cols), ', '.join(['%%(%s)s' % i for i in cols]))
|
||||
sql = "INSERT INTO account_bank_statement_line (%s) VALUES (%s);" % tmp_vals
|
||||
sql = "INSERT INTO account_bank_statement_line (%s) " \
|
||||
"VALUES (%s);" % tmp_vals
|
||||
try:
|
||||
cr.executemany(sql, tuple(self._serialize_sparse_fields(cols, statement_store)))
|
||||
cr.executemany(
|
||||
sql, tuple(self._serialize_sparse_fields(cols,
|
||||
statement_store)))
|
||||
except psycopg2.Error as sql_err:
|
||||
cr.rollback()
|
||||
raise osv.except_osv(_("ORM bypass error"),
|
||||
raise orm.except_orm(_("ORM bypass error"),
|
||||
sql_err.pgerror)
|
||||
|
||||
def _update_line(self, cr, uid, vals, context=None):
|
||||
@@ -512,18 +539,18 @@ class AccountStatementLine(orm.Model):
|
||||
cols = self._get_available_columns([vals])
|
||||
vals = self._prepare_insert(vals, cols)
|
||||
tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols]))
|
||||
sql = "UPDATE account_bank_statement_line SET %s where id = %%(id)s;" % tmp_vals
|
||||
sql = "UPDATE account_bank_statement_line " \
|
||||
"SET %s where id = %%(id)s;" % tmp_vals
|
||||
try:
|
||||
cr.execute(sql, vals)
|
||||
except psycopg2.Error as sql_err:
|
||||
cr.rollback()
|
||||
raise osv.except_osv(_("ORM bypass error"),
|
||||
raise orm.except_orm(_("ORM bypass error"),
|
||||
sql_err.pgerror)
|
||||
|
||||
|
||||
class AccountBankStatement(orm.Model):
|
||||
"""
|
||||
We add a basic button and stuff to support the auto-completion
|
||||
"""We add a basic button and stuff to support the auto-completion
|
||||
of the bank statement once line have been imported or manually fullfill.
|
||||
"""
|
||||
_inherit = "account.bank.statement"
|
||||
@@ -532,44 +559,42 @@ class AccountBankStatement(orm.Model):
|
||||
'completion_logs': fields.text('Completion Log', readonly=True),
|
||||
}
|
||||
|
||||
def write_completion_log(self, cr, uid, stat_id, error_msg, number_imported, context=None):
|
||||
"""
|
||||
Write the log in the completion_logs field of the bank statement to let the user
|
||||
know what have been done. This is an append mode, so we don't overwrite what
|
||||
already recoded.
|
||||
def write_completion_log(self, cr, uid, stat_id, error_msg,
|
||||
number_imported, context=None):
|
||||
"""Write the log in the completion_logs field of the bank statement to
|
||||
let the user know what have been done. This is an append mode, so we
|
||||
don't overwrite what already recoded.
|
||||
|
||||
:param int/long stat_id: ID of the account.bank.statement
|
||||
:param char error_msg: Message to add
|
||||
:number_imported int/long: Number of lines that have been completed
|
||||
:return True
|
||||
"""
|
||||
user_name = self.pool.get('res.users').read(cr, uid, uid,
|
||||
['name'], context=context)['name']
|
||||
user_name = self.pool.get('res.users').read(
|
||||
cr, uid, uid, ['name'], context=context)['name']
|
||||
statement = self.browse(cr, uid, stat_id, context=context)
|
||||
number_line = len(statement.line_ids)
|
||||
|
||||
log = self.read(cr, uid, stat_id, ['completion_logs'],
|
||||
context=context)['completion_logs']
|
||||
log = log if log else ""
|
||||
|
||||
completion_date = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
message = (_("%s Bank Statement ID %s has %s/%s lines completed by %s \n%s\n%s\n") %
|
||||
(completion_date, stat_id, number_imported, number_line, user_name,
|
||||
error_msg, log))
|
||||
self.write(cr, uid, [stat_id], {'completion_logs': message}, context=context)
|
||||
completion_date = datetime.datetime.now().strftime(
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
message = (_("%s Bank Statement ID %s has %s/%s lines completed by "
|
||||
"%s \n%s\n%s\n") % (completion_date, stat_id,
|
||||
number_imported, number_line,
|
||||
user_name, error_msg, log))
|
||||
self.write(
|
||||
cr, uid, [stat_id], {'completion_logs': message}, context=context)
|
||||
|
||||
body = (_('Statement ID %s auto-completed for %s/%s lines completed') %
|
||||
(stat_id, number_imported, number_line)),
|
||||
self.message_post(cr, uid,
|
||||
[stat_id],
|
||||
body=body,
|
||||
context=context)
|
||||
self.message_post(cr, uid, [stat_id], body=body, context=context)
|
||||
return True
|
||||
|
||||
def button_auto_completion(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Complete line with values given by rules and tic the already_completed
|
||||
checkbox so we won't compute them again unless the user untick them!
|
||||
"""Complete line with values given by rules and tic the
|
||||
already_completed checkbox so we won't compute them again unless the
|
||||
user untick them!
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
@@ -577,24 +602,27 @@ class AccountBankStatement(orm.Model):
|
||||
profile_obj = self.pool.get('account.statement.profile')
|
||||
compl_lines = 0
|
||||
stat_line_obj.check_access_rule(cr, uid, [], 'create')
|
||||
stat_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
|
||||
stat_line_obj.check_access_rights(
|
||||
cr, uid, 'create', raise_exception=True)
|
||||
for stat in self.browse(cr, uid, ids, context=context):
|
||||
msg_lines = []
|
||||
ctx = context.copy()
|
||||
ctx['line_ids'] = tuple((x.id for x in stat.line_ids))
|
||||
b_profile = stat.profile_id
|
||||
rules = profile_obj._get_rules(cr, uid, b_profile, context=context)
|
||||
profile_id = b_profile.id # Only for perfo even it gains almost nothing
|
||||
# Only for perfo even it gains almost nothing
|
||||
profile_id = b_profile.id
|
||||
master_account_id = b_profile.receivable_account_id
|
||||
master_account_id = master_account_id.id if master_account_id else False
|
||||
master_account_id = master_account_id.id if \
|
||||
master_account_id else False
|
||||
res = False
|
||||
for line in stat_line_obj.read(cr, uid, ctx['line_ids']):
|
||||
try:
|
||||
# performance trick
|
||||
line['master_account_id'] = master_account_id
|
||||
line['profile_id'] = profile_id
|
||||
res = stat_line_obj._get_line_values_from_rules(cr, uid, line,
|
||||
rules, context=ctx)
|
||||
res = stat_line_obj._get_line_values_from_rules(
|
||||
cr, uid, line, rules, context=ctx)
|
||||
if res:
|
||||
compl_lines += 1
|
||||
except ErrorTooManyPartner, exc:
|
||||
@@ -602,17 +630,20 @@ class AccountBankStatement(orm.Model):
|
||||
except Exception, exc:
|
||||
msg_lines.append(repr(exc))
|
||||
error_type, error_value, trbk = sys.exc_info()
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (
|
||||
error_type.__name__, error_value)
|
||||
st += ''.join(traceback.format_tb(trbk, 30))
|
||||
_logger.error(st)
|
||||
if res:
|
||||
# stat_line_obj.write(cr, uid, [line.id], vals, context=ctx)
|
||||
try:
|
||||
stat_line_obj._update_line(cr, uid, res, context=context)
|
||||
stat_line_obj._update_line(
|
||||
cr, uid, res, context=context)
|
||||
except Exception as exc:
|
||||
msg_lines.append(repr(exc))
|
||||
error_type, error_value, trbk = sys.exc_info()
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (
|
||||
error_type.__name__, error_value)
|
||||
st += ''.join(traceback.format_tb(trbk, 30))
|
||||
_logger.error(st)
|
||||
# we can commit as it is not needed to be atomic
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_form" />
|
||||
<field eval="16" name="priority"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/form/group/field[@name='sequence']" position="after">
|
||||
@@ -37,7 +36,6 @@
|
||||
<field name="name">account_bank_statement_import_base.bank_statement.auto_cmpl</field>
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_form" />
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/tree/field[@name='amount']" position="after">
|
||||
@@ -51,7 +49,6 @@
|
||||
<field name="name">account.statement.profile.view</field>
|
||||
<field name="model">account.statement.profile</field>
|
||||
<field name="inherit_id" ref="account_statement_ext.statement_importer_view_form"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="bank_statement_prefix" position="after">
|
||||
<separator colspan="4" string="Auto-Completion Rules"/>
|
||||
@@ -64,7 +61,6 @@
|
||||
<record id="statement_st_completion_rule_view_form" model="ir.ui.view">
|
||||
<field name="name">account.statement.completion.rule.view</field>
|
||||
<field name="model">account.statement.completion.rule</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Statement Completion Rule">
|
||||
<field name="sequence"/>
|
||||
@@ -79,7 +75,6 @@
|
||||
<record id="statement_st_completion_rule_view_tree" model="ir.ui.view">
|
||||
<field name="name">account.statement.completion.rule.view</field>
|
||||
<field name="model">account.statement.completion.rule</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Statement Completion Rule">
|
||||
<field name="sequence"/>
|
||||
|
||||
@@ -23,7 +23,8 @@ from openerp.tests import common
|
||||
import time
|
||||
from collections import namedtuple
|
||||
|
||||
name_completion_case = namedtuple("name_completion_case", ["partner_name", "line_label", "should_match"])
|
||||
name_completion_case = namedtuple(
|
||||
"name_completion_case", ["partner_name", "line_label", "should_match"])
|
||||
NAMES_COMPLETION_CASES = [
|
||||
name_completion_case("Acsone", "Line for Acsone SA", True),
|
||||
name_completion_case("Acsone", "Line for Acsone", True),
|
||||
@@ -34,11 +35,16 @@ NAMES_COMPLETION_CASES = [
|
||||
name_completion_case("é@|r{}", "Acsone é@|r{} for line", True),
|
||||
name_completion_case("Acsone", "A..one for line", False),
|
||||
name_completion_case("A.one SA", "A.one SA for line", True),
|
||||
name_completion_case("Acsone SA", "Line for Acsone ([^a-zA-Z0-9 -]) SA test", False),
|
||||
name_completion_case("Acsone ([^a-zA-Z0-9 -]) SA", "Line for Acsone ([^a-zA-Z0-9 -]) SA test", True),
|
||||
name_completion_case(r"Acsone (.^$*+?()[{\| -]\) SA", r"Line for Acsone (.^$*+?()[{\| -]\) SA test", True),
|
||||
name_completion_case(
|
||||
"Acsone SA", "Line for Acsone ([^a-zA-Z0-9 -]) SA test", False),
|
||||
name_completion_case(
|
||||
"Acsone ([^a-zA-Z0-9 -]) SA", "Line for Acsone ([^a-zA-Z0-9 -]) SA "
|
||||
"test", True),
|
||||
name_completion_case(
|
||||
r"Acsone (.^$*+?()[{\| -]\) SA", r"Line for Acsone (.^$*+?()[{\| -]\) "
|
||||
r"SA test", True),
|
||||
name_completion_case("Acšone SA", "Line for Acšone SA test", True),
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
class base_completion(common.TransactionCase):
|
||||
@@ -48,8 +54,10 @@ class base_completion(common.TransactionCase):
|
||||
self.company_a = self.browse_ref('base.main_company')
|
||||
self.profile_obj = self.registry("account.statement.profile")
|
||||
self.partner_obj = self.registry("res.partner")
|
||||
self.account_bank_statement_obj = self.registry("account.bank.statement")
|
||||
self.account_bank_statement_line_obj = self.registry("account.bank.statement.line")
|
||||
self.account_bank_statement_obj = self.registry(
|
||||
"account.bank.statement")
|
||||
self.account_bank_statement_line_obj = self.registry(
|
||||
"account.bank.statement.line")
|
||||
self.journal_id = self.ref("account.bank_journal")
|
||||
self.partner_id = self.ref('base.main_partner')
|
||||
self.account_id = self.ref("account.a_recv")
|
||||
@@ -57,10 +65,12 @@ class base_completion(common.TransactionCase):
|
||||
|
||||
def test_name_completion(self):
|
||||
"""Test complete partner_id from statement line label
|
||||
Test the automatic completion of the partner_id based if the name of the partner appears in
|
||||
the statement line label
|
||||
Test the automatic completion of the partner_id based if the name of the
|
||||
partner appears in the statement line label
|
||||
"""
|
||||
self.completion_rule_id = self.ref('account_statement_base_completion.bank_statement_completion_rule_3')
|
||||
self.completion_rule_id = self.ref(
|
||||
'account_statement_base_completion.'
|
||||
'bank_statement_completion_rule_3')
|
||||
# Create the profile
|
||||
self.profile_id = self.profile_obj.create(self.cr, self.uid, {
|
||||
"name": "TEST",
|
||||
@@ -68,7 +78,8 @@ class base_completion(common.TransactionCase):
|
||||
"journal_id": self.journal_id,
|
||||
"rule_ids": [(6, 0, [self.completion_rule_id])]})
|
||||
# Create a bank statement
|
||||
self.statement_id = self.account_bank_statement_obj.create(self.cr, self.uid, {
|
||||
self.statement_id = self.account_bank_statement_obj.create(
|
||||
self.cr, self.uid, {
|
||||
"balance_end_real": 0.0,
|
||||
"balance_start": 0.0,
|
||||
"date": time.strftime('%Y-%m-%d'),
|
||||
@@ -77,21 +88,33 @@ class base_completion(common.TransactionCase):
|
||||
})
|
||||
|
||||
for case in NAMES_COMPLETION_CASES:
|
||||
self.partner_obj.write(self.cr, self.uid, self.partner_id, {'name': case.partner_name})
|
||||
statement_line_id = self.account_bank_statement_line_obj.create(self.cr, self.uid, {
|
||||
self.partner_obj.write(
|
||||
self.cr, self.uid, self.partner_id, {'name': case.partner_name})
|
||||
statement_line_id = self.account_bank_statement_line_obj.create(
|
||||
self.cr, self.uid, {
|
||||
'amount': 1000.0,
|
||||
'name': case.line_label,
|
||||
'ref': 'My ref',
|
||||
'statement_id': self.statement_id,
|
||||
})
|
||||
statement_line = self.account_bank_statement_line_obj.browse(self.cr, self.uid, statement_line_id)
|
||||
self.assertFalse(statement_line.partner_id, "Partner_id must be blank before completion")
|
||||
statement_obj = self.account_bank_statement_obj.browse(self.cr, self.uid, self.statement_id)
|
||||
statement_line = self.account_bank_statement_line_obj.browse(
|
||||
self.cr, self.uid, statement_line_id)
|
||||
self.assertFalse(
|
||||
statement_line.partner_id,
|
||||
"Partner_id must be blank before completion")
|
||||
statement_obj = self.account_bank_statement_obj.browse(
|
||||
self.cr, self.uid, self.statement_id)
|
||||
statement_obj.button_auto_completion()
|
||||
statement_line = self.account_bank_statement_line_obj.browse(self.cr, self.uid, statement_line_id)
|
||||
statement_line = self.account_bank_statement_line_obj.browse(
|
||||
self.cr, self.uid, statement_line_id)
|
||||
if case.should_match:
|
||||
self.assertEquals(self.partner_id, statement_line.partner_id['id'],
|
||||
"Missing expected partner id after completion (partner_name: %s, line_name: %s)" % (case.partner_name, case.line_label))
|
||||
self.assertEquals(
|
||||
self.partner_id, statement_line.partner_id['id'],
|
||||
"Missing expected partner id after completion "
|
||||
"(partner_name: %s, line_name: %s)" %
|
||||
(case.partner_name, case.line_label))
|
||||
else:
|
||||
self.assertNotEquals(self.partner_id, statement_line.partner_id['id'],
|
||||
"Partner id should be empty after completion(partner_name: %s, line_name: %s)" % (case.partner_name, case.line_label))
|
||||
self.assertNotEquals(
|
||||
self.partner_id, statement_line.partner_id['id'],
|
||||
"Partner id should be empty after completion(partner_name: "
|
||||
"%s, line_name: %s)" % (case.partner_name, case.line_label))
|
||||
|
||||
@@ -31,30 +31,34 @@
|
||||
],
|
||||
'description': """
|
||||
This module brings basic methods and fields on bank statement to deal with
|
||||
the importation of different bank and offices. A generic abstract method is defined and an
|
||||
example that gives you a basic way of importing bank statement through a standard file is provided.
|
||||
the importation of different bank and offices. A generic abstract method is
|
||||
defined and an example that gives you a basic way of importing bank statement
|
||||
through a standard file is provided.
|
||||
|
||||
This module improves the bank statement and allows you to import your bank transactions with
|
||||
a standard .csv or .xls file (you'll find it in the 'data' folder). It respects the profile
|
||||
(provided by the accouhnt_statement_ext module) to pass the entries. That means,
|
||||
you'll have to choose a file format for each profile.
|
||||
In order to achieve this it uses the `xlrd` Python module which you will need to install
|
||||
separately in your environment.
|
||||
This module improves the bank statement and allows you to import your bank
|
||||
transactions with a standard .csv or .xls file (you'll find it in the 'data'
|
||||
folder). It respects the profile (provided by the accouhnt_statement_ext
|
||||
module) to pass the entries. That means, you'll have to choose a file format
|
||||
for each profile.
|
||||
In order to achieve this it uses the `xlrd` Python module which you will need
|
||||
to install separately in your environment.
|
||||
|
||||
This module can handle a commission taken by the payment office and has the following format:
|
||||
This module can handle a commission taken by the payment office and has the
|
||||
following format:
|
||||
|
||||
* ref : the SO number, INV number or any matching ref found. It'll be used as reference
|
||||
in the generated entries and will be useful for reconciliation process
|
||||
* date : date of the payment
|
||||
* amount : amount paid in the currency of the journal used in the importation profile
|
||||
* label : the comunication given by the payment office, used as communication in the
|
||||
generated entries.
|
||||
|
||||
The goal is here to populate the statement lines of a bank statement with the infos that the
|
||||
bank or office give you. Fell free to inherit from this module to add your own format.Then,
|
||||
if you need to complete data from there, add your own account_statement_*_completion module and implement
|
||||
the needed rules.
|
||||
* __ref__: the SO number, INV number or any matching ref found. It'll be used
|
||||
as reference in the generated entries and will be useful for reconciliation
|
||||
process
|
||||
* __date__: date of the payment
|
||||
* __amount__: amount paid in the currency of the journal used in the
|
||||
importation profile
|
||||
* __label__: the comunication given by the payment office, used as
|
||||
communication in the generated entries.
|
||||
|
||||
The goal is here to populate the statement lines of a bank statement with the
|
||||
infos that the bank or office give you. Fell free to inherit from this module
|
||||
to add your own format. Then, if you need to complete data from there, add your
|
||||
own account_statement_*_completion module and implement the needed rules.
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data': [
|
||||
@@ -66,4 +70,4 @@
|
||||
'images': [],
|
||||
'auto_install': False,
|
||||
'license': 'AGPL-3',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv.osv import except_osv
|
||||
from openerp.osv.orm import except_orm
|
||||
import tempfile
|
||||
import datetime
|
||||
from parser import BankStatementImportParser
|
||||
@@ -28,30 +28,35 @@ try:
|
||||
except:
|
||||
raise Exception(_('Please install python lib xlrd'))
|
||||
|
||||
|
||||
def float_or_zero(val):
|
||||
""" Conversion function used to manage
|
||||
empty string into float usecase"""
|
||||
return float(val) if val else 0.0
|
||||
|
||||
|
||||
class FileParser(BankStatementImportParser):
|
||||
"""
|
||||
Generic abstract class for defining parser for .csv, .xls or .xlsx file format.
|
||||
"""Generic abstract class for defining parser for .csv, .xls or .xlsx file
|
||||
format.
|
||||
"""
|
||||
|
||||
def __init__(self, parse_name, ftype='csv', extra_fields=None, header=None, **kwargs):
|
||||
def __init__(self, parse_name, ftype='csv', extra_fields=None, header=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:param char: parse_name: The name of the parser
|
||||
:param char: ftype: extension of the file (could be csv, xls or xlsx)
|
||||
:param dict: extra_fields: extra fields to add to the conversion dict. In the format
|
||||
{fieldname: fieldtype}
|
||||
:param list: header : specify header fields if the csv file has no header
|
||||
:param char: ftype: extension of the file (could be csv, xls or
|
||||
xlsx)
|
||||
:param dict: extra_fields: extra fields to add to the conversion
|
||||
dict. In the format {fieldname: fieldtype}
|
||||
:param list: header : specify header fields if the csv file has no
|
||||
header
|
||||
"""
|
||||
|
||||
super(FileParser, self).__init__(parse_name, **kwargs)
|
||||
if ftype in ('csv', 'xls' ,'xlsx'):
|
||||
if ftype in ('csv', 'xls', 'xlsx'):
|
||||
self.ftype = ftype[0:3]
|
||||
else:
|
||||
raise except_osv(_('User Error'),
|
||||
raise except_orm(
|
||||
_('User Error'),
|
||||
_('Invalid file type %s. Please use csv, xls or xlsx') % ftype)
|
||||
self.conversion_dict = {
|
||||
'ref': unicode,
|
||||
@@ -68,23 +73,17 @@ class FileParser(BankStatementImportParser):
|
||||
# Set in _parse_xls, from the contents of the file
|
||||
|
||||
def _custom_format(self, *args, **kwargs):
|
||||
"""
|
||||
No other work on data are needed in this parser.
|
||||
"""
|
||||
"""No other work on data are needed in this parser."""
|
||||
return True
|
||||
|
||||
def _pre(self, *args, **kwargs):
|
||||
"""
|
||||
No pre-treatment needed for this parser.
|
||||
"""
|
||||
"""No pre-treatment needed for this parser."""
|
||||
return True
|
||||
|
||||
def _parse(self, *args, **kwargs):
|
||||
"""
|
||||
Launch the parsing through .csv, .xls or .xlsx depending on the
|
||||
"""Launch the parsing through .csv, .xls or .xlsx depending on the
|
||||
given ftype
|
||||
"""
|
||||
|
||||
res = None
|
||||
if self.ftype == 'csv':
|
||||
res = self._parse_csv()
|
||||
@@ -94,31 +93,27 @@ class FileParser(BankStatementImportParser):
|
||||
return True
|
||||
|
||||
def _validate(self, *args, **kwargs):
|
||||
"""
|
||||
We check that all the key of the given file (means header) are present
|
||||
in the validation key provided. Otherwise, we raise an Exception.
|
||||
We skip the validation step if the file header is provided separately
|
||||
(in the field: fieldnames).
|
||||
"""We check that all the key of the given file (means header) are
|
||||
present in the validation key provided. Otherwise, we raise an
|
||||
Exception. We skip the validation step if the file header is provided
|
||||
separately (in the field: fieldnames).
|
||||
"""
|
||||
if self.fieldnames is None:
|
||||
parsed_cols = self.result_row_list[0].keys()
|
||||
for col in self.keys_to_validate:
|
||||
if col not in parsed_cols:
|
||||
raise except_osv(_('Invalid data'),
|
||||
raise except_orm(_('Invalid data'),
|
||||
_('Column %s not present in file') % col)
|
||||
return True
|
||||
|
||||
def _post(self, *args, **kwargs):
|
||||
"""
|
||||
Cast row type depending on the file format .csv or .xls after parsing the file.
|
||||
"""
|
||||
"""Cast row type depending on the file format .csv or .xls after
|
||||
parsing the file."""
|
||||
self.result_row_list = self._cast_rows(*args, **kwargs)
|
||||
return True
|
||||
|
||||
def _parse_csv(self):
|
||||
"""
|
||||
:return: list of dict from csv file (line/rows)
|
||||
"""
|
||||
""":return: list of dict from csv file (line/rows)"""
|
||||
csv_file = tempfile.NamedTemporaryFile()
|
||||
csv_file.write(self.filebuffer)
|
||||
csv_file.flush()
|
||||
@@ -127,9 +122,7 @@ class FileParser(BankStatementImportParser):
|
||||
return list(reader)
|
||||
|
||||
def _parse_xls(self):
|
||||
"""
|
||||
:return: dict of dict from xls/xlsx file (line/rows)
|
||||
"""
|
||||
""":return: dict of dict from xls/xlsx file (line/rows)"""
|
||||
wb_file = tempfile.NamedTemporaryFile()
|
||||
wb_file.write(self.filebuffer)
|
||||
# We ensure that cursor is at beginig of file
|
||||
@@ -144,8 +137,7 @@ class FileParser(BankStatementImportParser):
|
||||
return res
|
||||
|
||||
def _from_csv(self, result_set, conversion_rules):
|
||||
"""
|
||||
Handle the converstion from the dict and handle date format from
|
||||
"""Handle the converstion from the dict and handle date format from
|
||||
an .csv file.
|
||||
"""
|
||||
for line in result_set:
|
||||
@@ -156,65 +148,60 @@ class FileParser(BankStatementImportParser):
|
||||
line[rule] = datetime.datetime.strptime(date_string,
|
||||
'%Y-%m-%d')
|
||||
except ValueError as err:
|
||||
raise except_osv(_("Date format is not valid."),
|
||||
raise except_orm(
|
||||
_("Date format is not valid."),
|
||||
_(" It should be YYYY-MM-DD for column: %s"
|
||||
" value: %s \n \n"
|
||||
" \n Please check the line with ref: %s"
|
||||
" \n \n Detail: %s") % (rule,
|
||||
line.get(rule, _('Missing')),
|
||||
line.get('ref', line),
|
||||
repr(err)))
|
||||
" value: %s \n \n \n Please check the line with "
|
||||
"ref: %s \n \n Detail: %s") %
|
||||
(rule, line.get(rule, _('Missing')),
|
||||
line.get('ref', line), repr(err)))
|
||||
else:
|
||||
try:
|
||||
line[rule] = conversion_rules[rule](line[rule])
|
||||
except Exception as err:
|
||||
raise except_osv(_('Invalid data'),
|
||||
_("Value %s of column %s is not valid."
|
||||
"\n Please check the line with ref %s:"
|
||||
"\n \n Detail: %s") % (line.get(rule, _('Missing')),
|
||||
rule,
|
||||
line.get('ref', line),
|
||||
repr(err)))
|
||||
raise except_orm(
|
||||
_('Invalid data'),
|
||||
_("Value %s of column %s is not valid.\n Please "
|
||||
"check the line with ref %s:\n \n Detail: %s") %
|
||||
(line.get(rule, _('Missing')), rule,
|
||||
line.get('ref', line), repr(err)))
|
||||
return result_set
|
||||
|
||||
def _from_xls(self, result_set, conversion_rules):
|
||||
"""
|
||||
Handle the converstion from the dict and handle date format from
|
||||
"""Handle the converstion from the dict and handle date format from
|
||||
an .csv, .xls or .xlsx file.
|
||||
"""
|
||||
for line in result_set:
|
||||
for rule in conversion_rules:
|
||||
if conversion_rules[rule] == datetime.datetime:
|
||||
try:
|
||||
t_tuple = xlrd.xldate_as_tuple(line[rule], self._datemode)
|
||||
t_tuple = xlrd.xldate_as_tuple(line[rule],
|
||||
self._datemode)
|
||||
line[rule] = datetime.datetime(*t_tuple)
|
||||
except Exception as err:
|
||||
raise except_osv(_("Date format is not valid"),
|
||||
raise except_orm(
|
||||
_("Date format is not valid"),
|
||||
_("Please modify the cell formatting to date format"
|
||||
" for column: %s"
|
||||
" value: %s"
|
||||
"\n Please check the line with ref: %s"
|
||||
"\n \n Detail: %s") % (rule,
|
||||
line.get(rule, _('Missing')),
|
||||
line.get('ref', line),
|
||||
repr(err)))
|
||||
" for column: %s value: %s\n Please check the "
|
||||
"line with ref: %s\n \n Detail: %s") %
|
||||
(rule, line.get(rule, _('Missing')),
|
||||
line.get('ref', line), repr(err)))
|
||||
else:
|
||||
try:
|
||||
line[rule] = conversion_rules[rule](line[rule])
|
||||
except Exception as err:
|
||||
raise except_osv(_('Invalid data'),
|
||||
_("Value %s of column %s is not valid."
|
||||
"\n Please check the line with ref %s:"
|
||||
"\n \n Detail: %s") % (line.get(rule, _('Missing')),
|
||||
rule,
|
||||
line.get('ref', line),
|
||||
repr(err)))
|
||||
raise except_orm(
|
||||
_('Invalid data'),
|
||||
_("Value %s of column %s is not valid.\n Please "
|
||||
"check the line with ref %s:\n \n Detail: %s") %
|
||||
(line.get(rule, _('Missing')), rule,
|
||||
line.get('ref', line), repr(err)))
|
||||
return result_set
|
||||
|
||||
def _cast_rows(self, *args, **kwargs):
|
||||
"""
|
||||
Convert the self.result_row_list using the self.conversion_dict providen.
|
||||
We call here _from_xls or _from_csv depending on the self.ftype variable.
|
||||
"""Convert the self.result_row_list using the self.conversion_dict
|
||||
providen. We call here _from_xls or _from_csv depending on the
|
||||
self.ftype variable.
|
||||
"""
|
||||
func = getattr(self, '_from_%s' % self.ftype)
|
||||
res = func(self.result_row_list, self.conversion_dict)
|
||||
|
||||
@@ -18,32 +18,23 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.tools.translate import _
|
||||
import base64
|
||||
import csv
|
||||
import tempfile
|
||||
import datetime
|
||||
from file_parser import FileParser
|
||||
try:
|
||||
import xlrd
|
||||
except:
|
||||
raise Exception(_('Please install python lib xlrd'))
|
||||
|
||||
|
||||
class GenericFileParser(FileParser):
|
||||
"""
|
||||
Standard parser that use a define format in csv or xls to import into a
|
||||
"""Standard parser that use a define format in csv or xls to import into a
|
||||
bank statement. This is mostely an example of how to proceed to create a new
|
||||
parser, but will also be useful as it allow to import a basic flat file.
|
||||
"""
|
||||
|
||||
def __init__(self, parse_name, ftype='csv', **kwargs):
|
||||
super(GenericFileParser, self).__init__(parse_name, ftype=ftype, **kwargs)
|
||||
super(GenericFileParser, self).__init__(
|
||||
parse_name, ftype=ftype, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def parser_for(cls, parser_name):
|
||||
"""
|
||||
Used by the new_bank_statement_parser class factory. Return true if
|
||||
"""Used by the new_bank_statement_parser class factory. Return true if
|
||||
the providen name is generic_csvxls_so
|
||||
"""
|
||||
return parser_name == 'generic_csvxls_so'
|
||||
@@ -54,9 +45,10 @@ class GenericFileParser(FileParser):
|
||||
method of statement line in order to record it. It is the responsibility
|
||||
of every parser to give this dict of vals, so each one can implement his
|
||||
own way of recording the lines.
|
||||
:param: line: a dict of vals that represent a line of result_row_list
|
||||
:return: dict of values to give to the create method of statement line,
|
||||
it MUST contain at least:
|
||||
:param: line: a dict of vals that represent a line of
|
||||
result_row_list
|
||||
:return: dict of values to give to the create method of statement
|
||||
line, it MUST contain at least:
|
||||
{
|
||||
'name':value,
|
||||
'date':value,
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import base64
|
||||
import csv
|
||||
from datetime import datetime
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
def UnicodeDictReader(utf8_data, **kwargs):
|
||||
@@ -31,10 +32,12 @@ def UnicodeDictReader(utf8_data, **kwargs):
|
||||
dialect = sniffer.sniff(sample_data, delimiters=',;\t')
|
||||
csv_reader = csv.DictReader(utf8_data, dialect=dialect, **kwargs)
|
||||
for row in csv_reader:
|
||||
yield dict([(key, unicode(value, 'utf-8')) for key, value in row.iteritems()])
|
||||
yield dict([(key, unicode(value, 'utf-8')) for key, value in
|
||||
row.iteritems()])
|
||||
|
||||
|
||||
class BankStatementImportParser(object):
|
||||
|
||||
"""
|
||||
Generic abstract class for defining parser for different files and
|
||||
format to import in a bank statement. Inherit from it to create your
|
||||
@@ -42,14 +45,16 @@ class BankStatementImportParser(object):
|
||||
from the FileParser instead.
|
||||
"""
|
||||
|
||||
def __init__(self, parser_name, *args, **kwargs):
|
||||
def __init__(self, profile, *args, **kwargs):
|
||||
# The name of the parser as it will be called
|
||||
self.parser_name = parser_name
|
||||
self.parser_name = profile.import_type
|
||||
# The result as a list of row. One row per line of data in the file, but
|
||||
# not the commission one !
|
||||
self.result_row_list = None
|
||||
# The file buffer on which to work on
|
||||
self.filebuffer = None
|
||||
# The profile record to access its parameters in any parser method
|
||||
self.profile = profile
|
||||
self.balance_start = None
|
||||
self.balance_end = None
|
||||
self.statement_name = None
|
||||
@@ -58,23 +63,19 @@ class BankStatementImportParser(object):
|
||||
|
||||
@classmethod
|
||||
def parser_for(cls, parser_name):
|
||||
"""
|
||||
Override this method for every new parser, so that new_bank_statement_parser can
|
||||
return the good class from his name.
|
||||
"""Override this method for every new parser, so that
|
||||
new_bank_statement_parser can return the good class from his name.
|
||||
"""
|
||||
return False
|
||||
|
||||
def _decode_64b_stream(self):
|
||||
"""
|
||||
Decode self.filebuffer in base 64 and override it
|
||||
"""
|
||||
"""Decode self.filebuffer in base 64 and override it"""
|
||||
self.filebuffer = base64.b64decode(self.filebuffer)
|
||||
return True
|
||||
|
||||
def _format(self, decode_base_64=True, **kwargs):
|
||||
"""
|
||||
Decode into base 64 if asked and Format the given filebuffer by calling
|
||||
_custom_format method.
|
||||
"""Decode into base 64 if asked and Format the given filebuffer by
|
||||
calling _custom_format method.
|
||||
"""
|
||||
if decode_base_64:
|
||||
self._decode_64b_stream()
|
||||
@@ -82,44 +83,40 @@ class BankStatementImportParser(object):
|
||||
return True
|
||||
|
||||
def _custom_format(self, *args, **kwargs):
|
||||
"""
|
||||
Implement a method in your parser to convert format, encoding and so on before
|
||||
starting to work on datas. Work on self.filebuffer
|
||||
"""Implement a method in your parser to convert format, encoding and so
|
||||
on before starting to work on datas. Work on self.filebuffer
|
||||
"""
|
||||
return NotImplementedError
|
||||
|
||||
def _pre(self, *args, **kwargs):
|
||||
"""
|
||||
Implement a method in your parser to make a pre-treatment on datas before parsing
|
||||
them, like concatenate stuff, and so... Work on self.filebuffer
|
||||
"""Implement a method in your parser to make a pre-treatment on datas
|
||||
before parsing them, like concatenate stuff, and so... Work on
|
||||
self.filebuffer
|
||||
"""
|
||||
return NotImplementedError
|
||||
|
||||
def _parse(self, *args, **kwargs):
|
||||
"""
|
||||
Implement a method in your parser to save the result of parsing self.filebuffer
|
||||
in self.result_row_list instance property.
|
||||
"""Implement a method in your parser to save the result of parsing
|
||||
self.filebuffer in self.result_row_list instance property.
|
||||
"""
|
||||
return NotImplementedError
|
||||
|
||||
def _validate(self, *args, **kwargs):
|
||||
"""
|
||||
Implement a method in your parser to validate the self.result_row_list instance
|
||||
property and raise an error if not valid.
|
||||
"""Implement a method in your parser to validate the
|
||||
self.result_row_list instance property and raise an error if not valid.
|
||||
"""
|
||||
return NotImplementedError
|
||||
|
||||
def _post(self, *args, **kwargs):
|
||||
"""
|
||||
Implement a method in your parser to make some last changes on the result of parsing
|
||||
the datas, like converting dates, computing commission, ...
|
||||
"""Implement a method in your parser to make some last changes on the
|
||||
result of parsing the datas, like converting dates, computing
|
||||
commission, ...
|
||||
"""
|
||||
return NotImplementedError
|
||||
|
||||
def get_st_vals(self):
|
||||
"""
|
||||
This method return a dict of vals that ca be passed to
|
||||
create method of statement.
|
||||
"""This method return a dict of vals that ca be passed to create method
|
||||
of statement.
|
||||
:return: dict of vals that represent additional infos for the statement
|
||||
"""
|
||||
return {
|
||||
@@ -130,11 +127,11 @@ class BankStatementImportParser(object):
|
||||
}
|
||||
|
||||
def get_st_line_vals(self, line, *args, **kwargs):
|
||||
"""
|
||||
Implement a method in your parser that must return a dict of vals that can be
|
||||
passed to create method of statement line in order to record it. It is the responsibility
|
||||
of every parser to give this dict of vals, so each one can implement his
|
||||
own way of recording the lines.
|
||||
"""Implement a method in your parser that must return a dict of vals
|
||||
that can be passed to create method of statement line in order to record
|
||||
it. It is the responsibility of every parser to give this dict of vals,
|
||||
so each one can implement his own way of recording the lines.
|
||||
|
||||
:param: line: a dict of vals that represent a line of result_row_list
|
||||
:return: dict of values to give to the create method of statement line,
|
||||
it MUST contain at least:
|
||||
@@ -148,15 +145,14 @@ class BankStatementImportParser(object):
|
||||
return NotImplementedError
|
||||
|
||||
def parse(self, filebuffer, *args, **kwargs):
|
||||
"""
|
||||
This will be the method that will be called by wizard, button and so
|
||||
"""This will be the method that will be called by wizard, button and so
|
||||
to parse a filebuffer by calling successively all the private method
|
||||
that need to be define for each parser.
|
||||
Return:
|
||||
[] of rows as {'key':value}
|
||||
|
||||
Note: The row_list must contain only value that are present in the account.
|
||||
bank.statement.line object !!!
|
||||
Note: The row_list must contain only value that are present in the
|
||||
account.bank.statement.line object !!!
|
||||
"""
|
||||
if filebuffer:
|
||||
self.filebuffer = filebuffer
|
||||
@@ -217,13 +213,13 @@ def itersubclasses(cls, _seen=None):
|
||||
yield sub
|
||||
|
||||
|
||||
def new_bank_statement_parser(parser_name, *args, **kwargs):
|
||||
"""
|
||||
Return an instance of the good parser class base on the providen name
|
||||
:param char: parser_name
|
||||
:return: class instance of parser_name providen.
|
||||
def new_bank_statement_parser(profile, *args, **kwargs):
|
||||
"""Return an instance of the good parser class based on the given profile.
|
||||
|
||||
:param profile: browse_record of import profile.
|
||||
:return: class instance for given profile import type.
|
||||
"""
|
||||
for cls in itersubclasses(BankStatementImportParser):
|
||||
if cls.parser_for(parser_name):
|
||||
return cls(parser_name, *args, **kwargs)
|
||||
if cls.parser_for(profile.import_type):
|
||||
return cls(profile, *args, **kwargs)
|
||||
raise ValueError
|
||||
|
||||
@@ -20,24 +20,23 @@
|
||||
##############################################################################
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from openerp.tools.translate import _
|
||||
import datetime
|
||||
from openerp.osv.orm import Model
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.osv import fields, orm
|
||||
from parser import new_bank_statement_parser
|
||||
from openerp.tools.config import config
|
||||
|
||||
|
||||
class AccountStatementProfil(Model):
|
||||
class AccountStatementProfil(orm.Model):
|
||||
_inherit = "account.statement.profile"
|
||||
|
||||
def get_import_type_selection(self, cr, uid, context=None):
|
||||
def _get_import_type_selection(self, cr, uid, context=None):
|
||||
"""This is the method to be inherited for adding the parser"""
|
||||
return [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')]
|
||||
|
||||
def _get_import_type_selection(self, cr, uid, context=None):
|
||||
return self.get_import_type_selection(cr, uid, context=context)
|
||||
def __get_import_type_selection(self, cr, uid, context=None):
|
||||
""" Call method which can be inherited """
|
||||
return self._get_import_type_selection(cr, uid, context=context)
|
||||
|
||||
_columns = {
|
||||
'launch_import_completion': fields.boolean(
|
||||
@@ -45,10 +44,11 @@ class AccountStatementProfil(Model):
|
||||
help="Tic that box to automatically launch the completion "
|
||||
"on each imported file using this profile."),
|
||||
'last_import_date': fields.datetime("Last Import Date"),
|
||||
# we remove deprecated as it floods logs in standard/warning level sob...
|
||||
# we remove deprecated as it floods logs in standard/warning level
|
||||
# sob...
|
||||
'rec_log': fields.text('log', readonly=True), # Deprecated
|
||||
'import_type': fields.selection(
|
||||
_get_import_type_selection,
|
||||
__get_import_type_selection,
|
||||
'Type of import',
|
||||
required=True,
|
||||
help="Choose here the method by which you want to import bank"
|
||||
@@ -60,52 +60,51 @@ class AccountStatementProfil(Model):
|
||||
}
|
||||
|
||||
def _write_extra_statement_lines(
|
||||
self, cr, uid, parser, result_row_list, profile, statement_id, context):
|
||||
self, cr, uid, parser, result_row_list, profile, statement_id,
|
||||
context):
|
||||
"""Insert extra lines after the main statement lines.
|
||||
|
||||
After the main statement lines have been created, you can override this method to create
|
||||
extra statement lines.
|
||||
After the main statement lines have been created, you can override this
|
||||
method to create extra statement lines.
|
||||
|
||||
:param: browse_record of the current parser
|
||||
:param: result_row_list: [{'key':value}]
|
||||
:param: profile: browserecord of account.statement.profile
|
||||
:param: statement_id: int/long of the current importing statement ID
|
||||
:param: statement_id: int/long of the current importing
|
||||
statement ID
|
||||
:param: context: global context
|
||||
"""
|
||||
pass
|
||||
|
||||
def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, context):
|
||||
"""
|
||||
Write the log in the logger
|
||||
def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines,
|
||||
context):
|
||||
"""Write the log in the logger
|
||||
|
||||
:param int/long statement_id: ID of the concerned account.bank.statement
|
||||
:param int/long num_lines: Number of line that have been parsed
|
||||
:return: True
|
||||
"""
|
||||
self.message_post(cr,
|
||||
uid,
|
||||
ids,
|
||||
body=_('Statement ID %s have been imported with %s lines.') %
|
||||
(statement_id, num_lines),
|
||||
context=context)
|
||||
self.message_post(
|
||||
cr, uid, ids,
|
||||
body=_('Statement ID %s have been imported with %s '
|
||||
'lines.') % (statement_id, num_lines), context=context)
|
||||
return True
|
||||
|
||||
#Deprecated remove on V8
|
||||
# Deprecated remove on V8
|
||||
def prepare_statetement_lines_vals(self, *args, **kwargs):
|
||||
return self.prepare_statement_lines_vals(*args, **kwargs)
|
||||
|
||||
def prepare_statement_lines_vals(
|
||||
self, cr, uid, parser_vals,
|
||||
def prepare_statement_lines_vals(self, cr, uid, parser_vals,
|
||||
statement_id, context):
|
||||
"""
|
||||
Hook to build the values of a line from the parser returned values. At
|
||||
least it fullfill the statement_id. Overide it to add your
|
||||
own completion if needed.
|
||||
"""Hook to build the values of a line from the parser returned values.
|
||||
At least it fullfill the statement_id. Overide it to add your own
|
||||
completion if needed.
|
||||
|
||||
:param dict of vals from parser for account.bank.statement.line (called by
|
||||
parser.get_st_line_vals)
|
||||
:param dict of vals from parser for account.bank.statement.line
|
||||
(called by parser.get_st_line_vals)
|
||||
:param int/long statement_id: ID of the concerned account.bank.statement
|
||||
:return: dict of vals that will be passed to create method of statement line.
|
||||
:return: dict of vals that will be passed to create method of
|
||||
statement line.
|
||||
"""
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
values = parser_vals
|
||||
@@ -119,28 +118,35 @@ class AccountStatementProfil(Model):
|
||||
values['period_id'] = period_memoizer[date]
|
||||
else:
|
||||
# This is awfully slow...
|
||||
periods = self.pool.get('account.period').find(cr, uid,
|
||||
dt=values.get('date'),
|
||||
context=context)
|
||||
periods = self.pool.get('account.period').find(
|
||||
cr, uid, dt=values.get('date'), context=context)
|
||||
values['period_id'] = periods[0]
|
||||
period_memoizer[date] = periods[0]
|
||||
values = statement_line_obj._add_missing_default_values(cr, uid, values, context)
|
||||
values = statement_line_obj._add_missing_default_values(
|
||||
cr, uid, values, context)
|
||||
return values
|
||||
|
||||
def prepare_statement_vals(self, cr, uid, profile_id, result_row_list, parser, context):
|
||||
"""
|
||||
Hook to build the values of the statement from the parser and
|
||||
def prepare_statement_vals(self, cr, uid, profile_id, result_row_list,
|
||||
parser, context=None):
|
||||
"""Hook to build the values of the statement from the parser and
|
||||
the profile.
|
||||
"""
|
||||
vals = {'profile_id': profile_id}
|
||||
vals.update(parser.get_st_vals())
|
||||
if not vals.get('balance_start'):
|
||||
# Get starting balance from journal balance if parser doesn't
|
||||
# fill this data, simulating the manual flow
|
||||
statement_obj = self.pool['account.bank.statement']
|
||||
profile = self.browse(cr, uid, profile_id, context=context)
|
||||
temp = statement_obj.onchange_journal_id(
|
||||
cr, uid, None, profile.journal_id.id, context=context)
|
||||
vals['balance_start'] = temp['value'].get('balance_start', False)
|
||||
return vals
|
||||
|
||||
def multi_statement_import(self, cr, uid, ids, profile_id, file_stream,
|
||||
ftype="csv", context=None):
|
||||
"""
|
||||
Create multiple bank statements from values given by the parser for the
|
||||
givenprofile.
|
||||
"""Create multiple bank statements from values given by the parser for
|
||||
the given profile.
|
||||
|
||||
:param int/long profile_id: ID of the profile used to import the file
|
||||
:param filebuffer file_stream: binary of the providen file
|
||||
@@ -149,23 +155,26 @@ class AccountStatementProfil(Model):
|
||||
"""
|
||||
prof_obj = self.pool['account.statement.profile']
|
||||
if not profile_id:
|
||||
raise osv.except_osv(_("No Profile!"),
|
||||
_("You must provide a valid profile to import a bank statement!"))
|
||||
raise orm.except_orm(
|
||||
_("No Profile!"),
|
||||
_("You must provide a valid profile to import a bank "
|
||||
"statement!"))
|
||||
prof = prof_obj.browse(cr, uid, profile_id, context=context)
|
||||
|
||||
parser = new_bank_statement_parser(prof.import_type, ftype=ftype)
|
||||
parser = new_bank_statement_parser(prof, ftype=ftype)
|
||||
res = []
|
||||
for result_row_list in parser.parse(file_stream):
|
||||
statement_id = self._statement_import(cr, uid, ids, prof, parser,
|
||||
file_stream, ftype=ftype, context=context)
|
||||
statement_id = self._statement_import(
|
||||
cr, uid, ids, prof, parser, file_stream, ftype=ftype,
|
||||
context=context)
|
||||
res.append(statement_id)
|
||||
return res
|
||||
|
||||
def _statement_import(self, cr, uid, ids, prof, parser, file_stream, ftype="csv", context=None):
|
||||
"""
|
||||
Create a bank statement with the given profile and parser. It will fullfill the bank statement
|
||||
with the values of the file providen, but will not complete data (like finding the partner, or
|
||||
the right account). This will be done in a second step with the completion rules.
|
||||
def _statement_import(self, cr, uid, ids, prof, parser, file_stream,
|
||||
ftype="csv", context=None):
|
||||
"""Create a bank statement with the given profile and parser. It will
|
||||
fullfill the bank statement with the values of the file providen, but
|
||||
will not complete data (like finding the partner, or the right account).
|
||||
This will be done in a second step with the completion rules.
|
||||
|
||||
:param prof : The profile used to import the file
|
||||
:param parser: the parser
|
||||
@@ -173,27 +182,25 @@ class AccountStatementProfil(Model):
|
||||
:param char: ftype represent the file exstension (csv by default)
|
||||
:return: ID of the created account.bank.statemênt
|
||||
"""
|
||||
statement_obj = self.pool.get('account.bank.statement')
|
||||
statement_line_obj = self.pool.get('account.bank.statement.line')
|
||||
attachment_obj = self.pool.get('ir.attachment')
|
||||
|
||||
statement_obj = self.pool['account.bank.statement']
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
attachment_obj = self.pool['ir.attachment']
|
||||
result_row_list = parser.result_row_list
|
||||
# Check all key are present in account.bank.statement.line!!
|
||||
if not result_row_list:
|
||||
raise osv.except_osv(_("Nothing to import"),
|
||||
raise orm.except_orm(_("Nothing to import"),
|
||||
_("The file is empty"))
|
||||
parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys()
|
||||
for col in parsed_cols:
|
||||
if col not in statement_line_obj._columns:
|
||||
raise osv.except_osv(_("Missing column!"),
|
||||
_("Column %s you try to import is not "
|
||||
"present in the bank statement line!") % col)
|
||||
|
||||
statement_vals = self.prepare_statement_vals(cr, uid, prof.id, result_row_list, parser, context)
|
||||
statement_id = statement_obj.create(cr, uid,
|
||||
statement_vals,
|
||||
context=context)
|
||||
|
||||
raise orm.except_orm(
|
||||
_("Missing column!"),
|
||||
_("Column %s you try to import is not present in the bank "
|
||||
"statement line!") % col)
|
||||
statement_vals = self.prepare_statement_vals(
|
||||
cr, uid, prof.id, result_row_list, parser, context)
|
||||
statement_id = statement_obj.create(
|
||||
cr, uid, statement_vals, context=context)
|
||||
try:
|
||||
# Record every line in the bank statement
|
||||
statement_store = []
|
||||
@@ -204,44 +211,44 @@ class AccountStatementProfil(Model):
|
||||
context)
|
||||
statement_store.append(values)
|
||||
# Hack to bypass ORM poor perfomance. Sob...
|
||||
statement_line_obj._insert_lines(cr, uid, statement_store, context=context)
|
||||
|
||||
statement_line_obj._insert_lines(
|
||||
cr, uid, statement_store, context=context)
|
||||
self._write_extra_statement_lines(
|
||||
cr, uid, parser, result_row_list, prof, statement_id, context)
|
||||
# Trigger store field computation if someone has better idea
|
||||
start_bal = statement_obj.read(
|
||||
cr, uid, statement_id, ['balance_start'], context=context)
|
||||
start_bal = start_bal['balance_start']
|
||||
statement_obj.write(cr, uid, [statement_id], {'balance_start': start_bal})
|
||||
|
||||
statement_obj.write(
|
||||
cr, uid, [statement_id], {'balance_start': start_bal})
|
||||
attachment_data = {
|
||||
'name': 'statement file',
|
||||
'datas': file_stream,
|
||||
'datas_fname': "%s.%s" % (datetime.datetime.now().date(), ftype),
|
||||
'datas_fname': "%s.%s" % (datetime.datetime.now().date(),
|
||||
ftype),
|
||||
'res_model': 'account.bank.statement',
|
||||
'res_id': statement_id,
|
||||
}
|
||||
attachment_obj.create(cr, uid, attachment_data, context=context)
|
||||
|
||||
# If user ask to launch completion at end of import, do it!
|
||||
if prof.launch_import_completion:
|
||||
statement_obj.button_auto_completion(cr, uid, [statement_id], context)
|
||||
|
||||
statement_obj.button_auto_completion(
|
||||
cr, uid, [statement_id], context)
|
||||
# Write the needed log infos on profile
|
||||
self.write_logs_after_import(cr, uid, prof.id,
|
||||
statement_id,
|
||||
len(result_row_list),
|
||||
context)
|
||||
|
||||
except Exception:
|
||||
error_type, error_value, trbk = sys.exc_info()
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
|
||||
st = "Error: %s\nDescription: %s\nTraceback:" % (
|
||||
error_type.__name__, error_value)
|
||||
st += ''.join(traceback.format_tb(trbk, 30))
|
||||
#TODO we should catch correctly the exception with a python
|
||||
#Exception and only re-catch some special exception.
|
||||
#For now we avoid re-catching error in debug mode
|
||||
# TODO we should catch correctly the exception with a python
|
||||
# Exception and only re-catch some special exception.
|
||||
# For now we avoid re-catching error in debug mode
|
||||
if config['debug_mode']:
|
||||
raise
|
||||
raise osv.except_osv(_("Statement import error"),
|
||||
raise orm.except_orm(_("Statement import error"),
|
||||
_("The statement cannot be created: %s") % st)
|
||||
return statement_id
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<field name="name">account.statement.profile.view</field>
|
||||
<field name="model">account.statement.profile</field>
|
||||
<field name="inherit_id" ref="account_statement_ext.statement_importer_view_form"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="bank_statement_prefix" position="after">
|
||||
<separator colspan="4" string="Import related infos"/>
|
||||
@@ -31,7 +30,6 @@
|
||||
<field name="name">account_bank_statement.bank_statement.view_form</field>
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account_statement_base_completion.bank_statement_view_form" />
|
||||
<field name="type">form</field>
|
||||
<field eval="20" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='line_ids']/form//field[@name='account_id']" position="attributes">
|
||||
|
||||
@@ -22,19 +22,19 @@
|
||||
import base64
|
||||
import inspect
|
||||
import os
|
||||
|
||||
from openerp.tests import common
|
||||
|
||||
|
||||
class test_coda_import(common.TransactionCase):
|
||||
class TestCodaImport(common.TransactionCase):
|
||||
|
||||
def prepare(self):
|
||||
self.company_a = self.browse_ref('base.main_company')
|
||||
self.profile_obj = self.registry("account.statement.profile")
|
||||
self.account_bank_statement_obj = self.registry("account.bank.statement")
|
||||
# create the 2009 fiscal year since imported coda file reference statement lines in 2009
|
||||
self.account_bank_statement_obj = self.registry(
|
||||
"account.bank.statement")
|
||||
# create the 2009 fiscal year since imported coda file reference
|
||||
# statement lines in 2009
|
||||
self.fiscalyear_id = self._create_fiscalyear("2011", self.company_a.id)
|
||||
|
||||
self.account_id = self.ref("account.a_recv")
|
||||
self.journal_id = self.ref("account.bank_journal")
|
||||
self.import_wizard_obj = self.registry('credit.statement.import')
|
||||
@@ -71,15 +71,19 @@ class test_coda_import(common.TransactionCase):
|
||||
'input_statement': base64.b64encode(content),
|
||||
'file_name': os.path.basename(file_name),
|
||||
})
|
||||
res = self.import_wizard_obj.import_statement(self.cr, self.uid, wizard_id)
|
||||
statement_id = self.account_bank_statement_obj.search(self.cr, self.uid, eval(res['domain']))
|
||||
return self.account_bank_statement_obj.browse(self.cr, self.uid, statement_id)[0]
|
||||
res = self.import_wizard_obj.import_statement(
|
||||
self.cr, self.uid, wizard_id)
|
||||
statement_id = self.account_bank_statement_obj.search(
|
||||
self.cr, self.uid, eval(res['domain']))
|
||||
return self.account_bank_statement_obj.browse(
|
||||
self.cr, self.uid, statement_id)[0]
|
||||
|
||||
def test_simple_xls(self):
|
||||
"""Test import from xls
|
||||
"""
|
||||
self.prepare()
|
||||
file_name = self._filename_to_abs_filename(os.path.join("..", "data", "statement.xls"))
|
||||
file_name = self._filename_to_abs_filename(
|
||||
os.path.join("..", "data", "statement.xls"))
|
||||
statement = self._import_file(file_name)
|
||||
self._validate_imported_satement(statement)
|
||||
|
||||
@@ -87,7 +91,8 @@ class test_coda_import(common.TransactionCase):
|
||||
"""Test import from csv
|
||||
"""
|
||||
self.prepare()
|
||||
file_name = self._filename_to_abs_filename(os.path.join("..", "data", "statement.csv"))
|
||||
file_name = self._filename_to_abs_filename(
|
||||
os.path.join("..", "data", "statement.csv"))
|
||||
statement = self._import_file(file_name)
|
||||
self._validate_imported_satement(statement)
|
||||
|
||||
|
||||
@@ -36,12 +36,15 @@ class CreditPartnerStatementImporter(orm.TransientModel):
|
||||
if context is None:
|
||||
context = {}
|
||||
res = {}
|
||||
if (context.get('active_model', False) == 'account.statement.profile' and
|
||||
if (context.get('active_model', False) ==
|
||||
'account.statement.profile' and
|
||||
context.get('active_ids', False)):
|
||||
ids = context['active_ids']
|
||||
assert len(ids) == 1, 'You cannot use this on more than one profile !'
|
||||
assert len(
|
||||
ids) == 1, 'You cannot use this on more than one profile !'
|
||||
res['profile_id'] = ids[0]
|
||||
other_vals = self.onchange_profile_id(cr, uid, [], res['profile_id'], context=context)
|
||||
other_vals = self.onchange_profile_id(
|
||||
cr, uid, [], res['profile_id'], context=context)
|
||||
res.update(other_vals.get('value', {}))
|
||||
return res
|
||||
|
||||
@@ -55,8 +58,8 @@ class CreditPartnerStatementImporter(orm.TransientModel):
|
||||
'journal_id': fields.many2one('account.journal',
|
||||
'Financial journal to use transaction'),
|
||||
'file_name': fields.char('File Name', size=128),
|
||||
'receivable_account_id': fields.many2one('account.account',
|
||||
'Force Receivable/Payable Account'),
|
||||
'receivable_account_id': fields.many2one(
|
||||
'account.account', 'Force Receivable/Payable Account'),
|
||||
'force_partner_on_bank': fields.boolean(
|
||||
'Force partner on bank move',
|
||||
help="Tic that box if you want to use the credit insitute partner "
|
||||
@@ -71,12 +74,12 @@ class CreditPartnerStatementImporter(orm.TransientModel):
|
||||
def onchange_profile_id(self, cr, uid, ids, profile_id, context=None):
|
||||
res = {}
|
||||
if profile_id:
|
||||
c = self.pool.get("account.statement.profile").browse(
|
||||
c = self.pool["account.statement.profile"].browse(
|
||||
cr, uid, profile_id, context=context)
|
||||
res = {'value':
|
||||
{'partner_id': c.partner_id and c.partner_id.id or False,
|
||||
'journal_id': c.journal_id and c.journal_id.id or False,
|
||||
'receivable_account_id': c.receivable_account_id and c.receivable_account_id.id or False,
|
||||
'receivable_account_id': c.receivable_account_id.id,
|
||||
'force_partner_on_bank': c.force_partner_on_bank,
|
||||
'balance_check': c.balance_check,
|
||||
}
|
||||
@@ -86,7 +89,7 @@ class CreditPartnerStatementImporter(orm.TransientModel):
|
||||
def _check_extension(self, filename):
|
||||
(__, ftype) = os.path.splitext(filename)
|
||||
if not ftype:
|
||||
#We do not use osv exception we do not want to have it logged
|
||||
# We do not use osv exception we do not want to have it logged
|
||||
raise Exception(_('Please use a file with an extention'))
|
||||
return ftype
|
||||
|
||||
@@ -110,7 +113,8 @@ class CreditPartnerStatementImporter(orm.TransientModel):
|
||||
)
|
||||
model_obj = self.pool.get('ir.model.data')
|
||||
action_obj = self.pool.get('ir.actions.act_window')
|
||||
action_id = model_obj.get_object_reference(cr, uid, 'account', 'action_bank_statement_tree')[1]
|
||||
action_id = model_obj.get_object_reference(
|
||||
cr, uid, 'account', 'action_bank_statement_tree')[1]
|
||||
res = action_obj.read(cr, uid, action_id)
|
||||
res['domain'] = res['domain'][:-1] + ",('id', 'in', %s)]" % sid
|
||||
return res
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<record id="statement_importer_view" model="ir.ui.view">
|
||||
<field name="name">credit.statement.import.config.view</field>
|
||||
<field name="model">credit.statement.import</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Import statement">
|
||||
<group colspan="4" >
|
||||
|
||||
@@ -31,17 +31,18 @@
|
||||
'account_statement_base_import'
|
||||
],
|
||||
'description': """
|
||||
This module brings commission support to bank statement imports. It computes the sum of a commission
|
||||
field on each transaction and creates a statement entry for it.
|
||||
This module brings commission support to bank statement imports. It computes the
|
||||
sum of a commission field on each transaction and creates a statement entry for
|
||||
it.
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data': [
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data': [
|
||||
"statement_view.xml",
|
||||
"import_statement_view.xml",
|
||||
],
|
||||
'test': [],
|
||||
'installable': False,
|
||||
'images': [],
|
||||
'auto_install': False,
|
||||
'license': 'AGPL-3',
|
||||
],
|
||||
'test': [],
|
||||
'installable': False,
|
||||
'images': [],
|
||||
'auto_install': False,
|
||||
'license': 'AGPL-3',
|
||||
}
|
||||
|
||||
@@ -10,18 +10,18 @@ def float_or_zero(val):
|
||||
class AccountStatementProfil(orm.Model):
|
||||
_inherit = "account.statement.profile"
|
||||
|
||||
def _write_extra_statement_lines(
|
||||
self, cr, uid, parser, result_row_list, profile, statement_id, context):
|
||||
"""Prepare the global commission line if there is one.
|
||||
"""
|
||||
def _write_extra_statement_lines(self, cr, uid, parser, result_row_list,
|
||||
profile, statement_id, context=None):
|
||||
"""Prepare the global commission line if there is one."""
|
||||
global_commission_amount = 0
|
||||
for row in parser.result_row_list:
|
||||
global_commission_amount += float_or_zero(row.get('commission_amount', '0.0'))
|
||||
global_commission_amount += float_or_zero(
|
||||
row.get('commission_amount', '0.0'))
|
||||
if not global_commission_amount:
|
||||
return
|
||||
partner_id = profile.partner_id and profile.partner_id.id or False
|
||||
commission_account_id = profile.commission_account_id and profile.commission_account_id.id or False
|
||||
commission_analytic_id = profile.commission_analytic_id and profile.commission_analytic_id.id or False
|
||||
partner_id = profile.partner_id.id
|
||||
commission_account_id = profile.commission_account_id.id
|
||||
commission_analytic_id = profile.commission_analytic_id.id
|
||||
comm_values = {
|
||||
'name': 'IN ' + _('Commission line'),
|
||||
'date': parser.get_st_vals().get('date') or datetime.datetime.now(),
|
||||
@@ -32,11 +32,12 @@ class AccountStatementProfil(orm.Model):
|
||||
'account_id': commission_account_id,
|
||||
'ref': 'commission',
|
||||
'analytic_account_id': commission_analytic_id,
|
||||
# !! We set the already_completed so auto-completion will not update those values!
|
||||
# !! We set the already_completed so auto-completion will not update
|
||||
# those values!
|
||||
'already_completed': True,
|
||||
}
|
||||
statement_line_obj = self.pool.get('account.bank.statement.line')
|
||||
statement_line_obj.create(cr, uid, comm_values, context=context)
|
||||
st_obj = self.pool['account.bank.statement.line']
|
||||
st_obj.create(cr, uid, comm_values, context=context)
|
||||
|
||||
|
||||
class AccountStatementLineWithCommission(orm.Model):
|
||||
@@ -53,20 +54,18 @@ class CreditPartnerStatementImporter(orm.TransientModel):
|
||||
_inherit = "credit.statement.import"
|
||||
|
||||
_columns = {
|
||||
'commission_account_id': fields.many2one('account.account',
|
||||
'Commission account'),
|
||||
'commission_analytic_id': fields.many2one('account.analytic.account',
|
||||
'Commission analytic account'),
|
||||
'commission_account_id': fields.many2one(
|
||||
'account.account', 'Commission account'),
|
||||
'commission_analytic_id': fields.many2one(
|
||||
'account.analytic.account', 'Commission analytic account'),
|
||||
}
|
||||
|
||||
def onchange_profile_id(self, cr, uid, ids, profile_id, context=None):
|
||||
res = super(CreditPartnerStatementImporter, self).onchange_profile_id(
|
||||
cr, uid, ids, profile_id, context=context)
|
||||
if profile_id:
|
||||
c = self.pool.get("account.statement.profile").browse(
|
||||
p = self.pool["account.statement.profile"].browse(
|
||||
cr, uid, profile_id, context=context)
|
||||
res['value']['commission_account_id'] = \
|
||||
c.commission_account_id and c.commission_account_id.id or False
|
||||
res['value']['commission_a'] = \
|
||||
c.commission_analytic_id and c.commission_analytic_id.id or False
|
||||
res['value']['commission_account_id'] = p.commission_account_id.id
|
||||
res['value']['commission_a'] = p.commission_analytic_id.id
|
||||
return res
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<record id="statement_importer_view" model="ir.ui.view">
|
||||
<field name="name">credit.statement.import.config.view</field>
|
||||
<field name="model">credit.statement.import</field>
|
||||
<field name="type">form</field>
|
||||
<field name="inherit_id" ref="account_statement_base_import.statement_importer_view" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/form/group/field[@name='journal_id']" position="after">
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<field name="name">account_bank_statement.bank_statement.view_form</field>
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account_statement_base_completion.bank_statement_view_form" />
|
||||
<field name="type">form</field>
|
||||
<field eval="20" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
|
||||
@@ -22,4 +22,3 @@
|
||||
|
||||
from . import partner
|
||||
from . import statement
|
||||
|
||||
|
||||
@@ -26,10 +26,12 @@
|
||||
'category': 'Generic Modules/Others',
|
||||
'license': 'AGPL-3',
|
||||
'description': """
|
||||
Improve the basic rule "Match from statement line label (based on partner field 'Bank Statement Label')" provided by the
|
||||
Bank statement base completion module. The goal is to match the label field from the bank statement line with a partner and
|
||||
an account.
|
||||
For this, you have to create your record in the new class account.statement.label where you can link the label you want with a
|
||||
Improve the basic rule "Match from statement line label (based on partner
|
||||
field 'Bank Statement Label')" provided by the Bank statement base
|
||||
completion module. The goal is to match the label field from the bank
|
||||
statement line with a partner and an account.
|
||||
For this, you have to create your record in the new class
|
||||
account.statement.label where you can link the label you want with a
|
||||
partner and an account.
|
||||
|
||||
""",
|
||||
@@ -46,4 +48,3 @@
|
||||
'installable': False,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<record id="bk_view_partner_form" model="ir.ui.view">
|
||||
<field name="name">account_bank_statement_import.view.partner.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="type">form</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="account_statement_base_completion.bk_view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
@@ -22,14 +22,16 @@
|
||||
|
||||
from openerp.osv import fields, orm
|
||||
from collections import defaultdict
|
||||
from openerp.addons.account_statement_base_completion.statement import ErrorTooManyPartner
|
||||
from openerp.tools.translate import _
|
||||
from openerp.addons.account_statement_base_completion.statement import \
|
||||
ErrorTooManyPartner
|
||||
|
||||
|
||||
class ErrorTooManyLabel(Exception):
|
||||
"""New Exception definition that is raised when more than one label is
|
||||
matched by the completion rule.
|
||||
"""
|
||||
New Exception definition that is raised when more than one label is matched
|
||||
by the completion rule.
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
@@ -38,8 +40,7 @@ class ErrorTooManyLabel(Exception):
|
||||
|
||||
|
||||
class AccountBankSatement(orm.Model):
|
||||
"""
|
||||
We add a basic button and stuff to support the auto-completion
|
||||
"""We add a basic button and stuff to support the auto-completion
|
||||
of the bank statement once line have been imported or manually fullfill.
|
||||
"""
|
||||
_inherit = "account.bank.statement"
|
||||
@@ -60,8 +61,7 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
_inherit = "account.statement.completion.rule"
|
||||
|
||||
def get_from_label_and_partner_field(self, cr, uid, st_line, context=None):
|
||||
"""
|
||||
Match the partner and the account based on the name field of the
|
||||
"""Match the partner and the account based on the name field of the
|
||||
statement line and the table account.statement.label.
|
||||
If more than one statement label matched, raise the ErrorTooManylabel
|
||||
error.
|
||||
@@ -75,7 +75,7 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
|
||||
...}
|
||||
"""
|
||||
st_obj = self.pool.get('account.bank.statement')
|
||||
st_obj = self.pool['account.bank.statement']
|
||||
statement = st_obj.browse(cr, uid, st_line['statement_id'][0],
|
||||
context=context)
|
||||
res = {}
|
||||
@@ -99,14 +99,14 @@ class AccountStatementCompletionRule(orm.Model):
|
||||
st_l.id = %s
|
||||
""", (line.id,))
|
||||
for partner, account in cr.fetchall():
|
||||
context['label_memorizer'][line.id].append({'partner_id': partner,
|
||||
'account_id': account})
|
||||
context['label_memorizer'][line.id].append(
|
||||
{'partner_id': partner, 'account_id': account})
|
||||
if st_line['id'] in context['label_memorizer']:
|
||||
label_info = context['label_memorizer'][st_line['id']]
|
||||
if len(label_info) > 1:
|
||||
raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by '
|
||||
'more than one statement label.') %
|
||||
(st_line['name'], st_line['ref']))
|
||||
raise ErrorTooManyPartner(
|
||||
_('Line named "%s" (Ref:%s) was matched by more than one '
|
||||
'statement label.') % (st_line['name'], st_line['ref']))
|
||||
if label_info[0]['partner_id']:
|
||||
res['partner_id'] = label_info[0]['partner_id']
|
||||
res['account_id'] = label_info[0]['account_id']
|
||||
@@ -118,14 +118,13 @@ class AccountStatementLabel(orm.Model):
|
||||
and a specific account
|
||||
"""
|
||||
_name = "account.statement.label"
|
||||
|
||||
_description = "Account Statement Label"
|
||||
|
||||
_columns = {
|
||||
'partner_id': fields.many2one('res.partner', 'Partner'),
|
||||
'label': fields.char('Bank Statement Label', size=100),
|
||||
'account_id': fields.many2one('account.account', 'Account',
|
||||
required = True,
|
||||
required=True,
|
||||
help='Account corresponding to the label '
|
||||
'for a given partner'),
|
||||
'company_id': fields.related('account_id', 'company_id',
|
||||
@@ -139,10 +138,9 @@ class AccountStatementLabel(orm.Model):
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'company_id': lambda s,cr,uid,c:
|
||||
s.pool.get('res.company')._company_default_get(cr, uid,
|
||||
'account.statement.label',
|
||||
context=c),
|
||||
'company_id': lambda s, cr, uid, c:
|
||||
s.pool.get('res.company')._company_default_get(
|
||||
cr, uid, 'account.statement.label', context=c),
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
'account_voucher'
|
||||
],
|
||||
'description': """
|
||||
This module is only needed when using account_statement_base_completion with voucher in order adapt the view correctly.
|
||||
This module is only needed when using account_statement_base_completion with
|
||||
voucher in order adapt the view correctly.
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'init_xml': [],
|
||||
@@ -43,4 +44,4 @@
|
||||
'images': [],
|
||||
'auto_install': False,
|
||||
'license': 'AGPL-3',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,14 +29,14 @@
|
||||
'report_webkit',
|
||||
'account_voucher'],
|
||||
'description': """
|
||||
Improve the basic bank statement, by adding various new features,
|
||||
and help dealing with huge volume of reconciliation through payment offices such as Paypal, Lazer,
|
||||
Visa, Amazon...
|
||||
Improve the basic bank statement, by adding various new features, and help
|
||||
dealing with huge volume of reconciliation through payment offices such as
|
||||
Paypal, Lazer, Visa, Amazon...
|
||||
|
||||
It is mostly used for E-commerce but can be useful for other use cases as it introduces a
|
||||
notion of profile on the bank statement to have more control on the generated entries. It serves as
|
||||
a base for all new features developped to improve the reconciliation process (see our other
|
||||
set of modules:
|
||||
It is mostly used for E-commerce but can be useful for other use cases as it
|
||||
introduces a notion of profile on the bank statement to have more control on
|
||||
the generated entries. It serves as a base for all new features developped to
|
||||
improve the reconciliation process (see our other set of modules:
|
||||
|
||||
* account_statement_base_completion
|
||||
* account_statement_base_import
|
||||
@@ -44,33 +44,38 @@
|
||||
|
||||
Features:
|
||||
|
||||
1) Improve the bank statement: allows to define profiles (for each
|
||||
Office or Bank). The bank statement will then generate the entries based on some criteria chosen
|
||||
in the selected profile. You can setup on the profile:
|
||||
1) Improve the bank statement: allows to define profiles (for each Office or
|
||||
Bank). The bank statement will then generate the entries based on some criteria
|
||||
chosen in the selected profile. You can setup on the profile:
|
||||
|
||||
- the journal to use
|
||||
- use balance check or not
|
||||
- account commission and Analytic account for commission
|
||||
- partner concerned by the profile (used in commission and optionaly on generated credit move)
|
||||
- use a specific credit account (instead of the receivalble/payable default one)
|
||||
- force Partner on the counter-part move (e.g. 100.- debit, Partner: M.Martin; 100.- credit, Partner: HSBC)
|
||||
- partner concerned by the profile (used in commission and optionaly on
|
||||
generated credit move)
|
||||
- use a specific credit account (instead of the receivalble/payable default
|
||||
one)
|
||||
- force Partner on the counter-part move (e.g. 100.- debit, Partner: M.
|
||||
Martin; 100.- credit, Partner: HSBC)
|
||||
|
||||
2) Add a report on bank statement that can be used for checks remittance
|
||||
|
||||
3) When an error occurs in a bank statement confirmation, go through all line anyway and summarize
|
||||
all the erronous line in a same popup instead of raising and crashing on every step.
|
||||
3) When an error occurs in a bank statement confirmation, go through all line
|
||||
anyway and summarize all the erronous line in a same popup instead of
|
||||
raising and crashing on every step.
|
||||
|
||||
4) Remove the period on the bank statement, and compute it for each line based on their date instead.
|
||||
It also adds this feature in the voucher in order to compute the period correctly.
|
||||
4) Remove the period on the bank statement, and compute it for each line based
|
||||
on their date instead. It also adds this feature in the voucher in order to
|
||||
compute the period correctly.
|
||||
|
||||
5) Cancelling a bank statement is much more easy and will cancel all related entries, unreconcile them,
|
||||
and finally delete them.
|
||||
|
||||
6) Add the ID in entries view so that you can easily filter on a statement ID to reconcile all related
|
||||
entries at once (e.g. one statement (ID 100) for paypal on an intermediate account, and then another for
|
||||
the bank on the bank account. You can then manually reconcile all the line from the first one with
|
||||
one line of the second by finding them through the statement ID.)
|
||||
5) Cancelling a bank statement is much more easy and will cancel all related
|
||||
entries, unreconcile them, and finally delete them.
|
||||
|
||||
6) Add the ID in entries view so that you can easily filter on a statement ID
|
||||
to reconcile all related entries at once (e.g. one statement (ID 100) for
|
||||
Paypal on an intermediate account, and then another for the bank on the
|
||||
bank account. You can then manually reconcile all the line from the first
|
||||
one with one line of the second by finding them through the statement ID.)
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data': ['statement_view.xml',
|
||||
|
||||
@@ -19,23 +19,21 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv.orm import Model
|
||||
from openerp.osv import fields
|
||||
from openerp.osv import orm
|
||||
|
||||
|
||||
class account_move(Model):
|
||||
class AccountMove(orm.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Delete the reconciliation when we delete the moves. This
|
||||
"""Delete the reconciliation when we delete the moves. This
|
||||
allow an easier way of cancelling the bank statement.
|
||||
"""
|
||||
reconcile_to_delete = []
|
||||
reconcile_obj = self.pool.get('account.move.reconcile')
|
||||
reconcile_obj = self.pool['account.move.reconcile']
|
||||
for move in self.browse(cr, uid, ids, context=context):
|
||||
for move_line in move.line_id:
|
||||
if move_line.reconcile_id:
|
||||
reconcile_to_delete.append(move_line.reconcile_id.id)
|
||||
reconcile_obj.unlink(cr, uid, reconcile_to_delete, context=context)
|
||||
return super(account_move, self).unlink(cr, uid, ids, context=context)
|
||||
return super(AccountMove, self).unlink(cr, uid, ids, context=context)
|
||||
|
||||
@@ -28,15 +28,18 @@ from openerp.addons.report_webkit import webkit_report
|
||||
class BankStatementWebkit(report_sxw.rml_parse):
|
||||
|
||||
def __init__(self, cr, uid, name, context):
|
||||
super(BankStatementWebkit, self).__init__(cr, uid, name, context=context)
|
||||
super(BankStatementWebkit, self).__init__(
|
||||
cr, uid, name, context=context)
|
||||
self.pool = pooler.get_pool(self.cr.dbname)
|
||||
self.cursor = self.cr
|
||||
|
||||
company = self.pool.get('res.users').browse(
|
||||
self.cr, uid, uid, context=context).company_id
|
||||
header_report_name = ' - '.join((_('BORDEREAU DE REMISE DE CHEQUES'),
|
||||
header_report_name = ' - '.join((
|
||||
_('BORDEREAU DE REMISE DE CHEQUES'),
|
||||
company.name, company.currency_id.name))
|
||||
footer_date_time = self.formatLang(str(datetime.today())[:19], date_time=True)
|
||||
footer_date_time = self.formatLang(
|
||||
str(datetime.today())[:19], date_time=True)
|
||||
self.localcontext.update({
|
||||
'cr': cr,
|
||||
'uid': uid,
|
||||
@@ -50,7 +53,8 @@ class BankStatementWebkit(report_sxw.rml_parse):
|
||||
('--header-left', header_report_name),
|
||||
('--header-spacing', '2'),
|
||||
('--footer-left', footer_date_time),
|
||||
('--footer-right', ' '.join((_('Page'), '[page]', _('of'), '[topage]'))),
|
||||
('--footer-right',
|
||||
' '.join((_('Page'), '[page]', _('of'), '[topage]'))),
|
||||
('--footer-line',),
|
||||
],
|
||||
})
|
||||
@@ -65,7 +69,7 @@ class BankStatementWebkit(report_sxw.rml_parse):
|
||||
self.cr, self.uid, statement_line_ids)
|
||||
return statement_lines
|
||||
|
||||
webkit_report.WebKitParser('report.bank_statement_webkit',
|
||||
'account.bank.statement',
|
||||
webkit_report.WebKitParser(
|
||||
'report.bank_statement_webkit', 'account.bank.statement',
|
||||
'addons/account_statement_ext/report/bank_statement_report.mako',
|
||||
parser=BankStatementWebkit)
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
import openerp.addons.account.account_bank_statement as stat_mod
|
||||
from openerp.osv.orm import Model
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.osv import fields, orm
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
@@ -29,8 +28,8 @@ def fixed_write(self, cr, uid, ids, vals, context=None):
|
||||
""" Fix performance desing of original function
|
||||
Ideally we should use a real PostgreSQL sequence or serial fields.
|
||||
I will do it when I have time."""
|
||||
res = super(stat_mod.account_bank_statement, self).write(cr, uid, ids,
|
||||
vals, context=context)
|
||||
res = super(stat_mod.account_bank_statement, self).write(
|
||||
cr, uid, ids, vals, context=context)
|
||||
if ids: # will be false for an new empty bank statement
|
||||
cr.execute("UPDATE account_bank_statement_line"
|
||||
" SET sequence = account_bank_statement_line.id + 1"
|
||||
@@ -39,64 +38,56 @@ def fixed_write(self, cr, uid, ids, vals, context=None):
|
||||
stat_mod.account_bank_statement.write = fixed_write
|
||||
|
||||
|
||||
class AccountStatementProfile(Model):
|
||||
"""
|
||||
A Profile will contain all infos related to the type of
|
||||
class AccountStatementProfile(orm.Model):
|
||||
"""A Profile will contain all infos related to the type of
|
||||
bank statement, and related generated entries. It defines the
|
||||
journal to use, the partner and commision account and so on.
|
||||
"""
|
||||
_name = "account.statement.profile"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
_description = "Statement Profile"
|
||||
_order = 'sequence'
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True),
|
||||
'sequence': fields.integer('Sequence', help="Gives a sequence in lists, the first profile will be used as default"),
|
||||
'sequence': fields.integer(
|
||||
'Sequence',
|
||||
help="Gives a sequence in lists, the first profile will be used as "
|
||||
"default"),
|
||||
'partner_id': fields.many2one(
|
||||
'res.partner',
|
||||
'Bank/Payment Office partner',
|
||||
help="Put a partner if you want to have it on the "
|
||||
"commission move (and optionaly on the counterpart "
|
||||
"of the intermediate/banking move if you tick the "
|
||||
"corresponding checkbox)."),
|
||||
|
||||
help="Put a partner if you want to have it on the commission move "
|
||||
"(and optionaly on the counterpart of the intermediate/"
|
||||
"banking move if you tick the corresponding checkbox)."),
|
||||
'journal_id': fields.many2one(
|
||||
'account.journal',
|
||||
'Financial journal to use for transaction',
|
||||
required=True),
|
||||
|
||||
'commission_account_id': fields.many2one(
|
||||
'account.account',
|
||||
'Commission account',
|
||||
required=True),
|
||||
|
||||
'commission_analytic_id': fields.many2one(
|
||||
'account.analytic.account',
|
||||
'Commission analytic account'),
|
||||
|
||||
'receivable_account_id': fields.many2one(
|
||||
'account.account',
|
||||
'Force Receivable/Payable Account',
|
||||
help="Choose a receivable account to force the default "
|
||||
"debit/credit account (eg. an intermediat bank account "
|
||||
"instead of default debitors)."),
|
||||
|
||||
'force_partner_on_bank': fields.boolean(
|
||||
'Force partner on bank move',
|
||||
help="Tick that box if you want to use the credit "
|
||||
"institute partner in the counterpart of the "
|
||||
"intermediate/banking move."),
|
||||
|
||||
'balance_check': fields.boolean(
|
||||
'Balance check',
|
||||
help="Tick that box if you want OpenERP to control "
|
||||
"the start/end balance before confirming a bank statement. "
|
||||
"If don't ticked, no balance control will be done."),
|
||||
|
||||
'bank_statement_prefix': fields.char('Bank Statement Prefix', size=32),
|
||||
|
||||
'bank_statement_ids': fields.one2many('account.bank.statement',
|
||||
'profile_id',
|
||||
'Bank Statement Imported'),
|
||||
@@ -110,53 +101,50 @@ class AccountStatementProfile(Model):
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_check_partner, "You need to put a partner if you tic the 'Force partner on bank move'!", []),
|
||||
(_check_partner,
|
||||
"You need to put a partner if you tic the 'Force partner on bank "
|
||||
"move'!", []),
|
||||
]
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name, company_id)', 'The name of the bank statement must be unique !')
|
||||
('name_uniq', 'unique (name, company_id)',
|
||||
'The name of the bank statement must be unique !')
|
||||
]
|
||||
|
||||
|
||||
|
||||
class AccountBankStatement(Model):
|
||||
"""
|
||||
We improve the bank statement class mostly for :
|
||||
class AccountBankStatement(orm.Model):
|
||||
"""We improve the bank statement class mostly for :
|
||||
- Removing the period and compute it from the date of each line.
|
||||
- Allow to remove the balance check depending on the chosen profile
|
||||
- Report errors on confirmation all at once instead of crashing onr by one
|
||||
- Add a profile notion that can change the generated entries on statement
|
||||
confirmation.
|
||||
For this, we had to override quite some long method and we'll need to maintain
|
||||
them up to date. Changes are point up by '#Chg' comment.
|
||||
For this, we had to override quite some long method and we'll need to
|
||||
maintain them up to date. Changes are point up by '#Chg' comment.
|
||||
"""
|
||||
|
||||
_inherit = "account.bank.statement"
|
||||
|
||||
def _default_period(self, cr, uid, context=None):
|
||||
"""
|
||||
Statement default period
|
||||
"""
|
||||
"""Statement default period"""
|
||||
if context is None:
|
||||
context = {}
|
||||
period_obj = self.pool.get('account.period')
|
||||
periods = period_obj.find(cr, uid, dt=context.get('date'), context=context)
|
||||
periods = period_obj.find(
|
||||
cr, uid, dt=context.get('date'), context=context)
|
||||
return periods and periods[0] or False
|
||||
|
||||
def _default_profile(self, cr, uid, context=None):
|
||||
"""
|
||||
Returns the default statement profile
|
||||
"""Returns the default statement profile
|
||||
Default profile is the one with the lowest sequence of user's company
|
||||
|
||||
:return profile_id or False
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
user_obj = self.pool.get('res.users')
|
||||
profile_obj = self.pool.get('account.statement.profile')
|
||||
user_obj = self.pool['res.users']
|
||||
profile_obj = self.pool['account.statement.profile']
|
||||
user = user_obj.browse(cr, uid, uid, context=context)
|
||||
profile_ids = profile_obj.search(cr, uid, [('company_id', '=', user.company_id.id)], context=context)
|
||||
|
||||
profile_ids = profile_obj.search(
|
||||
cr, uid, [('company_id', '=', user.company_id.id)], context=context)
|
||||
return profile_ids[0] if profile_ids else False
|
||||
|
||||
def _get_statement_from_profile(self, cr, uid, profile_ids, context=None):
|
||||
@@ -166,7 +154,6 @@ class AccountBankStatement(Model):
|
||||
when the ORM calls this, self is an account.statement.profile.
|
||||
|
||||
Returns a list of account.bank.statement ids to recompute.
|
||||
|
||||
"""
|
||||
triggered = []
|
||||
for profile in self.browse(cr, uid, profile_ids, context=context):
|
||||
@@ -232,28 +219,28 @@ class AccountBankStatement(Model):
|
||||
}
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
"""Need to pass the journal_id in vals anytime because of account.cash.statement
|
||||
need it."""
|
||||
"""Need to pass the journal_id in vals anytime because of
|
||||
account.cash.statement need it."""
|
||||
if 'profile_id' in vals:
|
||||
profile_obj = self.pool.get('account.statement.profile')
|
||||
profile = profile_obj.browse(cr, uid, vals['profile_id'], context=context)
|
||||
profile_obj = self.pool['account.statement.profile']
|
||||
profile = profile_obj.browse(
|
||||
cr, uid, vals['profile_id'], context=context)
|
||||
vals['journal_id'] = profile.journal_id.id
|
||||
return super(AccountBankStatement, self
|
||||
).create(cr, uid, vals, context=context)
|
||||
return super(AccountBankStatement, self).create(
|
||||
cr, uid, vals, context=context)
|
||||
|
||||
def _get_period(self, cr, uid, date, context=None):
|
||||
"""Return matching period for a date."""
|
||||
if context is None:
|
||||
context = {}
|
||||
period_obj = self.pool.get('account.period')
|
||||
period_obj = self.pool['account.period']
|
||||
local_context = context.copy()
|
||||
local_context['account_period_prefer_normal'] = True
|
||||
periods = period_obj.find(cr, uid, dt=date, context=local_context)
|
||||
return periods and periods[0] or False
|
||||
|
||||
def _check_company_id(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Adapt this constraint method from the account module to reflect the
|
||||
"""Adapt this constraint method from the account module to reflect the
|
||||
move of period_id to the statement line
|
||||
"""
|
||||
for statement in self.browse(cr, uid, ids, context=context):
|
||||
@@ -278,18 +265,18 @@ class AccountBankStatement(Model):
|
||||
]
|
||||
|
||||
def _prepare_move(self, cr, uid, st_line, st_line_number, context=None):
|
||||
"""Add the period_id from the statement line date to the move preparation.
|
||||
Originaly, it was taken from the statement period_id
|
||||
:param browse_record st_line: account.bank.statement.line record to
|
||||
create the move from.
|
||||
:param char st_line_number: will be used as the name of the generated account move
|
||||
"""Add the period_id from the statement line date to the move
|
||||
preparation. Originaly, it was taken from the statement period_id
|
||||
:param browse_record st_line: account.bank.statement.line record
|
||||
to create the move from.
|
||||
:param char st_line_number: will be used as the name of the
|
||||
generated account move
|
||||
:return: dict of value to create() the account.move
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
res = super(AccountBankStatement, self
|
||||
)._prepare_move(cr, uid, st_line, st_line_number,
|
||||
context=context)
|
||||
res = super(AccountBankStatement, self)._prepare_move(
|
||||
cr, uid, st_line, st_line_number, context=context)
|
||||
ctx = context.copy()
|
||||
ctx['company_id'] = st_line.company_id.id
|
||||
period_id = self._get_period(cr, uid, st_line.date, context=ctx)
|
||||
@@ -300,19 +287,23 @@ class AccountBankStatement(Model):
|
||||
self, cr, uid, st_line, move_id, debit, credit, currency_id=False,
|
||||
amount_currency=False, account_id=False, analytic_id=False,
|
||||
partner_id=False, context=None):
|
||||
"""Add the period_id from the statement line date to the move preparation.
|
||||
Originaly, it was taken from the statement period_id
|
||||
"""Add the period_id from the statement line date to the move
|
||||
preparation. Originaly, it was taken from the statement period_id
|
||||
|
||||
:param browse_record st_line: account.bank.statement.line record to
|
||||
create the move from.
|
||||
:param int/long move_id: ID of the account.move to link the move line
|
||||
:param browse_record st_line: account.bank.statement.line record
|
||||
to create the move from.
|
||||
:param int/long move_id: ID of the account.move to link the move
|
||||
line
|
||||
:param float debit: debit amount of the move line
|
||||
:param float credit: credit amount of the move line
|
||||
:param int/long currency_id: ID of currency of the move line to create
|
||||
:param float amount_currency: amount of the debit/credit expressed in the currency_id
|
||||
:param int/long account_id: ID of the account to use in the move line if different
|
||||
from the statement line account ID
|
||||
:param int/long analytic_id: ID of analytic account to put on the move line
|
||||
:param int/long currency_id: ID of currency of the move line to
|
||||
create
|
||||
:param float amount_currency: amount of the debit/credit expressed
|
||||
in the currency_id
|
||||
:param int/long account_id: ID of the account to use in the move
|
||||
line if different from the statement line account ID
|
||||
:param int/long analytic_id: ID of analytic account to put on the
|
||||
move line
|
||||
:param int/long partner_id: ID of the partner to put on the move line
|
||||
:return: dict of value to create() the account.move.line
|
||||
"""
|
||||
@@ -332,12 +323,13 @@ class AccountBankStatement(Model):
|
||||
return res
|
||||
|
||||
def _get_counter_part_partner(self, cr, uid, st_line, context=None):
|
||||
"""
|
||||
We change the move line generated from the lines depending on the profile:
|
||||
- If partner_id is set and force_partner_on_bank is ticked, we'll let the partner of each line
|
||||
for the debit line, but we'll change it on the credit move line for the choosen partner_id
|
||||
=> This will ease the reconciliation process with the bank as the partner will match the bank
|
||||
statement line
|
||||
"""We change the move line generated from the lines depending on the
|
||||
profile:
|
||||
- If partner_id is set and force_partner_on_bank is ticked, we'll let
|
||||
the partner of each line for the debit line, but we'll change it on
|
||||
the credit move line for the choosen partner_id
|
||||
=> This will ease the reconciliation process with the bank as the
|
||||
partner will match the bank statement line
|
||||
:param browse_record st_line: account.bank.statement.line record to
|
||||
create the move from.
|
||||
:return: int/long of the res.partner to use as counterpart
|
||||
@@ -351,38 +343,38 @@ class AccountBankStatement(Model):
|
||||
return bank_partner_id
|
||||
|
||||
def _get_st_number_period_profile(self, cr, uid, date, profile_id):
|
||||
"""
|
||||
Retrieve the name of bank statement from sequence, according to the period
|
||||
corresponding to the date passed in args. Add a prefix if set in the profile.
|
||||
"""Retrieve the name of bank statement from sequence, according to the
|
||||
period corresponding to the date passed in args. Add a prefix if set in
|
||||
the profile.
|
||||
|
||||
:param: date: date of the statement used to compute the right period
|
||||
:param: int/long: profile_id: the account.statement.profile ID from which to take the
|
||||
bank_statement_prefix for the name
|
||||
:param: int/long: profile_id: the account.statement.profile ID from
|
||||
which to take the bank_statement_prefix for the name
|
||||
:return: char: name of the bank statement (st_number)
|
||||
|
||||
"""
|
||||
year = self.pool.get('account.period').browse(
|
||||
year = self.pool['account.period'].browse(
|
||||
cr, uid, self._get_period(cr, uid, date)).fiscalyear_id.id
|
||||
profile = self.pool.get('account.statement.profile').browse(cr, uid, profile_id)
|
||||
profile = self.pool.get(
|
||||
'account.statement.profile').browse(cr, uid, profile_id)
|
||||
c = {'fiscalyear_id': year}
|
||||
obj_seq = self.pool.get('ir.sequence')
|
||||
obj_seq = self.pool['ir.sequence']
|
||||
journal_sequence_id = (profile.journal_id.sequence_id and
|
||||
profile.journal_id.sequence_id.id or False)
|
||||
if journal_sequence_id:
|
||||
st_number = obj_seq.next_by_id(cr, uid, journal_sequence_id, context=c)
|
||||
st_number = obj_seq.next_by_id(
|
||||
cr, uid, journal_sequence_id, context=c)
|
||||
else:
|
||||
st_number = obj_seq.next_by_code(cr, uid, 'account.bank.statement', context=c)
|
||||
st_number = obj_seq.next_by_code(
|
||||
cr, uid, 'account.bank.statement', context=c)
|
||||
if profile.bank_statement_prefix:
|
||||
st_number = profile.bank_statement_prefix + st_number
|
||||
return st_number
|
||||
|
||||
def button_confirm_bank(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Completely override the method in order to have
|
||||
an error message which displays all the messages
|
||||
instead of having them pop one by one.
|
||||
We have to copy paste a big block of code, changing the error
|
||||
stack + managing period from date.
|
||||
"""Completely override the method in order to have an error message
|
||||
which displays all the messages instead of having them pop one by one.
|
||||
We have to copy paste a big block of code, changing the error stack +
|
||||
managing period from date.
|
||||
|
||||
TODO: Log the error in a bank statement field instead of using a popup!
|
||||
"""
|
||||
@@ -390,73 +382,78 @@ class AccountBankStatement(Model):
|
||||
|
||||
j_type = st.journal_id.type
|
||||
company_currency_id = st.journal_id.company_id.currency_id.id
|
||||
if not self.check_status_condition(cr, uid, st.state, journal_type=j_type):
|
||||
if not self.check_status_condition(cr, uid, st.state,
|
||||
journal_type=j_type):
|
||||
continue
|
||||
|
||||
self.balance_check(cr, uid, st.id, journal_type=j_type, context=context)
|
||||
self.balance_check(
|
||||
cr, uid, st.id, journal_type=j_type, context=context)
|
||||
if (not st.journal_id.default_credit_account_id) \
|
||||
or (not st.journal_id.default_debit_account_id):
|
||||
raise osv.except_osv(_('Configuration Error!'),
|
||||
_('Please verify that an account is defined in the journal.'))
|
||||
|
||||
raise orm.except_orm(
|
||||
_('Configuration Error!'),
|
||||
_('Please verify that an account is defined in the '
|
||||
'journal.'))
|
||||
if not st.name == '/':
|
||||
st_number = st.name
|
||||
else:
|
||||
# Begin Changes
|
||||
st_number = self._get_st_number_period_profile(cr, uid, st.date, st.profile_id.id)
|
||||
# End Changes
|
||||
# Begin Changes
|
||||
st_number = self._get_st_number_period_profile(
|
||||
cr, uid, st.date, st.profile_id.id)
|
||||
for line in st.move_line_ids:
|
||||
if line.state != 'valid':
|
||||
raise osv.except_osv(_('Error!'),
|
||||
raise orm.except_orm(
|
||||
_('Error!'),
|
||||
_('The account entries lines are not in valid state.'))
|
||||
# begin changes
|
||||
errors_stack = []
|
||||
for st_line in st.line_ids:
|
||||
try:
|
||||
if st_line.analytic_account_id:
|
||||
if not st.journal_id.analytic_journal_id:
|
||||
raise osv.except_osv(_('No Analytic Journal!'),
|
||||
_("You have to assign an analytic"
|
||||
" journal on the '%s' journal!") % st.journal_id.name)
|
||||
raise orm.except_orm(
|
||||
_('No Analytic Journal!'),
|
||||
_("You have to assign an analytic journal on "
|
||||
"the '%s' journal!") % st.journal_id.name)
|
||||
if not st_line.amount:
|
||||
continue
|
||||
st_line_number = self.get_next_st_line_number(cr, uid, st_number, st_line, context)
|
||||
self.create_move_from_st_line(cr, uid, st_line.id,
|
||||
company_currency_id,
|
||||
st_line_number,
|
||||
context)
|
||||
except osv.except_osv, exc:
|
||||
msg = "Line ID %s with ref %s had following error: %s" % (st_line.id, st_line.ref, exc.value)
|
||||
st_line_number = self.get_next_st_line_number(
|
||||
cr, uid, st_number, st_line, context)
|
||||
self.create_move_from_st_line(
|
||||
cr, uid, st_line.id, company_currency_id,
|
||||
st_line_number, context)
|
||||
except orm.except_orm, exc:
|
||||
msg = "Line ID %s with ref %s had following error: %s" % (
|
||||
st_line.id, st_line.ref, exc.value)
|
||||
errors_stack.append(msg)
|
||||
except Exception, exc:
|
||||
msg = "Line ID %s with ref %s had following error: %s" % (st_line.id, st_line.ref, str(exc))
|
||||
msg = "Line ID %s with ref %s had following error: %s" % (
|
||||
st_line.id, st_line.ref, str(exc))
|
||||
errors_stack.append(msg)
|
||||
if errors_stack:
|
||||
msg = u"\n".join(errors_stack)
|
||||
raise osv.except_osv(_('Error'), msg)
|
||||
# end changes
|
||||
raise orm.except_orm(_('Error'), msg)
|
||||
self.write(cr, uid, [st.id],
|
||||
{'name': st_number,
|
||||
'balance_end_real': st.balance_end},
|
||||
context=context)
|
||||
body = _('Statement %s confirmed, journal items were created.') % st_number
|
||||
body = _('Statement %s confirmed, journal items were '
|
||||
'created.') % st_number
|
||||
self.message_post(cr, uid, [st.id],
|
||||
body,
|
||||
context=context)
|
||||
return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
|
||||
|
||||
def get_account_for_counterpart(self, cr, uid, amount, account_receivable, account_payable):
|
||||
def get_account_for_counterpart(self, cr, uid, amount, account_receivable,
|
||||
account_payable):
|
||||
"""For backward compatibility."""
|
||||
account_id, type = self.get_account_and_type_for_counterpart(cr, uid, amount,
|
||||
account_receivable,
|
||||
account_payable)
|
||||
account_id, type = self.get_account_and_type_for_counterpart(
|
||||
cr, uid, amount, account_receivable, account_payable)
|
||||
return account_id
|
||||
|
||||
def _compute_type_from_partner_profile(self, cr, uid, partner_id,
|
||||
default_type, context=None):
|
||||
"""Compute the statement line type
|
||||
from partner profile (customer, supplier)"""
|
||||
obj_partner = self.pool.get('res.partner')
|
||||
obj_partner = self.pool['res.partner']
|
||||
part = obj_partner.browse(cr, uid, partner_id, context=context)
|
||||
if part.supplier == part.customer:
|
||||
return default_type
|
||||
@@ -476,71 +473,83 @@ class AccountBankStatement(Model):
|
||||
def get_type_for_counterpart(self, cr, uid, amount, partner_id=False):
|
||||
"""Give the amount and receive the type to use for the line.
|
||||
The rules are:
|
||||
- If the customer checkbox is checked on the found partner, type customer
|
||||
- If the supplier checkbox is checked on the found partner, typewill be supplier
|
||||
- If both checkbox are checked or none of them, it'll be based on the amount :
|
||||
- If the customer checkbox is checked on the found partner, type
|
||||
customer
|
||||
- If the supplier checkbox is checked on the found partner, typewill
|
||||
be supplier
|
||||
- If both checkbox are checked or none of them, it'll be based on the
|
||||
amount:
|
||||
If amount is positif the type customer,
|
||||
If amount is negativ, the type supplier
|
||||
:param float: amount of the line
|
||||
:param int/long: partner_id the partner id
|
||||
:return: type as string: the default type to use: 'customer' or 'supplier'.
|
||||
:return: type as string: the default type to use: 'customer' or
|
||||
'supplier'.
|
||||
"""
|
||||
s_line_type = self._compute_type_from_amount(cr, uid, amount)
|
||||
if partner_id:
|
||||
s_line_type = self._compute_type_from_partner_profile(cr, uid,
|
||||
partner_id, s_line_type)
|
||||
s_line_type = self._compute_type_from_partner_profile(
|
||||
cr, uid, partner_id, s_line_type)
|
||||
return s_line_type
|
||||
|
||||
def get_account_and_type_for_counterpart(self, cr, uid, amount, account_receivable,
|
||||
account_payable, partner_id=False):
|
||||
def get_account_and_type_for_counterpart(
|
||||
self, cr, uid, amount, account_receivable, account_payable,
|
||||
partner_id=False):
|
||||
"""
|
||||
Give the amount, payable and receivable account (that can be found using
|
||||
get_default_pay_receiv_accounts method) and receive the one to use. This method
|
||||
should be use when there is no other way to know which one to take.
|
||||
The rules are:
|
||||
- If the customer checkbox is checked on the found partner, type and account will be customer and receivable
|
||||
- If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
|
||||
- If both checkbox are checked or none of them, it'll be based on the amount :
|
||||
If amount is positive, the type and account will be customer and receivable,
|
||||
If amount is negative, the type and account will be supplier and payable
|
||||
Note that we return the payable or receivable account from agrs and not from the optional partner_id
|
||||
given!
|
||||
get_default_pay_receiv_accounts method) and receive the one to use. This
|
||||
method should be use when there is no other way to know which one to
|
||||
take. The rules are:
|
||||
- If the customer checkbox is checked on the found partner, type and
|
||||
account will be customer and receivable
|
||||
- If the supplier checkbox is checked on the found partner, type and
|
||||
account will be supplier and payable
|
||||
- If both checkbox are checked or none of them, it'll be based on the
|
||||
amount:
|
||||
If amount is positive, the type and account will be customer and
|
||||
receivable,
|
||||
If amount is negative, the type and account will be supplier and
|
||||
payable
|
||||
Note that we return the payable or receivable account from agrs and not
|
||||
from the optional partner_id given!
|
||||
|
||||
:param float: amount of the line
|
||||
:param int/long: account_receivable the receivable account
|
||||
:param int/long: account_payable the payable account
|
||||
:param int/long: partner_id the partner id
|
||||
:return: dict with [account_id as int/long,type as string]: the default account to be used by
|
||||
statement line as the counterpart of the journal account depending on the amount and the type
|
||||
as 'customer' or 'supplier'.
|
||||
:return: dict with [account_id as int/long,type as string]: the
|
||||
default account to be used by statement line as the counterpart of
|
||||
the journal account depending on the amount and the type as
|
||||
'customer' or 'supplier'.
|
||||
"""
|
||||
account_id = False
|
||||
ltype = self.get_type_for_counterpart(cr, uid, amount, partner_id=partner_id)
|
||||
ltype = self.get_type_for_counterpart(
|
||||
cr, uid, amount, partner_id=partner_id)
|
||||
if ltype == 'supplier':
|
||||
account_id = account_payable
|
||||
else:
|
||||
account_id = account_receivable
|
||||
if not account_id:
|
||||
raise osv.except_osv(
|
||||
raise orm.except_orm(
|
||||
_('Can not determine account'),
|
||||
_('Please ensure that minimal properties are set')
|
||||
)
|
||||
_('Please ensure that minimal properties are set'))
|
||||
return [account_id, ltype]
|
||||
|
||||
def get_default_pay_receiv_accounts(self, cr, uid, context=None):
|
||||
"""
|
||||
We try to determine default payable/receivable accounts to be used as counterpart
|
||||
from the company default propoerty. This is to be used if there is no otherway to
|
||||
find the good one, or to find a default value that will be overriden by a completion
|
||||
method (rules of account_statement_base_completion) afterwards.
|
||||
We try to determine default payable/receivable accounts to be used as
|
||||
counterpart from the company default propoerty. This is to be used if
|
||||
there is no otherway to find the good one, or to find a default value
|
||||
that will be overriden by a completion method (rules of
|
||||
account_statement_base_completion) afterwards.
|
||||
|
||||
:return: tuple of int/long ID that give account_receivable, account_payable based on
|
||||
company default.
|
||||
:return: tuple of int/long ID that give account_receivable,
|
||||
account_payable based on company default.
|
||||
"""
|
||||
|
||||
property_obj = self.pool.get('ir.property')
|
||||
account_receivable = property_obj.get(cr, uid, 'property_account_receivable',
|
||||
'res.partner', context=context)
|
||||
property_obj = self.pool['ir.property']
|
||||
account_receivable = property_obj.get(
|
||||
cr, uid, 'property_account_receivable', 'res.partner',
|
||||
context=context)
|
||||
account_payable = property_obj.get(cr, uid, 'property_account_payable',
|
||||
'res.partner', context=context)
|
||||
|
||||
@@ -549,8 +558,8 @@ class AccountBankStatement(Model):
|
||||
|
||||
def balance_check(self, cr, uid, st_id, journal_type='bank', context=None):
|
||||
"""
|
||||
Balance check depends on the profile. If no check for this profile is required,
|
||||
return True and do nothing, otherwise call super.
|
||||
Balance check depends on the profile. If no check for this profile is
|
||||
required, return True and do nothing, otherwise call super.
|
||||
|
||||
:param int/long st_id: ID of the concerned account.bank.statement
|
||||
:param char: journal_type that concern the bank statement
|
||||
@@ -565,27 +574,25 @@ class AccountBankStatement(Model):
|
||||
return True
|
||||
|
||||
def onchange_imp_config_id(self, cr, uid, ids, profile_id, context=None):
|
||||
"""
|
||||
Compute values on the change of the profile.
|
||||
"""Compute values on the change of the profile.
|
||||
|
||||
:param: int/long: profile_id that changed
|
||||
:return dict of dict with key = name of the field
|
||||
"""
|
||||
if not profile_id:
|
||||
return {}
|
||||
import_config = self.pool.get("account.statement.profile").browse(
|
||||
import_config = self.pool["account.statement.profile"].browse(
|
||||
cr, uid, profile_id, context=context)
|
||||
journal_id = import_config.journal_id.id
|
||||
return {'value': {'journal_id': journal_id,
|
||||
'balance_check': import_config.balance_check}}
|
||||
|
||||
|
||||
class AccountBankStatementLine(Model):
|
||||
"""
|
||||
Override to compute the period from the date of the line, add a method to retrieve
|
||||
the values for a line from the profile. Override the on_change method to take care of
|
||||
the profile when fullfilling the bank statement manually. Set the reference to 64
|
||||
Char long instead 32.
|
||||
class AccountBankStatementLine(orm.Model):
|
||||
"""Override to compute the period from the date of the line, add a method
|
||||
to retrieve the values for a line from the profile. Override the on_change
|
||||
method to take care of the profile when fullfilling the bank statement
|
||||
manually. Set the reference to 64 Char long instead 32.
|
||||
"""
|
||||
_inherit = "account.bank.statement.line"
|
||||
|
||||
@@ -599,7 +606,7 @@ class AccountBankStatementLine(Model):
|
||||
local_context['account_period_prefer_normal'] = True
|
||||
try:
|
||||
periods = period_obj.find(cr, uid, dt=date, context=local_context)
|
||||
except osv.except_osv:
|
||||
except orm.except_orm:
|
||||
# if no period defined, we are certainly at installation time
|
||||
return False
|
||||
return periods and periods[0] or False
|
||||
@@ -617,30 +624,40 @@ class AccountBankStatementLine(Model):
|
||||
'account_id': _get_default_account,
|
||||
}
|
||||
|
||||
def get_values_for_line(self, cr, uid, profile_id=False, partner_id=False, line_type=False, amount=False, master_account_id=None, context=None):
|
||||
"""
|
||||
Return the account_id to be used in the line of a bank statement. It'll base the result as follow:
|
||||
- If a receivable_account_id is set in the profile, return this value and type = general
|
||||
def get_values_for_line(self, cr, uid, profile_id=False, partner_id=False,
|
||||
line_type=False, amount=False,
|
||||
master_account_id=None, context=None):
|
||||
"""Return the account_id to be used in the line of a bank statement.
|
||||
It'll base the result as follow:
|
||||
- If a receivable_account_id is set in the profile, return this
|
||||
value and type = general
|
||||
# TODO
|
||||
- Elif how_get_type_account is set to force_supplier or force_customer, will take respectively payable and type=supplier,
|
||||
- Elif how_get_type_account is set to force_supplier or
|
||||
force_customer, will take respectively payable and type=supplier,
|
||||
receivable and type=customer otherwise
|
||||
# END TODO
|
||||
- Elif line_type is given, take the partner receivable/payable property (payable if type=supplier, receivable
|
||||
otherwise)
|
||||
- Elif line_type is given, take the partner receivable/payable
|
||||
property (payable if type=supplier, receivable otherwise)
|
||||
- Elif amount is given:
|
||||
- If the customer checkbox is checked on the found partner, type and account will be customer and receivable
|
||||
- If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
|
||||
- If both checkbox are checked or none of them, it'll be based on the amount :
|
||||
If amount is positive, the type and account will be customer and receivable,
|
||||
If amount is negative, the type and account will be supplier an payable
|
||||
- Then, if no partner are given we look and take the property from the company so we always give a value
|
||||
for account_id. Note that in that case, we return the receivable one.
|
||||
- If the customer checkbox is checked on the found partner,
|
||||
type and account will be customer and receivable
|
||||
- If the supplier checkbox is checked on the found partner,
|
||||
type and account will be supplier and payable
|
||||
- If both checkbox are checked or none of them, it'll be based
|
||||
on the amount :
|
||||
If amount is positive, the type and account will be
|
||||
customer and receivable,
|
||||
If amount is negative, the type and account will be
|
||||
supplier an payable
|
||||
- Then, if no partner are given we look and take the property from
|
||||
the company so we always give a value for account_id. Note that in
|
||||
that case, we return the receivable one.
|
||||
:param int/long profile_id of the related bank statement
|
||||
:param int/long partner_id of the line
|
||||
:param char line_type: a value from: 'general', 'supplier', 'customer'
|
||||
:param float: amount of the line
|
||||
:return: A dict of value that can be passed directly to the write method of
|
||||
the statement line:
|
||||
:return: A dict of value that can be passed directly to the write
|
||||
method of the statement line:
|
||||
{'partner_id': value,
|
||||
'account_id' : value,
|
||||
'type' : value,
|
||||
@@ -666,9 +683,9 @@ class AccountBankStatementLine(Model):
|
||||
cr, uid, profile_id, context=context)
|
||||
if profile.receivable_account_id:
|
||||
res['account_id'] = profile.receivable_account_id.id
|
||||
# We return general as default instead of get_type_for_counterpart
|
||||
# for perfomance reasons as line_type is not a meaningfull value
|
||||
# as account is forced
|
||||
# We return general as default instead of
|
||||
# get_type_for_counterpart for perfomance reasons as line_type
|
||||
# is not a meaningfull value as account is forced
|
||||
res['type'] = line_type if line_type else 'general'
|
||||
return res
|
||||
# If no account is available on profile you have to do the lookup
|
||||
@@ -684,36 +701,41 @@ class AccountBankStatementLine(Model):
|
||||
receiv_account = part.property_account_receivable.id
|
||||
# If no value, look on the default company property
|
||||
if not pay_account or not receiv_account:
|
||||
receiv_account, pay_account = obj_stat.get_default_pay_receiv_accounts(cr, uid, context=None)
|
||||
account_id, comp_line_type = obj_stat.get_account_and_type_for_counterpart(cr, uid, amount,
|
||||
receiv_account, pay_account,
|
||||
receiv_account, pay_account = obj_stat.\
|
||||
get_default_pay_receiv_accounts(cr, uid, context=None)
|
||||
account_id, comp_line_type = obj_stat.\
|
||||
get_account_and_type_for_counterpart(
|
||||
cr, uid, amount, receiv_account, pay_account,
|
||||
partner_id=partner_id)
|
||||
res['account_id'] = account_id if account_id else receiv_account
|
||||
res['type'] = line_type if line_type else comp_line_type
|
||||
return res
|
||||
|
||||
def onchange_partner_id(self, cr, uid, ids, partner_id, profile_id=None, context=None):
|
||||
def onchange_partner_id(self, cr, uid, ids, partner_id, profile_id=None,
|
||||
context=None):
|
||||
"""
|
||||
Override of the basic method as we need to pass the profile_id in the on_change_type
|
||||
call.
|
||||
Moreover, we now call the get_account_and_type_for_counterpart method now to get the
|
||||
type to use.
|
||||
Override of the basic method as we need to pass the profile_id in the
|
||||
on_change_type call.
|
||||
Moreover, we now call the get_account_and_type_for_counterpart method
|
||||
now to get the type to use.
|
||||
"""
|
||||
obj_stat = self.pool.get('account.bank.statement')
|
||||
obj_stat = self.pool['account.bank.statement']
|
||||
if not partner_id:
|
||||
return {}
|
||||
line_type = obj_stat.get_type_for_counterpart(cr, uid, 0.0, partner_id=partner_id)
|
||||
res_type = self.onchange_type(cr, uid, ids, partner_id, line_type, profile_id, context=context)
|
||||
line_type = obj_stat.get_type_for_counterpart(
|
||||
cr, uid, 0.0, partner_id=partner_id)
|
||||
res_type = self.onchange_type(
|
||||
cr, uid, ids, partner_id, line_type, profile_id, context=context)
|
||||
if res_type['value'] and res_type['value'].get('account_id', False):
|
||||
return {'value': {'type': line_type,
|
||||
'account_id': res_type['value']['account_id'],
|
||||
'voucher_id': False}}
|
||||
return {'value': {'type': line_type}}
|
||||
|
||||
def onchange_type(self, cr, uid, line_id, partner_id, line_type, profile_id, context=None):
|
||||
"""
|
||||
Keep the same features as in standard and call super. If an account is returned,
|
||||
call the method to compute line values.
|
||||
def onchange_type(self, cr, uid, line_id, partner_id, line_type, profile_id,
|
||||
context=None):
|
||||
"""Keep the same features as in standard and call super. If an account
|
||||
is returned, call the method to compute line values.
|
||||
"""
|
||||
res = super(AccountBankStatementLine, self
|
||||
).onchange_type(cr, uid, line_id, partner_id,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<record id="statement_importer_view_form" model="ir.ui.view">
|
||||
<field name="name">account.statement.profile.view</field>
|
||||
<field name="model">account.statement.profile</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Import statement">
|
||||
<separator string="" colspan="4"/>
|
||||
@@ -27,7 +26,6 @@
|
||||
<record id="statement_importer_view_tree" model="ir.ui.view">
|
||||
<field name="name">account.statement.profile.view</field>
|
||||
<field name="model">account.statement.profile</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Import statement">
|
||||
<field name="name" />
|
||||
@@ -76,7 +74,6 @@
|
||||
<field name="name">account.bank.statement.tree</field>
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_tree"/>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/tree/field[@name='name']" position="before">
|
||||
<field name="id"/>
|
||||
@@ -95,7 +92,6 @@
|
||||
<field name="name">account.bank.statement.form</field>
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_form"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml" >
|
||||
|
||||
<!-- Add before the group : profile and related infos -->
|
||||
|
||||
@@ -29,8 +29,9 @@ class AccountVoucher(Model):
|
||||
def _get_period(self, cr, uid, context=None):
|
||||
"""If period not in context, take it from the move lines"""
|
||||
if not context.get('period_id') and context.get('move_line_ids'):
|
||||
res = self.pool.get('account.move.line').browse(
|
||||
cr, uid, context.get('move_line_ids'), context=context)[0].period_id.id
|
||||
res = self.pool['account.move.line'].browse(
|
||||
cr, uid, context.get('move_line_ids'),
|
||||
context=context)[0].period_id.id
|
||||
context['period_id'] = res
|
||||
elif context.get('date'):
|
||||
periods = self.pool.get('account.period').find(
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.addons.point_of_sale.point_of_sale import pos_session as std_pos_session
|
||||
from openerp.osv import orm, osv
|
||||
from openerp.addons.point_of_sale.point_of_sale import pos_session as \
|
||||
std_pos_session
|
||||
from openerp.osv import orm
|
||||
from openerp.tools.translate import _
|
||||
|
||||
if not hasattr(std_pos_session, '_prepare_bank_statement'):
|
||||
@@ -34,21 +35,23 @@ if not hasattr(std_pos_session, '_prepare_bank_statement'):
|
||||
# which _inherit pos.session
|
||||
#
|
||||
# This change has been proposed for merging to fix lp:125375
|
||||
def mp_prepare_bank_statement(self, cr, uid, pos_config, journal, context=None):
|
||||
def mp_prepare_bank_statement(self, cr, uid, pos_config, journal,
|
||||
context=None):
|
||||
bank_values = {
|
||||
'journal_id' : journal.id,
|
||||
'user_id' : uid,
|
||||
'company_id' : pos_config.shop_id.company_id.id
|
||||
'journal_id': journal.id,
|
||||
'user_id': uid,
|
||||
'company_id': pos_config.shop_id.company_id.id
|
||||
}
|
||||
return bank_values
|
||||
|
||||
def mp_create(self, cr, uid, values, context=None):
|
||||
context = context or {}
|
||||
config_id = values.get('config_id', False) or context.get('default_config_id', False)
|
||||
config_id = values.get('config_id', False) or context.get(
|
||||
'default_config_id', False)
|
||||
if not config_id:
|
||||
raise osv.except_osv( _('Error!'),
|
||||
raise orm.except_orm(
|
||||
_('Error!'),
|
||||
_("You should assign a Point of Sale to your session."))
|
||||
|
||||
# journal_id is not required on the pos_config because it does not
|
||||
# exists at the installation. If nothing is configured at the
|
||||
# installation we do the minimal configuration. Impossible to do in
|
||||
@@ -57,64 +60,70 @@ if not hasattr(std_pos_session, '_prepare_bank_statement'):
|
||||
pos_config = jobj.browse(cr, uid, config_id, context=context)
|
||||
context.update({'company_id': pos_config.shop_id.company_id.id})
|
||||
if not pos_config.journal_id:
|
||||
jid = jobj.default_get(cr, uid, ['journal_id'], context=context)['journal_id']
|
||||
jid = jobj.default_get(
|
||||
cr, uid, ['journal_id'], context=context)['journal_id']
|
||||
if jid:
|
||||
jobj.write(cr, uid, [pos_config.id], {'journal_id': jid}, context=context)
|
||||
jobj.write(
|
||||
cr, uid, [pos_config.id], {'journal_id': jid},
|
||||
context=context)
|
||||
else:
|
||||
raise osv.except_osv( _('error!'),
|
||||
_("Unable to open the session. You have to assign a sale journal to your point of sale."))
|
||||
|
||||
raise orm.except_orm(
|
||||
_('error!'),
|
||||
_("Unable to open the session. You have to assign a sale "
|
||||
"journal to your point of sale."))
|
||||
# define some cash journal if no payment method exists
|
||||
if not pos_config.journal_ids:
|
||||
journal_proxy = self.pool.get('account.journal')
|
||||
cashids = journal_proxy.search(cr, uid, [('journal_user', '=', True), ('type','=','cash')], context=context)
|
||||
cashids = journal_proxy.search(
|
||||
cr, uid, [('journal_user', '=', True),
|
||||
('type', '=', 'cash')], context=context)
|
||||
if not cashids:
|
||||
cashids = journal_proxy.search(cr, uid, [('type', '=', 'cash')], context=context)
|
||||
cashids = journal_proxy.search(
|
||||
cr, uid, [('type', '=', 'cash')], context=context)
|
||||
if not cashids:
|
||||
cashids = journal_proxy.search(cr, uid, [('journal_user','=',True)], context=context)
|
||||
|
||||
jobj.write(cr, uid, [pos_config.id], {'journal_ids': [(6,0, cashids)]})
|
||||
|
||||
|
||||
cashids = journal_proxy.search(
|
||||
cr, uid, [('journal_user', '=', True)], context=context)
|
||||
jobj.write(
|
||||
cr, uid, [pos_config.id], {'journal_ids': [(6, 0, cashids)]})
|
||||
pos_config = jobj.browse(cr, uid, config_id, context=context)
|
||||
bank_statement_ids = []
|
||||
for journal in pos_config.journal_ids:
|
||||
bank_values = self._prepare_bank_statement(cr, uid, pos_config, journal, context)
|
||||
statement_id = self.pool.get('account.bank.statement').create(cr, uid, bank_values, context=context)
|
||||
bank_values = self._prepare_bank_statement(
|
||||
cr, uid, pos_config, journal, context)
|
||||
statement_id = self.pool.get('account.bank.statement').create(
|
||||
cr, uid, bank_values, context=context)
|
||||
bank_statement_ids.append(statement_id)
|
||||
|
||||
values.update({
|
||||
'name' : pos_config.sequence_id._next(),
|
||||
'statement_ids' : [(6, 0, bank_statement_ids)],
|
||||
'name': pos_config.sequence_id._next(),
|
||||
'statement_ids': [(6, 0, bank_statement_ids)],
|
||||
'config_id': config_id
|
||||
})
|
||||
return super(std_pos_session, self).create(cr, uid, values, context=context)
|
||||
|
||||
return super(std_pos_session, self).create(cr, uid, values,
|
||||
context=context)
|
||||
std_pos_session._prepare_bank_statement = mp_prepare_bank_statement
|
||||
std_pos_session.create = mp_create
|
||||
|
||||
|
||||
class pos_session(orm.Model):
|
||||
class PosSession(orm.Model):
|
||||
_inherit = 'pos.session'
|
||||
|
||||
def _prepare_bank_statement(self, cr, uid, pos_config, journal, context=None):
|
||||
""" Override the function _mp_create. To add the bank profile to the statement
|
||||
|
||||
Function That was previously added to pos.session model using monkey patching
|
||||
|
||||
def _prepare_bank_statement(self, cr, uid, pos_config, journal,
|
||||
context=None):
|
||||
""" Override the function _mp_create. To add the bank profile to the
|
||||
statement.
|
||||
Function That was previously added to pos.session model using monkey
|
||||
patching.
|
||||
"""
|
||||
|
||||
bank_values = super(pos_session, self)._prepare_bank_statement(cr, uid,
|
||||
pos_config,
|
||||
journal, context)
|
||||
user_obj = self.pool.get('res.users')
|
||||
profile_obj = self.pool.get('account.statement.profile')
|
||||
bank_values = super(PosSession, self)._prepare_bank_statement(
|
||||
cr, uid, pos_config, journal, context)
|
||||
user_obj = self.pool['res.users']
|
||||
profile_obj = self.pool['account.statement.profile']
|
||||
user = user_obj.browse(cr, uid, uid, context=context)
|
||||
defaults = self.pool['account.bank.statement'].default_get(cr, uid,
|
||||
['profile_id', 'period_id'],
|
||||
context=context)
|
||||
profile_ids = profile_obj.search(cr, uid,
|
||||
[('company_id', '=', user.company_id.id),
|
||||
defaults = self.pool['account.bank.statement'].default_get(
|
||||
cr, uid, ['profile_id', 'period_id'], context=context)
|
||||
profile_ids = profile_obj.search(
|
||||
cr, uid, [('company_id', '=', user.company_id.id),
|
||||
('journal_id', '=', bank_values['journal_id'])],
|
||||
context=context)
|
||||
if profile_ids:
|
||||
|
||||
@@ -30,23 +30,20 @@
|
||||
'account_voucher'
|
||||
],
|
||||
'description': """
|
||||
This module is deprecated. It was only needed when using account_bank_statement_ext with voucher in order to compute the period
|
||||
correctly. This is mainly because with account_bank_statement_ext, the period is computed for each line.
|
||||
|
||||
Now, we include this in the account_statement_ext module and added a dependencies on account_voucher (mainly cause we can't get
|
||||
rid of the voucher in version 7.0).
|
||||
This module is deprecated. It was only needed when using
|
||||
account_bank_statement_ext with voucher in order to compute the period
|
||||
correctly. This is mainly because with account_bank_statement_ext, the period
|
||||
is computed for each line.
|
||||
|
||||
Now, we include this in the account_statement_ext module and added a
|
||||
dependencies on account_voucher (mainly cause we can't get rid of the voucher
|
||||
in version 7.0).
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'init_xml': [],
|
||||
'update_xml': [
|
||||
'data': [
|
||||
"statement_voucher_view.xml",
|
||||
],
|
||||
'demo_xml': [],
|
||||
'test': [],
|
||||
'installable': False,
|
||||
'images': [],
|
||||
'auto_install': False,
|
||||
'license': 'AGPL-3',
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,11 @@ class AccountVoucher(Model):
|
||||
context = {}
|
||||
if not context.get('period_id') and context.get('move_line_ids'):
|
||||
res = self.pool.get('account.move.line').browse(
|
||||
cr, uid, context.get('move_line_ids'), context=context)[0].period_id.id
|
||||
cr, uid, context.get('move_line_ids'),
|
||||
context=context)[0].period_id.id
|
||||
context['period_id'] = res
|
||||
elif context.get('date'):
|
||||
periods = self.pool.get('account.period').find(
|
||||
periods = self.pool['account.period'].find(
|
||||
cr, uid, dt=context['date'], context=context)
|
||||
if periods:
|
||||
context['period_id'] = periods[0]
|
||||
|
||||
@@ -19,4 +19,3 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
'version': '0.1',
|
||||
'category': 'Generic Modules/Others',
|
||||
'license': 'AGPL-3',
|
||||
'description': """Module that remove the 'Import invoices' button on bank statement""",
|
||||
'description': """
|
||||
Module that remove the 'Import invoices' button on bank statement
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com/',
|
||||
'depends': [
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account_voucher.view_bank_statement_form_invoice" />
|
||||
<field eval="100" name="priority"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<button string="Import Invoices" position="replace">
|
||||
</button>
|
||||
|
||||
@@ -46,4 +46,4 @@
|
||||
'images': [],
|
||||
'auto_install': False,
|
||||
'license': 'AGPL-3',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,4 +19,3 @@
|
||||
#
|
||||
##############################################################################
|
||||
from . import ofx_parser
|
||||
|
||||
|
||||
@@ -21,49 +21,35 @@
|
||||
|
||||
import tempfile
|
||||
import datetime
|
||||
|
||||
from openerp.tools.translate import _
|
||||
from openerp.addons.account_statement_base_import.parser import BankStatementImportParser
|
||||
|
||||
from openerp.addons.account_statement_base_import.parser import \
|
||||
BankStatementImportParser
|
||||
try:
|
||||
import ofxparse
|
||||
except:
|
||||
raise Exception(_('Please install python lib ofxparse'))
|
||||
|
||||
class OfxParser(BankStatementImportParser):
|
||||
"""
|
||||
Class for defining parser for OFX file format.
|
||||
"""
|
||||
|
||||
def __init__(self, parser_name, *args, **kwargs):
|
||||
"""
|
||||
"""
|
||||
super(OfxParser, self).__init__(parser_name, *args, **kwargs)
|
||||
class OfxParser(BankStatementImportParser):
|
||||
"""Class for defining parser for OFX file format."""
|
||||
|
||||
@classmethod
|
||||
def parser_for(cls, parser_name):
|
||||
"""
|
||||
Used by the new_bank_statement_parser class factory. Return true if
|
||||
"""Used by the new_bank_statement_parser class factory. Return true if
|
||||
the providen name is 'ofx_so'.
|
||||
"""
|
||||
return parser_name == 'ofx_so'
|
||||
|
||||
def _custom_format(self, *args, **kwargs):
|
||||
"""
|
||||
No other work on data are needed in this parser.
|
||||
"""
|
||||
"""No other work on data are needed in this parser."""
|
||||
return True
|
||||
|
||||
def _pre(self, *args, **kwargs):
|
||||
"""
|
||||
No pre-treatment needed for this parser.
|
||||
"""
|
||||
"""No pre-treatment needed for this parser."""
|
||||
return True
|
||||
|
||||
def _parse(self, *args, **kwargs):
|
||||
"""
|
||||
Launch the parsing itself.
|
||||
"""
|
||||
"""Launch the parsing itself."""
|
||||
ofx_file = tempfile.NamedTemporaryFile()
|
||||
ofx_file.seek(0)
|
||||
ofx_file.write(self.filebuffer)
|
||||
@@ -82,26 +68,15 @@ class OfxParser(BankStatementImportParser):
|
||||
return True
|
||||
|
||||
def _validate(self, *args, **kwargs):
|
||||
"""
|
||||
Nothing to do here. ofxparse trigger possible format errors.
|
||||
"""
|
||||
"""Nothing to do here. ofxparse trigger possible format errors."""
|
||||
return True
|
||||
|
||||
def _post(self, *args, **kwargs):
|
||||
"""
|
||||
Nothing is needed to do after parsing.
|
||||
"""
|
||||
return True
|
||||
|
||||
def _post(self, *args, **kwargs):
|
||||
"""
|
||||
Nothing to do.
|
||||
"""
|
||||
"""Nothing is needed to do after parsing."""
|
||||
return True
|
||||
|
||||
def get_st_line_vals(self, line, *args, **kwargs):
|
||||
"""
|
||||
This method must return a dict of vals that can be passed to create
|
||||
"""This method must return a dict of vals that can be passed to create
|
||||
method of statement line in order to record it. It is the
|
||||
responsibility of every parser to give this dict of vals, so each one
|
||||
can implement his own way of recording the lines.
|
||||
@@ -117,4 +92,3 @@ class OfxParser(BankStatementImportParser):
|
||||
'ref': line.get('ref', '/'),
|
||||
'label': line.get('label', ''),
|
||||
}
|
||||
|
||||
|
||||
@@ -19,17 +19,16 @@
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv import fields, orm
|
||||
from openerp.osv import orm
|
||||
|
||||
|
||||
class AccountStatementProfil(orm.Model):
|
||||
_inherit = "account.statement.profile"
|
||||
|
||||
def get_import_type_selection(self, cr, uid, context=None):
|
||||
"""
|
||||
Inherited from parent to add parser.
|
||||
"""
|
||||
def _get_import_type_selection(self, cr, uid, context=None):
|
||||
"""Inherited from parent to add parser."""
|
||||
selection = super(AccountStatementProfil, self
|
||||
).get_import_type_selection(cr, uid,
|
||||
)._get_import_type_selection(cr, uid,
|
||||
context=context)
|
||||
selection.append(('ofx_so', _('OFX - Open Financial Exchange')))
|
||||
return selection
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from openerp.osv import fields, orm, osv
|
||||
from openerp.osv import fields, orm
|
||||
|
||||
|
||||
class AccountStatementProfile(orm.Model):
|
||||
@@ -36,12 +36,13 @@ class AccountStatementProfile(orm.Model):
|
||||
"for the refunds and one for the payments.")
|
||||
}
|
||||
|
||||
class account_bank_statement(orm.Model):
|
||||
|
||||
class AccountBankStatement(orm.Model):
|
||||
_inherit = "account.bank.statement"
|
||||
|
||||
def _prepare_move_line_vals(self, cr, uid, st_line, *args, **kwargs):
|
||||
res = super(account_bank_statement, self)._prepare_move_line_vals(cr, uid, st_line,
|
||||
*args, **kwargs)
|
||||
res = super(AccountBankStatement, self)._prepare_move_line_vals(
|
||||
cr, uid, st_line, *args, **kwargs)
|
||||
period_id = self._get_period(cr, uid, st_line.statement_id.date,
|
||||
context=kwargs.get('context'))
|
||||
if st_line.statement_id.profile_id.one_move:
|
||||
@@ -52,11 +53,10 @@ class account_bank_statement(orm.Model):
|
||||
})
|
||||
return res
|
||||
|
||||
|
||||
return res
|
||||
|
||||
def _prepare_move(self, cr, uid, st_line, st_line_number, context=None):
|
||||
res = super(account_bank_statement, self).\
|
||||
res = super(AccountBankStatement, self).\
|
||||
_prepare_move(cr, uid, st_line, st_line_number, context=context)
|
||||
res.update({
|
||||
'ref': st_line.statement_id.name,
|
||||
@@ -65,30 +65,29 @@ class account_bank_statement(orm.Model):
|
||||
})
|
||||
return res
|
||||
|
||||
|
||||
def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id,
|
||||
st_line_number, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
context['from_parent_object'] = True #For compability with module account_constraints
|
||||
account_move_obj = self.pool.get('account.move')
|
||||
account_bank_statement_line_obj = self.pool.get('account.bank.statement.line')
|
||||
st_line = account_bank_statement_line_obj.browse(cr, uid, st_line_id,
|
||||
context=context)
|
||||
# For compability with module account_constraints
|
||||
context['from_parent_object'] = True
|
||||
account_move_obj = self.pool['account.move']
|
||||
st_line_obj = self.pool['account.bank.statement.line']
|
||||
st_line = st_line_obj.browse(cr, uid, st_line_id, context=context)
|
||||
st = st_line.statement_id
|
||||
|
||||
if st.profile_id.one_move:
|
||||
if not context.get('move_id'):
|
||||
move_vals = self._prepare_move(cr, uid, st_line, st_line_number, context=context)
|
||||
context['move_id'] = account_move_obj.create(cr, uid, move_vals, context=context)
|
||||
move_vals = self._prepare_move(
|
||||
cr, uid, st_line, st_line_number, context=context)
|
||||
context['move_id'] = account_move_obj.create(
|
||||
cr, uid, move_vals, context=context)
|
||||
self.create_move_line_from_st_line(cr, uid, context['move_id'],
|
||||
st_line_id, company_currency_id,
|
||||
context=context)
|
||||
return context['move_id']
|
||||
else:
|
||||
return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line_id,
|
||||
company_currency_id,
|
||||
st_line_number,
|
||||
return super(AccountBankStatement, self).create_move_from_st_line(
|
||||
cr, uid, st_line_id, company_currency_id, st_line_number,
|
||||
context=context)
|
||||
|
||||
def create_move_line_from_st_line(self, cr, uid, move_id, st_line_id,
|
||||
@@ -96,46 +95,44 @@ class account_bank_statement(orm.Model):
|
||||
"""Create the account move line from the statement line.
|
||||
|
||||
:param int/long move_id: ID of the account.move
|
||||
:param int/long st_line_id: ID of the account.bank.statement.line to create the move line from.
|
||||
:param int/long company_currency_id: ID of the res.currency of the company
|
||||
:param int/long st_line_id: ID of the account.bank.statement.line
|
||||
to create the move line from.
|
||||
:param int/long company_currency_id: ID of the res.currency of the
|
||||
company
|
||||
:return: ID of the account.move created
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
res_currency_obj = self.pool.get('res.currency')
|
||||
account_move_line_obj = self.pool.get('account.move.line')
|
||||
account_bank_statement_line_obj = self.pool.get('account.bank.statement.line')
|
||||
st_line = account_bank_statement_line_obj.browse(cr, uid, st_line_id, context=context)
|
||||
res_currency_obj = self.pool['res.currency']
|
||||
account_move_line_obj = self.pool['account.move.line']
|
||||
st_line_obj = self.pool['account.bank.statement.line']
|
||||
st_line = st_line_obj.browse(cr, uid, st_line_id, context=context)
|
||||
st = st_line.statement_id
|
||||
|
||||
context.update({'date': st_line.date})
|
||||
acc_cur = ((st_line.amount<=0) and st.journal_id.default_debit_account_id) or st_line.account_id
|
||||
|
||||
acc_cur = (((st_line.amount <= 0)
|
||||
and st.journal_id.default_debit_account_id) or
|
||||
st_line.account_id)
|
||||
context.update({
|
||||
'res.currency.compute.account': acc_cur,
|
||||
})
|
||||
amount = res_currency_obj.compute(cr, uid, st.currency.id,
|
||||
company_currency_id,
|
||||
st_line.amount,
|
||||
amount = res_currency_obj.compute(
|
||||
cr, uid, st.currency.id, company_currency_id, st_line.amount,
|
||||
context=context)
|
||||
bank_move_vals = self._prepare_bank_move_line(
|
||||
cr, uid, st_line, move_id, amount, company_currency_id,
|
||||
context=context)
|
||||
return account_move_line_obj.create(cr, uid, bank_move_vals,
|
||||
context=context)
|
||||
|
||||
bank_move_vals = self._prepare_bank_move_line(cr, uid, st_line, move_id, amount,
|
||||
company_currency_id, context=context)
|
||||
return account_move_line_obj.create(cr, uid, bank_move_vals, context=context)
|
||||
|
||||
def _valid_move(self, cr, uid, move_id, context=None):
|
||||
move_obj = self.pool.get('account.move')
|
||||
move = move_obj.browse(cr, uid, move_id, context=context)
|
||||
move_obj = self.pool['account.move']
|
||||
move_obj.post(cr, uid, [move_id], context=context)
|
||||
return True
|
||||
|
||||
|
||||
def _prepare_transfer_move_line_vals(self, cr, uid, st, name, amount, move_id, context=None):
|
||||
"""
|
||||
Prepare the dict of values to create the transfer move lines.
|
||||
"""
|
||||
def _prepare_transfer_move_line_vals(self, cr, uid, st, name, amount,
|
||||
move_id, context=None):
|
||||
"""Prepare the dict of values to create the transfer move lines."""
|
||||
account_id = st.profile_id.journal_id.default_debit_account_id.id
|
||||
partner_id = st.profile_id.partner_id and profile.partner_id.id or False
|
||||
if amount < 0.0:
|
||||
debit = 0.0
|
||||
credit = -amount
|
||||
@@ -145,7 +142,7 @@ class account_bank_statement(orm.Model):
|
||||
vals = {
|
||||
'name': name,
|
||||
'date': st.date,
|
||||
'partner_id': partner_id,
|
||||
'partner_id': st.profile_id.partner_id.id,
|
||||
'statement_id': st.id,
|
||||
'account_id': account_id,
|
||||
'ref': name,
|
||||
@@ -157,19 +154,18 @@ class account_bank_statement(orm.Model):
|
||||
}
|
||||
return vals
|
||||
|
||||
|
||||
def create_move_transfer_lines(self, cr, uid, move, st, context=None):
|
||||
move_line_obj = self.pool.get('account.move.line')
|
||||
move_line_obj = self.pool['account.move.line']
|
||||
move_id = move.id
|
||||
refund = 0.0
|
||||
payment = 0.0
|
||||
transfer_lines = []
|
||||
transfer_line_ids = []
|
||||
#Calculate the part of the refund amount and the payment amount
|
||||
# Calculate the part of the refund amount and the payment amount
|
||||
for move_line in move.line_id:
|
||||
refund -= move_line.debit
|
||||
payment += move_line.credit
|
||||
#Create 2 Transfer lines or One global tranfer line
|
||||
# Create 2 Transfer lines or One global tranfer line
|
||||
if st.profile_id.split_transfer_line:
|
||||
if refund:
|
||||
transfer_lines.append(['Refund Transfer', refund])
|
||||
@@ -180,27 +176,22 @@ class account_bank_statement(orm.Model):
|
||||
if amount:
|
||||
transfer_lines.append(['Transfer', amount])
|
||||
for transfer_line in transfer_lines:
|
||||
vals = self._prepare_transfer_move_line_vals(cr, uid, st,
|
||||
transfer_line[0],
|
||||
transfer_line[1],
|
||||
move_id,
|
||||
vals = self._prepare_transfer_move_line_vals(
|
||||
cr, uid, st, transfer_line[0], transfer_line[1], move_id,
|
||||
context=context)
|
||||
transfer_line_ids.append(move_line_obj.create(cr, uid, vals, context=context))
|
||||
transfer_line_ids.append(
|
||||
move_line_obj.create(cr, uid, vals, context=context))
|
||||
return transfer_line_ids
|
||||
|
||||
|
||||
def button_confirm_bank(self, cr, uid, ids, context=None):
|
||||
st_line_obj = self.pool.get('account.bank.statement.line')
|
||||
move_obj = self.pool.get('account.move')
|
||||
st_line_obj = self.pool['account.bank.statement.line']
|
||||
if context is None:
|
||||
context = {}
|
||||
for st in self.browse(cr, uid, ids, context=context):
|
||||
super(account_bank_statement, self).button_confirm_bank(cr, uid, ids,
|
||||
context=context)
|
||||
super(AccountBankStatement, self).button_confirm_bank(
|
||||
cr, uid, ids, context=context)
|
||||
if st.profile_id.one_move and context.get('move_id', False):
|
||||
move_id = context['move_id']
|
||||
move = move_obj.browse(cr, uid, move_id, context=context)
|
||||
transfe_line_ids = self.create_move_transfer_lines(cr, uid, move, st, context=context)
|
||||
self._valid_move(cr, uid, move_id, context=context)
|
||||
lines_ids = [x.id for x in st.line_ids]
|
||||
st_line_obj.write(cr, uid, lines_ids,
|
||||
@@ -209,17 +200,14 @@ class account_bank_statement(orm.Model):
|
||||
return True
|
||||
|
||||
def button_cancel(self, cr, uid, ids, context=None):
|
||||
done = []
|
||||
for st in self.browse(cr, uid, ids, context=context):
|
||||
if st.profile_id.one_move and st.line_ids:
|
||||
for move in st.line_ids[0].move_ids:
|
||||
if move.state != 'draft':
|
||||
move.button_cancel(context=context)
|
||||
move.unlink(context=context)
|
||||
st.write({'state':'draft'}, context=context)
|
||||
st.write({'state': 'draft'}, context=context)
|
||||
else:
|
||||
super(account_bank_statement, self).button_cancel(cr, uid, ids,
|
||||
context=context)
|
||||
super(AccountBankStatement, self).button_cancel(
|
||||
cr, uid, ids, context=context)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
Account Statement Regex Account Completion addon
|
||||
=========================
|
||||
|
||||
- Add a completion method based on a specified regular expression
|
||||
and update account to use in the bank statement line with the specified account.
|
||||
Add a completion method based on a specified regular expression and update
|
||||
account to use in the bank statement line with the specified account.
|
||||
""",
|
||||
"data": ['statement_view.xml',
|
||||
],
|
||||
@@ -53,4 +53,3 @@ Account Statement Regex Account Completion addon
|
||||
"auto_install": False,
|
||||
"application": False,
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -35,6 +35,7 @@ import re
|
||||
|
||||
|
||||
class AccountStatementCompletionRule(Model):
|
||||
|
||||
"""Add a rule to complete account based on a regular expression"""
|
||||
|
||||
_inherit = "account.statement.completion.rule"
|
||||
@@ -43,13 +44,14 @@ class AccountStatementCompletionRule(Model):
|
||||
res = super(AccountStatementCompletionRule, self)._get_functions(
|
||||
cr, uid, context=context)
|
||||
res.append(('set_account',
|
||||
'Set account for line labels matching a regular expression'))
|
||||
'Set account for line labels matching a regular '
|
||||
'expression'))
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'function_to_call': fields.selection(_get_functions, 'Method'),
|
||||
'regex': fields.char('Regular Expression', size=128),
|
||||
'account_id': fields.many2one('account.account', string="Account to set"),
|
||||
'account_id': fields.many2one('account.account',
|
||||
string="Account to set"),
|
||||
}
|
||||
|
||||
def set_account(self, cr, uid, id, st_line, context=None):
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<field name="name">account.statement.completion.rule.view (account_statement_regex_account_completion)</field>
|
||||
<field name="model">account.statement.completion.rule</field>
|
||||
<field name="inherit_id" ref="account_statement_base_completion.statement_st_completion_rule_view_form" />
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="function_to_call" position="after">
|
||||
<group colspan="2">
|
||||
|
||||
@@ -36,34 +36,38 @@ ACC_NUMBER = "BE38733040385372"
|
||||
class test_regex_account_completion(common.TransactionCase):
|
||||
|
||||
def prepare(self):
|
||||
self.account_bank_statement_obj = self.registry("account.bank.statement")
|
||||
self.account_bank_statement_line_obj = self.registry("account.bank.statement.line")
|
||||
self.st_obj = self.registry(
|
||||
"account.bank.statement")
|
||||
self.st_line_obj = self.registry(
|
||||
"account.bank.statement.line")
|
||||
self.account_id = self.ref('account.a_expense')
|
||||
# create the completion rule
|
||||
rule_vals = {'function_to_call': 'set_account',
|
||||
'regex': '^My statement',
|
||||
'account_id': self.account_id}
|
||||
completion_rule_id = self.registry("account.statement.completion.rule").create(self.cr, self.uid, rule_vals)
|
||||
|
||||
completion_rule_id = self.registry(
|
||||
"account.statement.completion.rule").create(
|
||||
self.cr, self.uid, rule_vals)
|
||||
# Create the profile
|
||||
journal_id = self.ref("account.bank_journal")
|
||||
profile_id = self.registry("account.statement.profile").create(self.cr, self.uid, {
|
||||
profile_id = self.registry("account.statement.profile").create(
|
||||
self.cr, self.uid, {
|
||||
"name": "TEST",
|
||||
"commission_account_id": self.ref("account.a_recv"),
|
||||
"journal_id": journal_id,
|
||||
"rule_ids": [(6, 0, [completion_rule_id])]})
|
||||
|
||||
"rule_ids": [(6, 0, [completion_rule_id])]
|
||||
})
|
||||
# Create a bank statement
|
||||
self.statement_id = self.account_bank_statement_obj.create(self.cr, self.uid, {
|
||||
self.statement_id = self.st_obj.create(
|
||||
self.cr, self.uid, {
|
||||
"balance_end_real": 0.0,
|
||||
"balance_start": 0.0,
|
||||
"date": time.strftime('%Y-%m-%d'),
|
||||
"journal_id": journal_id,
|
||||
"profile_id": profile_id
|
||||
})
|
||||
|
||||
# Create two bank statement lines
|
||||
self.statement_line1_id = self.account_bank_statement_line_obj.create(self.cr, self.uid, {
|
||||
self.statement_line1_id = self.st_line_obj.create(self.cr, self.uid, {
|
||||
'amount': 1000.0,
|
||||
'name': 'My statement',
|
||||
'ref': 'My ref',
|
||||
@@ -71,7 +75,7 @@ class test_regex_account_completion(common.TransactionCase):
|
||||
'partner_acc_number': ACC_NUMBER
|
||||
})
|
||||
|
||||
self.statement_line2_id = self.account_bank_statement_line_obj.create(self.cr, self.uid, {
|
||||
self.statement_line2_id = self.st_line_obj.create(self.cr, self.uid, {
|
||||
'amount': 2000.0,
|
||||
'name': 'My second statement',
|
||||
'ref': 'My second ref',
|
||||
@@ -83,9 +87,14 @@ class test_regex_account_completion(common.TransactionCase):
|
||||
"""Test the automatic completion on account
|
||||
"""
|
||||
self.prepare()
|
||||
statement_obj = self.account_bank_statement_obj.browse(self.cr, self.uid, self.statement_id)
|
||||
statement_obj = self.st_obj.browse(self.cr, self.uid, self.statement_id)
|
||||
statement_obj.button_auto_completion()
|
||||
statement_line1 = self.account_bank_statement_line_obj.browse(self.cr, self.uid, self.statement_line1_id)
|
||||
self.assertEquals(self.account_id, statement_line1.account_id.id, "The account should be the account of the completion")
|
||||
statement_line2 = self.account_bank_statement_line_obj.browse(self.cr, self.uid, self.statement_line2_id)
|
||||
self.assertNotEqual(self.account_id, statement_line2.account_id.id, "The account should be not the account of the completion")
|
||||
statement_line1 = self.st_line_obj.browse(
|
||||
self.cr, self.uid, self.statement_line1_id)
|
||||
self.assertEquals(self.account_id, statement_line1.account_id.id,
|
||||
"The account should be the account of the completion")
|
||||
statement_line2 = self.st_line_obj.browse(
|
||||
self.cr, self.uid, self.statement_line2_id)
|
||||
self.assertNotEqual(self.account_id, statement_line2.account_id.id,
|
||||
"The account should be not the account of the "
|
||||
"completion")
|
||||
|
||||
@@ -22,19 +22,19 @@
|
||||
# #
|
||||
###############################################################################
|
||||
|
||||
from openerp.osv import fields, orm
|
||||
from openerp.osv import orm
|
||||
from tools.translate import _
|
||||
|
||||
from openerp.addons.account_statement_base_completion.statement import ErrorTooManyPartner
|
||||
from openerp.addons.account_statement_base_completion.statement import \
|
||||
ErrorTooManyPartner
|
||||
|
||||
|
||||
class account_statement_completion_rule(orm.Model):
|
||||
class AccountStatementCompletionRule(orm.Model):
|
||||
|
||||
_name = "account.statement.completion.rule"
|
||||
_inherit = "account.statement.completion.rule"
|
||||
|
||||
def _get_functions(self, cr, uid, context=None):
|
||||
res = super(account_statement_completion_rule, self)._get_functions(
|
||||
res = super(AccountStatementCompletionRule, self)._get_functions(
|
||||
cr, uid, context=context)
|
||||
res.append(
|
||||
('get_from_ref_and_so', 'From line reference (based on SO number)')
|
||||
@@ -60,14 +60,12 @@ class account_statement_completion_rule(orm.Model):
|
||||
|
||||
...}
|
||||
"""
|
||||
st_obj = self.pool.get('account.bank.statement.line')
|
||||
st_obj = self.pool['account.bank.statement.line']
|
||||
res = {}
|
||||
if st_line:
|
||||
so_obj = self.pool.get('sale.order')
|
||||
so_id = so_obj.search(cr,
|
||||
uid,
|
||||
[('name', '=', st_line['ref'])],
|
||||
context=context)
|
||||
so_id = so_obj.search(
|
||||
cr, uid, [('name', '=', st_line['ref'])], context=context)
|
||||
if so_id:
|
||||
if so_id and len(so_id) == 1:
|
||||
so = so_obj.browse(cr, uid, so_id[0], context=context)
|
||||
@@ -78,9 +76,7 @@ class account_statement_completion_rule(orm.Model):
|
||||
'than one partner while looking on SO by ref.') %
|
||||
(st_line['name'], st_line['ref']))
|
||||
st_vals = st_obj.get_values_for_line(
|
||||
cr,
|
||||
uid,
|
||||
profile_id=st_line['profile_id'],
|
||||
cr, uid, profile_id=st_line['profile_id'],
|
||||
master_account_id=st_line['master_account_id'],
|
||||
partner_id=res.get('partner_id', False),
|
||||
line_type='customer',
|
||||
@@ -88,7 +84,3 @@ class account_statement_completion_rule(orm.Model):
|
||||
context=context)
|
||||
res.update(st_vals)
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'function_to_call': fields.selection(_get_functions, 'Method'),
|
||||
}
|
||||
|
||||
@@ -58,4 +58,4 @@
|
||||
'images': [],
|
||||
'auto_install': True,
|
||||
'license': 'AGPL-3',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv.orm import Model
|
||||
from openerp.osv import fields
|
||||
from openerp.addons.account_statement_base_completion.statement import ErrorTooManyPartner
|
||||
from openerp.addons.account_statement_base_completion.statement import \
|
||||
ErrorTooManyPartner
|
||||
|
||||
|
||||
class AccountStatementCompletionRule(Model):
|
||||
@@ -41,15 +42,12 @@ class AccountStatementCompletionRule(Model):
|
||||
]
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'function_to_call': fields.selection(_get_functions, 'Method'),
|
||||
}
|
||||
|
||||
def get_from_transaction_id_and_so(self, cr, uid, st_line, context=None):
|
||||
"""
|
||||
Match the partner based on the transaction ID field of the SO.
|
||||
Then, call the generic st_line method to complete other values.
|
||||
In that case, we always fullfill the reference of the line with the SO name.
|
||||
In that case, we always fullfill the reference of the line with the SO
|
||||
name.
|
||||
:param dict st_line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
A dict of value that can be passed directly to the write method of
|
||||
@@ -58,23 +56,22 @@ class AccountStatementCompletionRule(Model):
|
||||
'account_id' : value,
|
||||
...}
|
||||
"""
|
||||
st_obj = self.pool.get('account.bank.statement.line')
|
||||
st_obj = self.pool['account.bank.statement.line']
|
||||
res = {}
|
||||
so_obj = self.pool.get('sale.order')
|
||||
so_id = so_obj.search(cr,
|
||||
uid,
|
||||
[('transaction_id', '=', st_line['transaction_id'])],
|
||||
so_obj = self.pool['sale.order']
|
||||
so_id = so_obj.search(
|
||||
cr, uid, [('transaction_id', '=', st_line['transaction_id'])],
|
||||
context=context)
|
||||
if len(so_id) > 1:
|
||||
raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more than '
|
||||
raise ErrorTooManyPartner(
|
||||
_('Line named "%s" (Ref:%s) was matched by more than '
|
||||
'one partner.') % (st_line['name'], st_line['ref']))
|
||||
if len(so_id) == 1:
|
||||
so = so_obj.browse(cr, uid, so_id[0], context=context)
|
||||
res['partner_id'] = so.partner_id.id
|
||||
res['ref'] = so.name
|
||||
st_vals = st_obj.get_values_for_line(cr,
|
||||
uid,
|
||||
profile_id=st_line['profile_id'],
|
||||
st_vals = st_obj.get_values_for_line(
|
||||
cr, uid, profile_id=st_line['profile_id'],
|
||||
master_account_id=st_line['master_account_id'],
|
||||
partner_id=res.get('partner_id', False),
|
||||
line_type=st_line['type'],
|
||||
@@ -83,12 +80,13 @@ class AccountStatementCompletionRule(Model):
|
||||
res.update(st_vals)
|
||||
return res
|
||||
|
||||
def get_from_transaction_id_and_invoice(self, cr, uid, st_line, context=None):
|
||||
"""
|
||||
Match the partner based on the transaction ID field of the invoice.
|
||||
def get_from_transaction_id_and_invoice(self, cr, uid, st_line,
|
||||
context=None):
|
||||
"""Match the partner based on the transaction ID field of the invoice.
|
||||
Then, call the generic st_line method to complete other values.
|
||||
|
||||
In that case, we always fullfill the reference of the line with the invoice name.
|
||||
In that case, we always fullfill the reference of the line with the
|
||||
invoice name.
|
||||
|
||||
:param dict st_line: read of the concerned account.bank.statement.line
|
||||
:return:
|
||||
@@ -98,9 +96,9 @@ class AccountStatementCompletionRule(Model):
|
||||
'account_id' : value,
|
||||
...}
|
||||
"""
|
||||
st_obj = self.pool.get('account.bank.statement.line')
|
||||
st_obj = self.pool['account.bank.statement.line']
|
||||
res = {}
|
||||
invoice_obj = self.pool.get('account.invoice')
|
||||
invoice_obj = self.pool['account.invoice']
|
||||
invoice_id = invoice_obj.search(
|
||||
cr, uid,
|
||||
[('transaction_id', '=', st_line['transaction_id'])],
|
||||
@@ -134,11 +132,8 @@ class AccountStatementLine(Model):
|
||||
_inherit = "account.bank.statement.line"
|
||||
|
||||
_columns = {
|
||||
# 'additionnal_bank_fields' : fields.serialized('Additionnal infos from bank', help="Used by completion and import system."),
|
||||
'transaction_id': fields.sparse(
|
||||
type='char',
|
||||
string='Transaction ID',
|
||||
size=128,
|
||||
type='char', string='Transaction ID', size=128,
|
||||
serialization_field='additionnal_bank_fields',
|
||||
help="Transaction id from the financial institute"),
|
||||
}
|
||||
@@ -151,19 +146,22 @@ class AccountBankStatement(Model):
|
||||
self, cr, uid, st_line, move_id, debit, credit, currency_id=False,
|
||||
amount_currency=False, account_id=False, analytic_id=False,
|
||||
partner_id=False, context=None):
|
||||
"""Add the period_id from the statement line date to the move preparation.
|
||||
Originaly, it was taken from the statement period_id
|
||||
"""Add the period_id from the statement line date to the move
|
||||
preparation. Originaly, it was taken from the statement period_id
|
||||
|
||||
:param browse_record st_line: account.bank.statement.line record to
|
||||
create the move from.
|
||||
:param int/long move_id: ID of the account.move to link the move line
|
||||
:param float debit: debit amount of the move line
|
||||
:param float credit: credit amount of the move line
|
||||
:param int/long currency_id: ID of currency of the move line to create
|
||||
:param float amount_currency: amount of the debit/credit expressed in the currency_id
|
||||
:param int/long account_id: ID of the account to use in the move line if different
|
||||
from the statement line account ID
|
||||
:param int/long analytic_id: ID of analytic account to put on the move line
|
||||
:param int/long currency_id: ID of currency of the move line to
|
||||
create
|
||||
:param float amount_currency: amount of the debit/credit expressed
|
||||
in the currency_id
|
||||
:param int/long account_id: ID of the account to use in the move
|
||||
line if different from the statement line account ID
|
||||
:param int/long analytic_id: ID of analytic account to put on the
|
||||
move line
|
||||
:param int/long partner_id: ID of the partner to put on the move line
|
||||
:return: dict of value to create() the account.move.line
|
||||
"""
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
{'name': "Bank statement transactionID import",
|
||||
{
|
||||
'name': "Bank statement transactionID import",
|
||||
'version': '1.0',
|
||||
'author': 'Camptocamp',
|
||||
'maintainer': 'Camptocamp',
|
||||
@@ -33,28 +34,25 @@
|
||||
This module brings generic methods and fields on bank statement to deal with
|
||||
the importation of different bank and offices that uses transactionID.
|
||||
|
||||
This module allows you to import your bank transactions with a standard .csv or .xls file
|
||||
(you'll find samples in the 'data' folder). It respects the chosen profile
|
||||
(model provided by the account_statement_ext module) to generate the entries.
|
||||
This module allows you to import your bank transactions with a standard .csv
|
||||
or .xls file (you'll find samples in the 'data' folder). It respects the chosen
|
||||
profile (model provided by the account_statement_ext module) to generate the
|
||||
entries.
|
||||
|
||||
This module can handle a commission taken by the payment office and has the following format:
|
||||
This module can handle a commission taken by the payment office and has the
|
||||
following format:
|
||||
|
||||
* transaction_id: the transaction ID given by the bank/office. It is used as reference
|
||||
in the generated entries and is useful for reconciliation process
|
||||
* transaction_id: the transaction ID given by the bank/office. It is used as
|
||||
reference in the generated entries and is useful for reconciliation process
|
||||
* date: date of the payment
|
||||
* amount: amount paid in the currency of the journal used in the importation profile
|
||||
* amount: amount paid in the currency of the journal used in the importation
|
||||
profile
|
||||
* commission_amount: amount of the comission for each line
|
||||
* label: the comunication given by the payment office, used as communication in the
|
||||
generated entries.
|
||||
* label: the comunication given by the payment office, used as communication in
|
||||
the generated entries.
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'init_xml': [],
|
||||
'update_xml': [
|
||||
],
|
||||
'demo_xml': [],
|
||||
'test': [],
|
||||
'installable': False,
|
||||
'images': [],
|
||||
'auto_install': False,
|
||||
'license': 'AGPL-3',
|
||||
}
|
||||
|
||||
@@ -22,44 +22,45 @@ from account_statement_base_import.parser.file_parser import FileParser
|
||||
|
||||
|
||||
class TransactionIDFileParser(FileParser):
|
||||
"""
|
||||
TransactionID parser that use a define format in csv or xls to import
|
||||
"""TransactionID parser that use a define format in csv or xls to import
|
||||
bank statement.
|
||||
"""
|
||||
|
||||
def __init__(self, parse_name, ftype='csv', extra_fields=None, header=None, **kwargs):
|
||||
"""
|
||||
Add transaction_id in header keys
|
||||
:param char: parse_name: The name of the parser
|
||||
def __init__(self, profile, ftype='csv', extra_fields=None, header=None,
|
||||
**kwargs):
|
||||
"""Add transaction_id in header keys
|
||||
:param char: profile: Reference to the profile
|
||||
:param char: ftype: extension of the file (could be csv or xls)
|
||||
:param dict: extra_fields: extra fields to add to the conversion dict. In the format
|
||||
{fieldname: fieldtype}
|
||||
:param list: header : specify header fields if the csv file has no header
|
||||
:param dict: extra_fields: extra fields to add to the conversion
|
||||
dict. In the format {fieldname: fieldtype}
|
||||
:param list: header : specify header fields if the csv file has no
|
||||
header
|
||||
"""
|
||||
extra_fields = {'transaction_id': unicode}
|
||||
super(TransactionIDFileParser, self).__init__(parse_name, extra_fields=extra_fields,
|
||||
ftype=ftype, header=header, **kwargs)
|
||||
super(TransactionIDFileParser, self).__init__(
|
||||
profile, extra_fields=extra_fields, ftype=ftype, header=header,
|
||||
**kwargs)
|
||||
# ref is replaced by transaction_id thus we delete it from check
|
||||
self.keys_to_validate = [k for k in self.keys_to_validate if k != 'ref']
|
||||
self.keys_to_validate = [
|
||||
k for k in self.keys_to_validate if k != 'ref']
|
||||
del self.conversion_dict['ref']
|
||||
|
||||
@classmethod
|
||||
def parser_for(cls, parser_name):
|
||||
"""
|
||||
Used by the new_bank_statement_parser class factory. Return true if
|
||||
"""Used by the new_bank_statement_parser class factory. Return true if
|
||||
the providen name is generic_csvxls_transaction
|
||||
"""
|
||||
return parser_name == 'generic_csvxls_transaction'
|
||||
|
||||
def get_st_line_vals(self, line, *args, **kwargs):
|
||||
"""
|
||||
This method must return a dict of vals that can be passed to create
|
||||
"""This method must return a dict of vals that can be passed to create
|
||||
method of statement line in order to record it. It is the responsibility
|
||||
of every parser to give this dict of vals, so each one can implement his
|
||||
own way of recording the lines.
|
||||
:param: line: a dict of vals that represent a line of result_row_list
|
||||
:return: dict of values to give to the create method of statement line,
|
||||
it MUST contain at least:
|
||||
:param: line: a dict of vals that represent a line of
|
||||
result_row_list
|
||||
:return: dict of values to give to the create method of statement
|
||||
line, it MUST contain at least:
|
||||
{
|
||||
'name':value,
|
||||
'date':value,
|
||||
@@ -68,13 +69,15 @@ class TransactionIDFileParser(FileParser):
|
||||
'label':value,
|
||||
'commission_amount':value,
|
||||
}
|
||||
In this generic parser, the commission is given for every line, so we store it
|
||||
for each one.
|
||||
In this generic parser, the commission is given for every line, so we
|
||||
store it for each one.
|
||||
"""
|
||||
return {'name': line.get('label', line.get('ref', '/')),
|
||||
return {
|
||||
'name': line.get('label', line.get('ref', '/')),
|
||||
'date': line.get('date', datetime.datetime.now().date()),
|
||||
'amount': line.get('amount', 0.0),
|
||||
'ref': line.get('transaction_id', '/'),
|
||||
'label': line.get('label', ''),
|
||||
'transaction_id': line.get('transaction_id', '/'),
|
||||
'commission_amount': line.get('commission_amount', 0.0)}
|
||||
'commission_amount': line.get('commission_amount', 0.0)
|
||||
}
|
||||
|
||||
@@ -19,29 +19,16 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv.orm import Model
|
||||
from openerp.osv import fields
|
||||
from openerp.osv import orm
|
||||
|
||||
|
||||
class AccountStatementProfil(Model):
|
||||
class AccountStatementProfil(orm.Model):
|
||||
_inherit = "account.statement.profile"
|
||||
|
||||
def get_import_type_selection(self, cr, uid, context=None):
|
||||
"""
|
||||
Has to be inherited to add parser
|
||||
"""
|
||||
res = super(AccountStatementProfil, self).get_import_type_selection(
|
||||
def _get_import_type_selection(self, cr, uid, context=None):
|
||||
"""Has to be inherited to add parser"""
|
||||
res = super(AccountStatementProfil, self)._get_import_type_selection(
|
||||
cr, uid, context=context)
|
||||
res.append(('generic_csvxls_transaction',
|
||||
'Generic .csv/.xls based on SO transaction ID'))
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'import_type': fields.selection(
|
||||
get_import_type_selection,
|
||||
'Type of import',
|
||||
required=True,
|
||||
help="Choose here the method by which you want to import "
|
||||
"bank statement for this profile."),
|
||||
|
||||
}
|
||||
|
||||
@@ -54,4 +54,4 @@
|
||||
'images': [],
|
||||
'auto_install': False,
|
||||
'license': 'AGPL-3',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<field name="name">customer.invoice.transaction.inherit</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="inherit_id" ref="account.invoice_form"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<notebook position="inside">
|
||||
<page string="Transactions datas">
|
||||
@@ -19,7 +18,6 @@
|
||||
<field name="name">account.invoice.tree.inherit</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="inherit_id" ref="account.invoice_tree"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="origin" position="after">
|
||||
<field name="transaction_id" select="2"/>
|
||||
|
||||
@@ -35,7 +35,7 @@ class SaleOrder(Model):
|
||||
}
|
||||
|
||||
def _prepare_invoice(self, cr, uid, order, lines, context=None):
|
||||
#we put the transaction id in the generated invoices
|
||||
# we put the transaction id in the generated invoices
|
||||
invoice_vals = super(SaleOrder, self)._prepare_invoice(
|
||||
cr, uid, order, lines, context=context)
|
||||
invoice_vals.update({
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<record id="view_order_form_transaction" model="ir.ui.view">
|
||||
<field name="name">sale.order.form.transaction</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="type">form</field>
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="payment_term" position="after">
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<field name="name">Hide voucher in invoice</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="inherit_id" ref="account_voucher.view_invoice_customer" />
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='invoice_pay_customer'][last()]"
|
||||
position="replace">
|
||||
@@ -30,7 +29,6 @@
|
||||
<field name="name">Hide voucher in supplier invoice</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="inherit_id" ref="account_voucher.view_invoice_supplier" />
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='invoice_pay_customer'][last()]"
|
||||
position="replace">
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
'version': '1.0.0',
|
||||
'category': 'other',
|
||||
'description': """
|
||||
Prevent voucher creation when importing lines into statement.
|
||||
#############################################################
|
||||
Prevent voucher creation when importing lines into statement
|
||||
============================================================
|
||||
|
||||
When importing invoice or payment into a bank statement or a payment order, normally a
|
||||
draft voucher is created on the line. This module will disable this voucher creation.
|
||||
When importing payment line, date used to populate statement
|
||||
When importing invoice or payment into a bank statement or a payment order,
|
||||
normally a draft voucher is created on the line. This module will disable this
|
||||
voucher creation. When importing payment line, date used to populate statement
|
||||
line will be take from imported line in this order:
|
||||
|
||||
* Date
|
||||
@@ -44,5 +44,4 @@ line will be take from imported line in this order:
|
||||
],
|
||||
'test': [],
|
||||
'installable': False,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_form" />
|
||||
<field eval="100" name="priority"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="voucher_id" position="replace">
|
||||
</field>
|
||||
|
||||
@@ -19,12 +19,11 @@
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.osv import orm
|
||||
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
import time
|
||||
|
||||
|
||||
class AccountStatementFromInvoiceLines(orm.TransientModel):
|
||||
|
||||
_inherit = "account.statement.from.invoice.lines"
|
||||
|
||||
def populate_statement(self, cr, uid, ids, context=None):
|
||||
@@ -39,32 +38,32 @@ class AccountStatementFromInvoiceLines(orm.TransientModel):
|
||||
line_ids = data['line_ids']
|
||||
if not line_ids:
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
line_obj = self.pool.get('account.move.line')
|
||||
statement_obj = self.pool.get('account.bank.statement')
|
||||
statement_line_obj = self.pool.get('account.bank.statement.line')
|
||||
currency_obj = self.pool.get('res.currency')
|
||||
line_date = time.strftime('%Y-%m-%d')
|
||||
statement = statement_obj.browse(cr, uid, statement_id, context=context)
|
||||
line_obj = self.pool['account.move.line']
|
||||
statement_obj = self.pool['account.bank.statement']
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
currency_obj = self.pool['res.currency']
|
||||
line_date = time.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
statement = statement_obj.browse(
|
||||
cr, uid, statement_id, context=context)
|
||||
# for each selected move lines
|
||||
for line in line_obj.browse(cr, uid, line_ids, context=context):
|
||||
ctx = context.copy()
|
||||
# take the date for computation of currency => use payment date
|
||||
ctx['date'] = line_date
|
||||
amount = 0.0
|
||||
|
||||
if line.debit > 0:
|
||||
amount = line.debit
|
||||
elif line.credit > 0:
|
||||
amount = -line.credit
|
||||
|
||||
if line.amount_currency:
|
||||
amount = currency_obj.compute(cr, uid, line.currency_id.id,
|
||||
statement.currency.id, line.amount_currency, context=ctx)
|
||||
elif (line.invoice and line.invoice.currency_id.id != statement.currency.id):
|
||||
amount = currency_obj.compute(cr, uid, line.invoice.currency_id.id,
|
||||
statement.currency.id, amount, context=ctx)
|
||||
|
||||
amount = currency_obj.compute(
|
||||
cr, uid, line.currency_id.id, statement.currency.id,
|
||||
line.amount_currency, context=ctx)
|
||||
elif (line.invoice and
|
||||
line.invoice.currency_id.id != statement.currency.id):
|
||||
amount = currency_obj.compute(
|
||||
cr, uid, line.invoice.currency_id.id, statement.currency.id,
|
||||
amount, context=ctx)
|
||||
context.update({'move_line_ids': [line.id],
|
||||
'invoice_id': line.invoice.id})
|
||||
s_type = 'general'
|
||||
@@ -97,25 +96,25 @@ class AccountPaymentPopulateStatement(orm.TransientModel):
|
||||
def populate_statement(self, cr, uid, ids, context=None):
|
||||
"""Taken from payment addon as no hook is vailable. No function
|
||||
no refactoring, just trimming the part that generates voucher"""
|
||||
line_obj = self.pool.get('payment.line')
|
||||
statement_obj = self.pool.get('account.bank.statement')
|
||||
statement_line_obj = self.pool.get('account.bank.statement.line')
|
||||
currency_obj = self.pool.get('res.currency')
|
||||
|
||||
line_obj = self.pool['payment.line']
|
||||
statement_obj = self.pool['account.bank.statement']
|
||||
statement_line_obj = self.pool['account.bank.statement.line']
|
||||
currency_obj = self.pool['res.currency']
|
||||
if context is None:
|
||||
context = {}
|
||||
data = self.read(cr, uid, ids, [], context=context)[0]
|
||||
line_ids = data['lines']
|
||||
if not line_ids:
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
statement = statement_obj.browse(cr, uid, context['active_id'], context=context)
|
||||
|
||||
statement = statement_obj.browse(
|
||||
cr, uid, context['active_id'], context=context)
|
||||
for line in line_obj.browse(cr, uid, line_ids, context=context):
|
||||
ctx = context.copy()
|
||||
ctx['date'] = line.ml_maturity_date # Last value_date earlier,but this field exists no more now
|
||||
amount = currency_obj.compute(cr, uid, line.currency.id,
|
||||
statement.currency.id, line.amount_currency, context=ctx)
|
||||
# Last value_date earlier,but this field exists no more now
|
||||
ctx['date'] = line.ml_maturity_date
|
||||
amount = currency_obj.compute(
|
||||
cr, uid, line.currency.id, statement.currency.id,
|
||||
line.amount_currency, context=ctx)
|
||||
if not line.move_line_id.id:
|
||||
continue
|
||||
context.update({'move_line_ids': [line.move_line_id.id]})
|
||||
@@ -123,13 +122,14 @@ class AccountPaymentPopulateStatement(orm.TransientModel):
|
||||
cr, uid, line, -amount, statement, context=context)
|
||||
st_line_id = statement_line_obj.create(cr, uid, vals,
|
||||
context=context)
|
||||
|
||||
line_obj.write(cr, uid, [line.id], {'bank_statement_line_id': st_line_id})
|
||||
line_obj.write(
|
||||
cr, uid, [line.id], {'bank_statement_line_id': st_line_id})
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def _prepare_statement_line_vals(self, cr, uid, payment_line, amount,
|
||||
statement, context=None):
|
||||
return {'name': payment_line.order_id.reference or '?',
|
||||
return {
|
||||
'name': payment_line.order_id.reference or '?',
|
||||
'amount': amount,
|
||||
'type': 'supplier',
|
||||
'partner_id': payment_line.partner_id.id,
|
||||
|
||||
Reference in New Issue
Block a user