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